feat(mcp): connect card, MCP docs, and an end-to-end roles/audit test over HTTP
MCP-8 #4972 "Connect card, docs, and end-to-end integration test". - AI access page: a "Connect a client" card with the /mcp URL (from Settings → General → Public base URL, else the request host) and recipes for Claude Code, Claude Desktop (mcp-remote) and other Streamable HTTP clients, plus the reverse-proxy note. The token-created page shows the same recipes with the new token filled in, ready to paste. - docs/core/mcp.md: connecting, the reverse-proxy requirements, the security model (token ceiling, default deny, check-mode default, secrets, audit), tools by role, response conventions, adding tools. docs/architecture.md describes the /mcp dispatch outside the blueprint pipeline; docs/reference/code-map.md gains an MCP section. - tests/integration/test_mcp.py: over HTTP, a viewer token lists no write tools and is refused one by name; an operator token reads get_overview and its maintenance-window create/end land in the audit log as "<user> via MCP" with the token id. (Revoked → 401 was already covered.) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -44,6 +44,30 @@ All routes are Quart Blueprints registered in `app.py`:
|
||||
|
||||
Plugin blueprints are mounted automatically by `load_plugins()` using the plugin directory name as the URL prefix.
|
||||
|
||||
`/ai-access/` (`ai_access_bp`, `steward/ai_access/routes.py`) is where a signed-in user manages their API tokens.
|
||||
|
||||
### `/mcp`: outside the blueprint pipeline
|
||||
|
||||
`mount_mcp(app)` (`steward/mcp/server.py`) wraps `app.asgi_app`. A request to
|
||||
`/mcp` is handed to the MCP SDK's streamable-HTTP app before Quart sees it, so
|
||||
no blueprint, session cookie or `before_request` hook runs for it. Each
|
||||
request then goes through these steps:
|
||||
|
||||
1. **Database gate.** If the database is unreachable, the request gets a 503
|
||||
with a readable message.
|
||||
2. **Bearer resolution.** `api_tokens.resolve_token()` turns the token into a
|
||||
`Caller` (user, effective role, token id). A missing, revoked or expired
|
||||
token gets a 401.
|
||||
3. **Caller context.** The `Caller` and the app are set in ContextVars
|
||||
(`steward/mcp/_context.py`). Tools read them through `current_caller()` and
|
||||
`mcp_app()`, because Quart's `current_app` and `session` do not exist there.
|
||||
4. **Role gate.** `StewardMCPServer.refusal()` hides and refuses tools above
|
||||
the caller's role, and plugin tools whose plugin is disabled.
|
||||
|
||||
The SDK's session manager runs in Quart's `before_serving`/`after_serving`
|
||||
lifecycle. The server is stateless: no MCP session outlives a request. See
|
||||
[MCP server](core/mcp.md) for the security model and the tools.
|
||||
|
||||
---
|
||||
|
||||
## Scheduler
|
||||
|
||||
Reference in New Issue
Block a user