Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
102 lines
3.7 KiB
Markdown
102 lines
3.7 KiB
Markdown
# mcp-gateway
|
|
|
|
Streamable-HTTP [MCP](https://modelcontextprotocol.io) server that lets an AI
|
|
agent work inside **one** organization of the platform. Owned by the org-platform
|
|
workstream; see `docs/plan/TODO-agent-1-org-platform.md` Phase 5.
|
|
|
|
pnpm mcp:dev
|
|
|
|
That runs `python -m app.interfaces.mcp.server`, which binds
|
|
`MCP_GATEWAY_HOST:MCP_GATEWAY_PORT` (127.0.0.1:8765 by default) and serves
|
|
`/mcp` on the streamable-HTTP transport.
|
|
|
|
## What a client sees
|
|
|
|
Eight tools, each guarded by a scope the token was minted with:
|
|
|
|
| tool | scope | what it does |
|
|
|---|---|---|
|
|
| `app_get_profile` | `app:read` | the org's app profile: type, language, framework, build/test commands, port |
|
|
| `tasks_list` | `tasks:read` | the org's tasks, newest first |
|
|
| `tasks_get` | `tasks:read` | one task by id |
|
|
| `tasks_create` | `tasks:write` | a new task for the org's manager, as a user order |
|
|
| `memory_search` | `memory:read` | rules, preferences, lessons, code map |
|
|
| `memory_write` | `memory:write` | teach a `fact` or a `preference` (nothing else) |
|
|
| `knowledge_search` | `memory:read` | the org's long-form knowledge sources |
|
|
| `pipeline_status` | `delivery:read` | delivery state — currently always `{"available": false}` |
|
|
|
|
## Rules the gateway enforces
|
|
|
|
- **The organization comes from the token, never from the arguments.** A tool
|
|
body cannot be pointed at another tenant, whatever it is asked.
|
|
- **A token is shown once.** Mint one in `/settings` → «یکپارچهسازی (MCP)»; the
|
|
raw value (`smo_…`) appears in that one response and is never stored — only its
|
|
hash and a six-character prefix, which is what the audit trail carries.
|
|
- **Scopes are a floor.** A `tasks:read` token cannot create a task.
|
|
- **Every call is audited** (`mcp.tool_call`, token prefix, tool, ok/error) and
|
|
**rate-limited** to `MCP_RATE_LIMIT_PER_MINUTE` (60) per token, in memory.
|
|
- **Every output is redacted.** A secret that reaches the gateway does not leave it.
|
|
- **Two kill switches.** The platform owner can disable the gateway globally
|
|
(`/admin/mcp`) or for one organization; both are honoured on the next call.
|
|
|
|
## Client configuration
|
|
|
|
The gateway speaks plain MCP over HTTP with a bearer token:
|
|
|
|
```json
|
|
{
|
|
"mcpServers": {
|
|
"smart-org": {
|
|
"url": "https://mcp.example.com/mcp",
|
|
"headers": {
|
|
"Authorization": "Bearer smo_PASTE_YOUR_TOKEN"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
Local, without Caddy:
|
|
|
|
```bash
|
|
curl -sS http://127.0.0.1:8765/mcp \
|
|
-H "Authorization: Bearer smo_PASTE_YOUR_TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-H "Accept: application/json, text/event-stream" \
|
|
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
|
|
```
|
|
|
|
## Putting Caddy in front of it
|
|
|
|
Suggested host: `mcp.<root domain>` (the platform owner configures Caddy — see
|
|
`infra/`):
|
|
|
|
```caddy
|
|
mcp.example.com {
|
|
reverse_proxy 127.0.0.1:8765
|
|
}
|
|
```
|
|
|
|
Keep the gateway bound to loopback in the normal case: the process is small, it
|
|
has no session state of its own, and the platform's TLS termination and rate
|
|
limiting belong at the edge. `MCP_GATEWAY_HOST=0.0.0.0` is for a container that
|
|
must be reached directly, not for a public deployment without a proxy.
|
|
|
|
## Environment
|
|
|
|
| variable | default | meaning |
|
|
|---|---|---|
|
|
| `MCP_GATEWAY_HOST` | `127.0.0.1` | bind address |
|
|
| `MCP_GATEWAY_PORT` | `8765` | bind port |
|
|
| `MCP_GATEWAY_ENABLED` | `true` | the owner's global switch |
|
|
| `MCP_TOKEN_TTL_DAYS` | `90` | default token lifetime |
|
|
| `MCP_RATE_LIMIT_PER_MINUTE` | `60` | calls per token per minute, in memory |
|
|
|
|
## Tests
|
|
|
|
pnpm --filter @agy/api test tests/test_mcp.py
|
|
|
|
They drive `McpGateway.call` directly (the SDK is a thin adapter over it) and
|
|
cover tenant isolation, scope enforcement, revoked/expired tokens, the rate
|
|
limit, audit rows, and secret redaction.
|