diff --git a/README.md b/README.md index 5487d1f..e34af7e 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,8 @@ no spend, no volume-cost actions.** | [`INTENT.md`](INTENT.md) | Why this exists; boundaries | | [`specs/ArchitectureBlueprint.md`](specs/ArchitectureBlueprint.md) | Architecture, phases, policy model | | [`research/2026-07-21-mcp-gateway-and-governed-domain-assistant.md`](research/2026-07-21-mcp-gateway-and-governed-domain-assistant.md) | External + internal research | -| [`docs/operator-runbook.md`](docs/operator-runbook.md) | How to run and verify Phase 1 | +| [`docs/operator-runbook.md`](docs/operator-runbook.md) | How to run and verify Phase 1 (REST) | +| [`docs/mcp-integration.md`](docs/mcp-integration.md) | How to run and verify Phase 2 (MCP) | ## Status @@ -26,24 +27,35 @@ Phase 1 runtime is implemented: - REST endpoints: `/v1/health`, `/v1/accounts`, `/v1/transactions`, `/v1/snapshot` - audit metadata, rate limiting, concurrency bounds, tests -Phase 2 (MCP surface, `workplans/QONTO-WP-0003-mcp-surface.md`) is in -progress: a streamable-HTTP MCP adapter is mounted at `/mcp` on the same -capability core and policy kernel as REST, with tools `qonto_org_summary`, -`qonto_list_transactions`, and `qonto_cost_run_rate_hints`. The endpoint is -gated by a shared-secret bearer token (`QONTO_ASSISTANT_MCP_TOKEN`) — see -`docs/mcp-integration.md` for the auth model, its gap vs. the OIDC/workload -target, and the shared multi-harness client config snippet. The -`finance-qonto-read` tool profile is not yet implemented. +Phase 2 (MCP surface, `workplans/QONTO-WP-0003-mcp-surface.md`) is +**finished**: a streamable-HTTP MCP adapter is mounted at `/mcp` on the same +capability core and policy kernel as REST — proven identical by test, not +just by construction (`tests/test_policy.py`, `tests/test_audit_parity.py`) +— with tools `qonto_org_summary`, `qonto_list_transactions`, and +`qonto_cost_run_rate_hints`. The endpoint is gated by a shared-secret bearer +token (`QONTO_ASSISTANT_MCP_TOKEN`); see `docs/mcp-integration.md` for the +auth model, its explicit gap vs. the OIDC/workload target (no issuer exists +in this fleet yet), and the shared multi-harness client config snippet. The +`finance-qonto-read` agent-harness tool profile is documented (qonto-assistant +side) but not yet registered in `agent-harness` itself — separate repo, +separate workplan. Current verification: -- `PYTHONPATH=src ../state-hub/.venv/bin/python -m pytest` → `16 passed` +- `PYTHONPATH=src ../state-hub/.venv/bin/python -m pytest` → `31 passed` - `python3 -m compileall src tests scripts` - `../state-hub/.venv/bin/python scripts/smoke_rest_api.py --python ../state-hub/.venv/bin/python` +- `../state-hub/.venv/bin/python scripts/smoke_mcp.py --python ../state-hub/.venv/bin/python` Local fixture-backed smoke mode is available through `QONTO_FIXTURE_DIR`, so the -service can be exercised without real Qonto credentials. The smoke path checks -both a `31`-day recent snapshot and a `90`-day recurring-cost snapshot. +service can be exercised without real Qonto credentials. The REST smoke checks +both a `31`-day recent snapshot and a `90`-day recurring-cost snapshot; the MCP +smoke additionally exercises the bearer-token auth gate and an out-of-catalog +tool deny path. + +Phase 3 (flex-auth resource `finance.qonto.read`, graduated quotas, +redaction profiles) is not started — see `workplans/QONTO-WP-0003-mcp-surface.md` +closure notes for the seed. Normal local workflow, when toolchain support exists: diff --git a/workplans/QONTO-WP-0003-mcp-surface.md b/workplans/QONTO-WP-0003-mcp-surface.md index 6ea02f3..bb08eb5 100644 --- a/workplans/QONTO-WP-0003-mcp-surface.md +++ b/workplans/QONTO-WP-0003-mcp-surface.md @@ -4,11 +4,11 @@ type: workplan title: "Phase 2 — MCP surface for all harnesses" domain: infotech repo: qonto-assistant -status: active +status: finished owner: codex topic_slug: the-custodian created: "2026-07-22" -updated: "2026-07-22" +updated: "2026-07-23" state_hub_workstream_id: "6ad0e906-5fbe-4b9e-8307-0abfc5c4a60a" --- @@ -299,7 +299,7 @@ exits `0` against fixtures, no real Qonto credentials. `pytest` → `31 passed` ```task id: QONTO-WP-0003-T07 -status: todo +status: done priority: low state_hub_task_id: "1dc90a1e-0dcc-40c1-80c9-c7403a348db0" ``` @@ -308,3 +308,49 @@ 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`. + +**Closed 2026-07-23.** T01–T06 all done. Final end-to-end confirmation +before closing: `pytest` → `31 passed`; `scripts/smoke_rest_api.py` and +`scripts/smoke_mcp.py` both exit `0` against fixtures in the same run; +`python3 -m compileall src tests scripts` clean. + +**Identical policy outcomes, confirmed two ways, not asserted:** +`tests/test_policy.py::test_policy_decision_identical_across_protocols` +(T01) proves `PolicyEngine.decide()` never branches on `protocol` at the +unit level, and `tests/test_audit_parity.py` (T05) proves the resulting +audit events are schema-identical for the same capability call over both +transports, for both an allow and a deny path. Both REST and MCP call +through the same `CapabilityService._execute` → `PolicyEngine.decide()` +path — there is structurally no second policy implementation to drift. + +**What Phase 2 shipped:** streamable-HTTP MCP adapter mounted at `/mcp` +(T01); three capability tools — `qonto_org_summary`, +`qonto_list_transactions`, `qonto_cost_run_rate_hints` — mapped 1:1 to the +existing REST-allowed capabilities, `snapshot_bundle` deliberately not +mirrored as it isn't a separate privilege (T02); shared-secret bearer-token +workload auth (`QONTO_ASSISTANT_MCP_TOKEN`) plus one client config snippet +covering Claude Code/Desktop/Cursor/Codex-style harnesses, with the real +OIDC/workload-identity gap called out explicitly rather than faked (T03); +the qonto-assistant-side contract for agent-harness's `finance-qonto-read` +tool profile, checked against the real `agent_harness/profiles.py` schema +(T04); REST/MCP audit-schema parity pinned by test (T05); an MCP smoke +script exercising the full tool catalog, the auth gate, and the +out-of-catalog deny path against fixtures with no real credentials (T06). + +**Known gaps carried forward, not closed by this workplan:** +- Real OIDC/workload-identity federation (no issuer exists in this fleet — + see `docs/mcp-integration.md`'s auth model section). The bearer token is + a deployable interim, not the blueprint's target state. +- Per-actor identity (`X-Actor-*` headers) is still self-asserted, not + cryptographically bound to the workload-auth token. +- `finance-qonto-read` is documented but not registered in + `agent-harness/agent_harness/profiles.py` — that's agent-harness's own + repo/workplan to pick up. + +**Phase 3 seed** (`specs/ArchitectureBlueprint.md` §6 Phase 3, not started): +a flex-auth resource `finance.qonto.read` (+ later `.export`) to bind actor +identity to the workload-auth layer instead of self-asserted headers; +graduated quotas and differentiated response-redaction profiles; optional +Envoy/gateway mesh in front for multi-assistant deployments. The +`QONTO_ASSISTANT_REQUIRED_SCOPE`/`QONTO_ASSISTANT_ENFORCE_SCOPE` settings +already exist in `config.py` as the landing spot for that scope name.