Streamable-HTTP MCP adapter on the same capability core and policy kernel as Phase 1's REST surface, per specs/ArchitectureBlueprint.md Phase 2. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
5.6 KiB
| id | type | title | domain | repo | status | owner | topic_slug | created | updated |
|---|---|---|---|---|---|---|---|---|---|
| QONTO-WP-0003 | workplan | Phase 2 — MCP surface for all harnesses | infotech | qonto-assistant | ready | codex | the-custodian | 2026-07-22 | 2026-07-22 |
Phase 2 — MCP surface for all harnesses
Execute Phase 2 of specs/ArchitectureBlueprint.md §6: add a
streamable-HTTP MCP adapter on top of the same protocol-neutral capability
core and decide(request, claims) -> Allow|Deny policy kernel that Phase 1's
REST surface already uses. No new capabilities and no policy fork — MCP is a
second transport onto the existing allow-listed reads
(org_summary, list_transactions, cost_run_rate_hints, optionally
snapshot_bundle). Spend/volume-cost/credential-exfil stay hard-denied.
Depends on: QONTO-WP-0002 (done) for the capability core, policy kernel,
Qonto client, and audit layer this phase reuses. Non-goals: flex-auth
resource scopes and multi-tenant path layout (Phase 3/4); vendor
qonto-mcp-server as an internal backend (blueprint §4.3/§5.1 — explicitly
rejected).
Task: MCP adapter skeleton on the shared capability core
id: QONTO-WP-0003-T01
status: todo
priority: high
Pick an MCP server library/transport for the Python 3.12 stack (streamable-HTTP,
per blueprint §6 Phase 2.1 — not stdio-only). Scaffold the adapter under
src/qonto_assistant/ alongside the existing REST adapter, routing every tool
call through the same CapabilityRequest → decide() path used by REST
(protocol: mcp on the request). Do not duplicate policy logic in the MCP
layer.
Done when: adapter starts, exposes an empty/smoke tool list, and a manual
decide() call for a known-deny case is proven to hit the identical policy
path as the REST equivalent (e.g. shared unit test parametrized over both
protocols).
Task: MCP tool catalog mapped to allowed capabilities
id: QONTO-WP-0003-T02
status: todo
priority: high
Implement MCP tools per the capability map in specs/ArchitectureBlueprint.md
§5:
| Internal capability id | MCP tool name |
|---|---|
org_summary |
qonto_org_summary |
list_transactions |
qonto_list_transactions |
cost_run_rate_hints |
qonto_cost_run_rate_hints |
snapshot_bundle |
optional — only if it stays a pure orchestrator over already-allowed reads, not a bigger export |
Tool names may differ from REST paths but must resolve to the same internal
capability id and go through the same deny-reason vocabulary (authz, tenant
scope, argument constraint, credential exfil). No find_counterparties or any
write/spend-shaped tool — those stay hard-denied per Phase 1 policy.
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.
Task: Client auth and one shared config snippet
id: QONTO-WP-0003-T03
status: todo
priority: high
Wire OIDC/workload auth for the MCP endpoint — no bank secrets ever reach the client, matching the REST surface's custody model (blueprint §4.1, §7). Produce a single documented client config snippet usable across Claude Code, Codex, Cursor, and Grok-style harnesses: URL + auth only, no per-harness variants.
Done when: the snippet is in docs/operator-runbook.md (or a new
docs/mcp-integration.md), and a local smoke connects a real MCP client
against the running adapter using only that snippet.
Task: agent-harness tool profile finance-qonto-read
id: QONTO-WP-0003-T04
status: todo
priority: medium
Register the finance-qonto-read tool profile (blueprint §6 Phase 2.3) so
harness-side configuration grants this MCP surface only to the intended
assistant context, not blanket per-harness key sprawl. Coordinate with
whichever agent-harness config surface is authoritative for profile
definitions (outside this repo) — this task covers qonto-assistant's side:
documenting the profile contract (tool names, required scopes) it expects
callers to present.
Done when: profile contract is documented and referenced from the MCP integration doc; at least one harness config example uses it end-to-end.
Task: Unified audit schema across REST and MCP
id: QONTO-WP-0003-T05
status: todo
priority: medium
Confirm MCP tool calls emit the exact same structured audit event shape as REST calls (actor, capability, decision, deny_reason, latency, upstream HTTP status, policy version — no secret fields), per blueprint §6 Phase 2.4. State Hub continues to receive operator-level progress notes, not per-call hot-path events — do not wire per-invocation State Hub writes.
Done when: a test asserts REST and MCP audit records for the same capability call are schema-identical modulo protocol field.
Task: MCP smoke path + operator runbook update
id: QONTO-WP-0003-T06
status: todo
priority: medium
Add an MCP-equivalent of scripts/smoke_rest_api.py: start the service,
connect over streamable-HTTP, exercise each allowed tool against fixture data
(QONTO_FIXTURE_DIR), and confirm deny behavior for an out-of-catalog tool.
Extend docs/operator-runbook.md with MCP start/verify instructions
alongside the existing REST section.
Done when: smoke script passes locally against fixtures without real Qonto credentials, matching the Phase 1 REST smoke pattern.
Task: Closure review
id: QONTO-WP-0003-T07
status: todo
priority: low
Mark workplan finished when T01–T06 are done; confirm REST and MCP surfaces
enforce identical policy outcomes end to end. Note Phase 3 seed (flex-auth
resource finance.qonto.read, graduated quotas, redaction profiles) in
closure. Run statehub fix-consistency.