QONTO-WP-0003-T02: MCP tool catalog on the shared capability core
Add qonto_org_summary, qonto_list_transactions, and qonto_cost_run_rate_hints MCP tools, all routed through CapabilityService with protocol="mcp" -- same PolicyEngine.decide() path as REST, same deny-reason vocabulary. Skip snapshot_bundle as an MCP tool (REST already covers the composite read; not a separate privilege). CapabilityService now threads protocol through _execute/_emit_audit instead of hardcoding "rest". cost_run_rate_hints gets its own service method since it's an independent policy capability, not only a snapshot sub-field. Actor identity reuses REST's X-Actor-* header convention via a shared auth.actor_claims_from_headers(), read from the MCP Context's request when present. Fixed streamable_http_path defaulting to "/mcp", which doubled to "/mcp/mcp" once mounted under the "/mcp" prefix. Verified end-to-end with the mcp SDK's streamablehttp_client against the live fixture-backed server: tool list, allow/deny paths, and X-Actor-ID flowing through to the audit log exactly like REST. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
ba2612682f
commit
b4b1dc1c7b
7 changed files with 284 additions and 29 deletions
|
|
@ -70,7 +70,7 @@ QONTO-WP-0003-T02.
|
|||
|
||||
```task
|
||||
id: QONTO-WP-0003-T02
|
||||
status: todo
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "f9e0d403-d6ae-4800-8d42-847c3d70827f"
|
||||
```
|
||||
|
|
@ -94,6 +94,30 @@ Done when: unit tests cover allow paths for each tool and deny paths for
|
|||
out-of-catalog / spend-shaped tool names, mirroring the REST policy tests from
|
||||
QONTO-WP-0002-T02.
|
||||
|
||||
**Done 2026-07-22:** Implemented `qonto_org_summary`, `qonto_list_transactions`,
|
||||
and `qonto_cost_run_rate_hints` in `mcp_server.py`, all routed through
|
||||
`CapabilityService` with `protocol="mcp"` (skipped `snapshot_bundle` — REST
|
||||
already covers the composite read and it isn't a separate privilege).
|
||||
`CapabilityService` now threads `protocol: ProtocolName` through `_execute`
|
||||
and `_emit_audit` instead of hardcoding `"rest"`; added
|
||||
`get_cost_run_rate_hints()` + `_build_cost_run_rate_hints_payload()` since
|
||||
`cost_run_rate_hints` is its own policy capability, not only a snapshot
|
||||
sub-field. Actor identity uses the same `X-Actor-*` header convention as
|
||||
REST — `auth.py` now exposes a shared `actor_claims_from_headers()`, read
|
||||
from the MCP `Context`'s underlying Starlette request when present, falling
|
||||
back to defaults for stdio/no-request-context callers. Fixed a routing bug:
|
||||
`FastMCP`'s default `streamable_http_path="/mcp"` plus mounting at `/mcp`
|
||||
doubled to `/mcp/mcp` — set `streamable_http_path="/"` on the sub-app instead.
|
||||
|
||||
Verified: `tests/test_mcp_server.py` covers tool listing, an allow path
|
||||
(`qonto_org_summary`, asserts redacted output + `protocol: "mcp"` audit
|
||||
event), a deny path (`qonto_list_transactions` oversized `page_size` →
|
||||
`ToolError`, `deny_reason: "arg_constraint"`), and `qonto_cost_run_rate_hints`.
|
||||
Also ran a real end-to-end check with the `mcp` SDK's `streamablehttp_client`
|
||||
against the live server (fixture-backed): tool list, `qonto_org_summary` call,
|
||||
and confirmed `X-Actor-ID` header flows through to the audit log exactly like
|
||||
REST. `pytest` → `25 passed`; `python3 -m compileall src tests scripts`.
|
||||
|
||||
## Task: Client auth and one shared config snippet
|
||||
|
||||
```task
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue