From d643e552f4567116a3c93af7712b7a6bfb9bfef7 Mon Sep 17 00:00:00 2001 From: tegwick Date: Wed, 22 Jul 2026 21:36:38 +0200 Subject: [PATCH] Draft QONTO-WP-0003: Phase 2 MCP surface workplan 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 --- workplans/QONTO-WP-0003-mcp-surface.md | 159 +++++++++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 workplans/QONTO-WP-0003-mcp-surface.md diff --git a/workplans/QONTO-WP-0003-mcp-surface.md b/workplans/QONTO-WP-0003-mcp-surface.md new file mode 100644 index 0000000..c3c8462 --- /dev/null +++ b/workplans/QONTO-WP-0003-mcp-surface.md @@ -0,0 +1,159 @@ +--- +id: QONTO-WP-0003 +type: workplan +title: "Phase 2 — MCP surface for all harnesses" +domain: infotech +repo: qonto-assistant +status: ready +owner: codex +topic_slug: the-custodian +created: "2026-07-22" +updated: "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 + +```task +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 + +```task +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 + +```task +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` + +```task +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 + +```task +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 + +```task +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 + +```task +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`.