Skip to main content
Glama
techakash32

MCP Order Management with RBAC

by techakash32
README.md
# Order Management — RBAC with MCP

Role-Based Access Control enforced **inside the MCP server**, not the
FastAPI client. FastAPI only authenticates (issues/validates JWTs);
every permission decision is made by `permissions.py` and applied by
the MCP tools in `order_checking.py`.

```
Client -> FastAPI (auth: JWT) -> MCP Server (authz: RBAC) -> MySQL
```

## Roles

| Action                      | USER | MANAGER       | ADMIN |
| ---------------------------- | ---- | ------------- | ----- |
| View own order                | Y   | Y            | Y    |
| View managed user's order     | N   | Y            | Y    |
| View another user's order     | N   | N            | Y    |
| Refund own order               | Y   | Y            | Y    |
| Refund managed user's order   | N   | Y            | Y    |
| Refund anyone                 | N   | N            | Y    |
| Delete order                  | N   | N            | Y    |
| Create manager                | N   | N            | Y    |
| View users                    | N   | managed users | all   |

## Setup

1. Create the database and tables:
   ```bash
   mysql -u root -p < sql/schema.sql
   ```

2. Copy `.env.example` to `.env` and fill in real values (at minimum
   set a strong `JWT_SECRET` and your real `DATABASE_URL`), then
   export them into your shell / process manager.

3. Install dependencies:
   ```bash
   pip install -r requirements.txt
   ```

4. Seed example users/orders (akash=ADMIN, raj=MANAGER, aman/vivek=USER
   managed by raj):
   ```bash
   python sql/seed.py
   ```

5. Run the MCP server:
   ```bash
   python order_checking.py
   ```

6. Run the FastAPI app (separate process/terminal):
   ```bash
   uvicorn main:app --reload
   ```

## Usage

```bash
# Log in, get a JWT
curl -X POST http://127.0.0.1:8000/login \
  -H "Content-Type: application/json" \
  -d '{"username": "raj", "password": "managerpass123"}'

# Use the token on protected routes
curl http://127.0.0.1:8000/order_status/ORD101 \
  -H "Authorization: Bearer <token>"

curl -X POST http://127.0.0.1:8000/refund/ORD101 \
  -H "Authorization: Bearer <token>"
```

## Files

| File               | Responsibility                                   |
| ------------------- | ------------------------------------------------- |
| `config.py`         | Env-driven settings (DB URL, JWT secret, ports)    |
| `auth.py`            | Password hashing, JWT issuing/validation, `get_current_user` dependency |
| `permissions.py`     | The RBAC rules -- the only place authorization decisions are made |
| `database.py`        | SQL access: users, orders, audit log writes        |
| `audit.py`            | Logs every tool call (allowed or denied) to file + DB |
| `order_checking.py`  | The MCP server -- tools call `permissions.authorize()` before touching data |
| `main.py`             | FastAPI app: `/login` + JWT-protected routes that forward identity to MCP |
| `schemas.py`          | Pydantic request/response models |
| `sql/schema.sql`      | Table definitions (users, orders, audit_logs) |
| `sql/seed.py`         | Creates example users/orders matching the permission matrix above |

## Security notes

- The MCP server trusts the `username`/`role` passed in from FastAPI.
  In this design FastAPI is the only client allowed to reach the MCP
  server (bind it to localhost / an internal network, not the public
  internet) -- the JWT check happens once, at the FastAPI edge.
- Every tool call is written to `audit_logs` (and `logs/app.log`)
  with the outcome and reason, whether allowed or denied.
- Rotate `JWT_SECRET` via environment variable / secret manager --
  never commit it.