qonto-assistant/workplans/QONTO-WP-0003-mcp-surface.md
tegwick d643e552f4 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 <noreply@anthropic.com>
2026-07-22 21:36:38 +02:00

159 lines
5.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
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 T01T06 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`.