636 lines
28 KiB
Markdown
636 lines
28 KiB
Markdown
# Architecture Blueprint — Governed Qonto Assistant
|
||
|
||
> Status: blueprint v0.2 — 2026-07-21 (revised after repo review)
|
||
> Repo: **qonto-assistant** (canonical home).
|
||
> Context: BINKY-WP-0005 finished (live OpenBao lane + first read-only pull).
|
||
> Question: how do multiple coding agents / harnesses interact with Qonto
|
||
> **conveniently and under one policy**, without wiring vendor MCP into each
|
||
> client and accepting policy drift?
|
||
> Related: `INTENT.md`, `research/2026-07-21-mcp-gateway-and-governed-domain-assistant.md`,
|
||
> binky-control `integrations/qonto-mcp.md`, AutonomyPolicy, agent-harness
|
||
> ADR-001, ops-warden catalog `binky-qonto-api`, CCR-2026-0008.
|
||
|
||
---
|
||
|
||
## 1. Problem
|
||
|
||
We already can:
|
||
|
||
1. Fetch company Qonto credentials from OpenBao (`tenants/binky/qonto-api`).
|
||
2. Call Qonto’s thirdparty REST API (proven: first pull, CostRunRate v2).
|
||
3. Map those credentials into the self-hosted `qonto/qonto-mcp-server` env
|
||
shape (`QONTO_API_KEY` / `QONTO_ORGANIZATION_ID`).
|
||
|
||
What we must **not** do as the long-term pattern:
|
||
|
||
- Install vendor MCP (or raw API keys) into Claude Desktop, Claude Code,
|
||
Codex, Cursor, Grok, agent-harness, executor-worker, etc. **separately**.
|
||
- Rely on each harness’s local tool allow-list as the only spend control.
|
||
- Hope every client remembers “read-only forever.”
|
||
|
||
Why that fails:
|
||
|
||
| Failure mode | Effect |
|
||
| --- | --- |
|
||
| **Policy drift** | One harness allows a write tool; another forbids it. Same company key, different blast radius. |
|
||
| **Credential sprawl** | Keys appear in N configs, Docker args, session env dumps. |
|
||
| **Audit fragmentation** | No single log of “which agent did what to the bank.” |
|
||
| **Qonto cannot help** | Business API keys are **not** scope-limited server-side (confirmed in design). There is no “read-only API key” or MCP-side spend ACL from Qonto. |
|
||
| **Volume-cost tools** | Cards, invoicing, payment requests, transfers, and some “create” operations may incur fees or move money — must be blocked **before** the vendor sees them. |
|
||
|
||
**Founder policy for now (normative intent):**
|
||
|
||
> Agents may inspect balances, transactions, and statements for control-plane
|
||
> and finance awareness. Agents must **not** initiate spending, transfers,
|
||
> card operations, or any action with volume/transaction cost. Payments stay
|
||
> Red lane (human in Qonto app).
|
||
|
||
That is stronger than “read-only MCP tools” in the abstract: it is a **business
|
||
policy** that must be enforced at a choke point, not a client preference.
|
||
|
||
---
|
||
|
||
## 2. What industry patterns say
|
||
|
||
Research (2025–2026 MCP ecosystem) converges on the same shape your proxy
|
||
intuition already points at.
|
||
|
||
### 2.1 MCP gateway / proxy as the control plane
|
||
|
||
Production patterns treat MCP **clients** (agent harnesses) as untrusted for
|
||
tool policy, and put a **gateway** between them and tools:
|
||
|
||
- **Envoy AI Gateway MCPRoute** — multiplex backends, **toolSelector**
|
||
allow/deny, OAuth on the gateway, upstream API-key injection so clients
|
||
never hold vendor secrets, CEL rules on `tools/call` params.
|
||
- **Kong AI Gateway MCP Tool ACLs** — default-deny, per-consumer tool
|
||
subsets at the gateway (solves “all-or-nothing” MCP exposure).
|
||
- **Speakeasy / Portkey / Permit MCP Gateway** framing — authn, RBAC, audit
|
||
of every tool call, policy evaluated **on each call**, not only at
|
||
connect time.
|
||
- **Harness’s own MCP guidance** — gateways for centralized credentials,
|
||
governance, audit, single endpoint vs N direct MCP connections.
|
||
|
||
Common lesson: **clients connect to one governed endpoint; the gateway owns
|
||
credentials, tool inventory, and policy.**
|
||
|
||
### 2.2 MCP security best practices (protocol-level)
|
||
|
||
From MCP security guidance and industry write-ups:
|
||
|
||
| Practice | Application to Qonto |
|
||
| --- | --- |
|
||
| **No token passthrough** | Clients must not present the Qonto bank key. The assistant service is the only OAuth/API client to Qonto. |
|
||
| **Tool-level scopes, not server-level** | “Access to Qonto MCP” is too coarse; allow `list_transactions`, deny `create_transfer`. |
|
||
| **Default deny** | Unknown tools fail closed. |
|
||
| **JIT escalation for high privilege** | Any future spend path is Yellow/Orange/Red with explicit re-authorization — never session-global “write mode.” |
|
||
| **Confused-deputy / static client ID** | If the service ever OAuth-proxies, consent and audience binding matter; for API-key custody we avoid OAuth to Qonto entirely. |
|
||
| **Audit end-to-end** | Actor (harness/agent id) + tool + args hash + decision + latency; no secrets in logs. |
|
||
|
||
### 2.3 Domain capability service (not “another harness”)
|
||
|
||
A second pattern, older than MCP: **BFF / capability API** for a domain
|
||
(banking, email, CRM). Agents call a **narrow, policy-shaped interface**
|
||
(“get runway snapshot”) rather than the full vendor surface. That is how
|
||
email-connect already behaves for IMAP: consumer never holds IMAP secrets
|
||
in the product path; it goes through custody + a purpose-built reader.
|
||
|
||
**llm-connect is the wrong home** for this: it is multi-provider **LLM**
|
||
abstraction (adapters, routing, cost models). Putting bank tools there
|
||
blurs the layer map (ARCHITECTURE-LAYERS.md) and couples finance policy to
|
||
model routing.
|
||
|
||
**agent-harness is the wrong home for Qonto policy itself**, but the right
|
||
home for *session* binding: harness ADR-001 already says the harness is the
|
||
credential holder and policy enforcement point **for agent sessions**. That
|
||
must not mean “each harness reimplements bank policy.” It means the harness
|
||
only grants agents a **named tool profile** that talks to **already-governed**
|
||
domain services. Domain policy lives **below** the harness.
|
||
|
||
### 2.4 Synthesis
|
||
|
||
| Pattern | Use for Qonto? |
|
||
| --- | --- |
|
||
| Wire vendor MCP into every harness | **No** — policy drift, key sprawl |
|
||
| Generic MCP gateway only (Envoy/Kong in front of vendor MCP) | **Partial** — good transport + tool filter; weak on **semantic** spend rules and finance-shaped tools |
|
||
| **Governed domain assistant** (our MCP + REST, policy inside) | **Yes — recommended** |
|
||
| Flex-auth decisions on every call | **Yes** — who may call; lane/risk attributes |
|
||
| OpenBao + ops-warden for bank key | **Yes** — already live; only the assistant fetches |
|
||
|
||
Your “qonto-proxy governed API & MCP service” is the right **class** of
|
||
solution. Name it as a **capability assistant**, not only a dumb reverse
|
||
proxy: it should *own* a safe tool surface, not only filter someone else’s.
|
||
|
||
---
|
||
|
||
## 3. Goals and non-goals
|
||
|
||
### Goals
|
||
|
||
1. **One policy** for “no spend / no volume-cost actions” across all agents.
|
||
2. **One credential** path (existing OpenBao lane); harnesses never see bank keys.
|
||
3. **Convenient agent UX**: natural “ask the finance assistant” via MCP and/or
|
||
small REST helpers used by scripts and rhythm sessions.
|
||
4. **Auditability**: every call attributable to agent/harness/session.
|
||
5. **Fits NetKingdom**: flex-auth, OpenBao, ops-warden, agent-harness
|
||
instances, activity-core schedules — no parallel IAM.
|
||
6. **Dogfood-ready**: internal use is product evidence (DogfoodPolicy).
|
||
|
||
### Non-goals (v1)
|
||
|
||
- Replacing DATEV / StB / DUO accounting.
|
||
- Automating payments, SEPA, card issuance, expense submission.
|
||
- Hosted Qonto OAuth connector (`mcp.qonto.com`) as corporate custody path.
|
||
- Making llm-connect a tool gateway.
|
||
- Per-harness custom Qonto integrations.
|
||
|
||
---
|
||
|
||
## 4. Recommended architecture
|
||
|
||
### 4.1 One-liner
|
||
|
||
> **Build a small “Qonto Governed Assistant” service that is the only
|
||
> component allowed to hold the bank API key and speak Qonto’s protocol.
|
||
> Expose a **policy-shaped** MCP server and a thin REST API. All harnesses
|
||
> are dumb clients of that service. Policy is code + tests in one place.**
|
||
|
||
### 4.2 Context diagram
|
||
|
||
```text
|
||
┌──────────────────────────────────────────────────────────────────────────┐
|
||
│ Agent clients │
|
||
│ Claude Code · Codex · Cursor · Grok · agent-harness sessions · scripts │
|
||
└───────────────┬───────────────────────────────┬──────────────────────────┘
|
||
│ MCP (streamable-HTTP) │ REST (JSON)
|
||
▼ ▼
|
||
┌──────────────────────────────────────────────────────────────────────────┐
|
||
│ Qonto Governed Assistant («qonto-assistant») │
|
||
│ │
|
||
│ Authn/Authz edge │
|
||
│ OIDC / workload id / mTLS + optional flex-auth │
|
||
│ │ │
|
||
│ ▼ │
|
||
│ Protocol adapters │
|
||
│ MCP adapter REST adapter │
|
||
│ │ │ │
|
||
│ └──────────┬────────┘ │
|
||
│ ▼ │
|
||
│ Capability service core │
|
||
│ canonical capability ids + response contracts │
|
||
│ │ │
|
||
│ ▼ │
|
||
│ Policy engine │
|
||
│ default-deny, tenant scope, arg constraints, data class rules │
|
||
│ │ │
|
||
│ ┌─────┴──────────┐ │
|
||
│ ▼ ▼ │
|
||
│ Qonto adapter Audit emitter │
|
||
│ REST client structured events │
|
||
│ │ │ │
|
||
└──────┼────────────────┼──────────────────────────────────────────────────┘
|
||
│ │
|
||
▼ ▼
|
||
OpenBao tenants/<tenant>/qonto-api stdout / log sink / metrics
|
||
│
|
||
▼
|
||
Qonto thirdparty API
|
||
```
|
||
|
||
Optional later: put **Envoy AI Gateway** (or similar) **in front of**
|
||
`qonto-assistant` for fleet-wide MCP aggregation with other domain assistants
|
||
(email, issue-core, …). Do **not** put the gateway *directly* in front of
|
||
vendor `qonto-mcp-server` as the sole control — semantic policy is thinner
|
||
there.
|
||
|
||
### 4.3 Why a domain assistant beats “proxy the vendor MCP only”
|
||
|
||
| Approach | Pros | Cons |
|
||
| --- | --- | --- |
|
||
| **A. Gateway + vendor MCP** | Fast; toolSelector; industry-standard | Vendor tool names/args still drive the model; hard to encode “no volume cost”; vendor MCP is unmaintained / dual-use with write tools |
|
||
| **B. Domain assistant (recommended)** | Safe tool catalog we design; semantic policy; dual MCP+REST; dogfood product surface | Build cost |
|
||
| **C. Per-harness allow-lists** | Zero new service | Guaranteed drift; keys leak into clients |
|
||
|
||
**Hybrid that works:** implement **B**; if we still want vendor MCP for
|
||
parity experiments, run it **only as an internal backend** behind the same
|
||
policy gate (never published to harnesses). Prefer **direct REST client**
|
||
inside the assistant (we already proved REST) — fewer moving parts than
|
||
shelling out to unmaintained MCP.
|
||
|
||
### 4.4 Component responsibilities
|
||
|
||
| Component | Owns | Does not own |
|
||
| --- | --- | --- |
|
||
| **qonto-assistant** | Tool catalog, spend/volume policy, Qonto REST client, dual surfaces (MCP+REST), call audit | LLM routing, scheduling, identity issuance |
|
||
| **OpenBao + ops-warden** | Custody path, rotation guide, high-risk agent boundary | Business rules about transfers |
|
||
| **flex-auth** | “May actor X call capability `finance.qonto.read`?” | Bank credential material |
|
||
| **agent-harness** | Session lifecycle; grants profile `finance-qonto-read`; injects *assistant* URL/token only | Bank policy implementation |
|
||
| **llm-connect** | Which model answers | Tool execution |
|
||
| **activity-core** | When Finance Steward runs | How bank is called |
|
||
| **binky-control** | CostRunRate, queues, AutonomyPolicy, dogfood evidence | Runtime of the assistant |
|
||
| **Qonto** | Bank of record | Our agent policy |
|
||
|
||
### 4.5 Protocol adapters over one service core
|
||
|
||
The critical implementation rule is:
|
||
|
||
> REST routes and MCP tools are only transport adapters. They must translate
|
||
> into the same internal `CapabilityRequest` model before policy runs.
|
||
|
||
That avoids the most likely drift bug in this repo: REST enforcing one set of
|
||
rules while MCP grows a slightly different set later.
|
||
|
||
Canonical request shape:
|
||
|
||
```text
|
||
CapabilityRequest
|
||
capability_id
|
||
tenant_id
|
||
actor_claims
|
||
resource_scope
|
||
request_args
|
||
protocol # rest | mcp
|
||
```
|
||
|
||
Recommended v1 capability map:
|
||
|
||
| Internal capability id | REST surface | MCP surface | Notes |
|
||
| --- | --- | --- | --- |
|
||
| `org_summary` | `GET /v1/accounts` | `qonto_org_summary` | Core balances/org read |
|
||
| `list_transactions` | `GET /v1/transactions` | `qonto_list_transactions` | Capped, filtered history |
|
||
| `cost_run_rate_hints` | used by `GET /v1/snapshot` | `qonto_cost_run_rate_hints` (Phase 2) | Normalized finance hints, not raw export |
|
||
| `snapshot_bundle` | `GET /v1/snapshot` | optional later | Composite read; not a separate privilege if it only orchestrates allowed reads |
|
||
| `find_counterparties` | defer | defer | Candidate future read capability, not Phase 1 MVP |
|
||
|
||
Rules:
|
||
|
||
- `snapshot_bundle` is an orchestrator over already-allowed reads, not a way
|
||
to bypass policy with a "bigger" internal export.
|
||
- REST and MCP names may differ, but both must map to the same
|
||
`capability_id`, deny reasons, and response redaction rules.
|
||
- Vendor payloads should be normalized into repo-owned response schemas; do
|
||
not stream raw Qonto JSON into agent contexts by default.
|
||
|
||
### 4.6 Policy model (v1)
|
||
|
||
Encode as **code + declarative YAML**, tested in CI — not wiki prose alone.
|
||
Policy should evaluate this tuple:
|
||
|
||
```text
|
||
(capability_id, actor_claims, tenant_id, resource_scope, request_args, response_class)
|
||
```
|
||
|
||
Sketch:
|
||
|
||
```yaml
|
||
# policy/qonto-v1.yaml (sketch)
|
||
version: 1
|
||
default: deny
|
||
capabilities:
|
||
org_summary:
|
||
lanes: [green, blue]
|
||
list_transactions:
|
||
lanes: [green, blue]
|
||
constraints:
|
||
max_per_page: 100
|
||
max_pages_per_call: 5
|
||
max_window_days: 93
|
||
cost_run_rate_hints:
|
||
lanes: [green, blue]
|
||
snapshot_bundle:
|
||
compose_only: [org_summary, cost_run_rate_hints]
|
||
deny_classes:
|
||
spend:
|
||
match_prefixes: [create_, issue_, transfer_, payout_]
|
||
volume_cost:
|
||
match_tags: [card_operation, invoice_send, payment_link, subscription_change]
|
||
credential_exfil:
|
||
response_fields: [api_key, authorization_header, full_iban]
|
||
lanes:
|
||
green_blue:
|
||
allow_set: [org_summary, list_transactions, cost_run_rate_hints, snapshot_bundle]
|
||
yellow_plus:
|
||
same_as: green_blue
|
||
red:
|
||
human_only_in_qonto_app: true
|
||
```
|
||
|
||
Semantic rules beyond tool name:
|
||
|
||
- Reject unknown capability ids before touching Qonto.
|
||
- Enforce tenant binding before resource lookup; callers do not choose an
|
||
arbitrary OpenBao path or tenant id.
|
||
- Inspect arguments for suspicious write semantics even if a route/tool is
|
||
mis-wired later.
|
||
- Treat response shaping as policy, not presentation polish. Redaction rules
|
||
should be versioned and testable like allow/deny rules.
|
||
- Emit stable deny reasons such as `unknown_capability`, `tenant_scope`,
|
||
`arg_constraint`, `volume_cost`, `credential_exfil`, and `authz_denied`.
|
||
|
||
Map to AutonomyPolicy:
|
||
|
||
| Action class | Lane |
|
||
| --- | --- |
|
||
| Balance / txn read, CostRunRate update drafts | Green / Blue |
|
||
| Recommend plan downgrade / keep Qonto | Green (recommend only) |
|
||
| Change Qonto plan, create API key, transfer | Red |
|
||
|
||
### 4.7 Authentication and identity
|
||
|
||
Separate three identities clearly:
|
||
|
||
1. **Caller identity**: human or workload calling REST/MCP.
|
||
2. **Assistant service identity**: the only identity allowed to read
|
||
`tenants/<tenant>/qonto-api`.
|
||
3. **Qonto upstream identity**: the company credential presented to Qonto.
|
||
|
||
```text
|
||
Caller
|
||
-> authenticates to qonto-assistant (OIDC workload identity, mTLS, or similar)
|
||
-> assistant checks claims + optional flex-auth grant (finance.qonto.read)
|
||
-> assistant resolves tenant from claims/policy
|
||
-> assistant fetches secret using its own runtime role
|
||
-> Qonto sees only the tenant's API identity
|
||
```
|
||
|
||
Rules:
|
||
|
||
- **Never** put bank credentials in harness env for general sessions.
|
||
- Do **not** reuse an `agent-harness-*` OpenBao role for assistant secret
|
||
fetch. Use a dedicated assistant runtime identity such as
|
||
`qonto-assistant-runtime`.
|
||
- Even in single-tenant dogfood, keep `tenant_id` explicit in claims and audit
|
||
events so productization does not require reworking the core contract.
|
||
|
||
### 4.8 Secret and upstream session lifecycle
|
||
|
||
The blueprint needs an explicit secret lifecycle because this service is the
|
||
sole credential choke point.
|
||
|
||
- OpenBao read happens inside the service only.
|
||
- Secrets may be cached **in memory only** with a short TTL to avoid fetching
|
||
on every request.
|
||
- Cache invalidation should occur on startup, TTL expiry, and upstream 401/403.
|
||
- No secret material is written to disk, progress logs, traces, or chat output.
|
||
- Local developer mode may use env inject for mocks/tests, but production path
|
||
remains OpenBao-first.
|
||
|
||
This keeps rotation and incident response tractable without coupling every
|
||
request to a secret store round-trip.
|
||
|
||
### 4.9 Data handling and response shaping
|
||
|
||
The current blueprint mentions redaction, but this needs to be a first-class
|
||
contract because the main exfil path is not only "the key" but over-sharing
|
||
financial data into agent chat contexts.
|
||
|
||
| Data class | Examples | Default handling |
|
||
| --- | --- | --- |
|
||
| `secret` | API key, Authorization header, OpenBao token | Never return, never log |
|
||
| `sensitive_financial` | full IBAN, account owner identifiers, raw vendor refs | Redact or truncate by default |
|
||
| `operational_summary` | balances, normalized counterparties, capped txn summaries | Return when capability allows |
|
||
|
||
Guidelines:
|
||
|
||
- Prefer normalized summary DTOs over raw vendor objects.
|
||
- Default to IBAN last4 or account alias, not full identifiers.
|
||
- Bulk export, statement download, and raw vendor dumps are out of scope for v1.
|
||
- If a future higher-assurance mode needs fuller data, it should be a separate
|
||
capability with separate policy and audit semantics, not a flag hidden inside
|
||
an existing read.
|
||
|
||
### 4.10 Observability and audit
|
||
|
||
Per-request audit should not depend on State Hub progress writes. State Hub is
|
||
appropriate for work/progress records, not as the hot-path sink for every bank
|
||
read.
|
||
|
||
Recommended split:
|
||
|
||
- **Hot path**: structured stdout/file/OTLP audit events for every request.
|
||
- **Cold path**: State Hub progress notes or operator summaries for notable
|
||
sessions, incidents, or rollout evidence.
|
||
|
||
Suggested audit envelope:
|
||
|
||
```yaml
|
||
request_id: req-...
|
||
timestamp: ...
|
||
actor: agt-... / workload id
|
||
tenant_id: binky
|
||
capability: list_transactions
|
||
protocol: rest | mcp
|
||
decision: allow | deny
|
||
deny_reason: volume_cost | unknown_capability | tenant_scope | authz_denied
|
||
policy_version: 1
|
||
latency_ms: ...
|
||
qonto_http_status: ...
|
||
result_count: ...
|
||
# never: Authorization header, API_KEY, OpenBao token, full IBAN by default
|
||
```
|
||
|
||
### 4.11 Operational guardrails (v1, not "later if we remember")
|
||
|
||
Basic safety and reliability controls belong in Phase 1 because one noisy
|
||
agent session can otherwise degrade the single bank integration for everyone.
|
||
|
||
| Guardrail | Minimum requirement |
|
||
| --- | --- |
|
||
| Upstream timeout | bounded per request; fail fast and explicitly |
|
||
| Pagination | hard caps in policy, not caller preference |
|
||
| Concurrency | bounded per actor/tenant to avoid Qonto burst abuse |
|
||
| Rate limiting | basic per-actor or per-token limit from first deploy |
|
||
| Retry policy | conservative; no blind retries on ambiguous write-like failures |
|
||
| Persistence | no raw Qonto payload store in v1; ephemeral in-memory only |
|
||
| Failure mode | fail closed on policy/authn/authz uncertainty |
|
||
|
||
These controls move from "nice operational follow-up" to "architecture
|
||
requirement" because the repo's entire point is governed centralization.
|
||
|
||
### 4.12 Placement in the repo map (options)
|
||
|
||
| Option | Repo | When |
|
||
| --- | --- | --- |
|
||
| **A (preferred dogfood)** | New small service under Coulomb, e.g. `qonto-assistant` or generalize later to `finance-connect` | Clean boundary, productizable |
|
||
| **B** | Module inside a future `domain-connectors` monorepo | If many thin connectors share skeleton |
|
||
| **C** | Package under `binky-control` only | Too tenant-specific; resists reuse |
|
||
|
||
Start as **tenant-backed dogfood** with a clear INTENT that it may graduate
|
||
to a multi-tenant “governed bank connector” offer. Keep binky-control as
|
||
*consumer + policy source of business truth* (CostRunRate, AutonomyPolicy),
|
||
not as the runtime host long-term.
|
||
|
||
---
|
||
|
||
## 5. Alternatives considered
|
||
|
||
### 5.1 “Anchor policy at Qonto”
|
||
|
||
**Unavailable for our custody model.** Self-hosted MCP + API keys are not
|
||
fine-scoped. Hosted OAuth MCP is the corporate **anti-pattern** we already
|
||
rejected (credentials outside OpenBao, write tools bundled). Even with
|
||
OAuth, spend prevention would still need our gateway for defense in depth.
|
||
|
||
### 5.2 Generic MCP gateway only
|
||
|
||
**Good complement, insufficient alone.** Use later for fleet aggregation.
|
||
v1 needs **semantic** finance policy and a stable REST face for non-MCP
|
||
clients — easier as our service.
|
||
|
||
### 5.3 agent-harness-only enforcement
|
||
|
||
Harness tool profiles remain necessary (don’t hand agents unrestricted
|
||
network). But **bank policy must not live only there**: interactive Claude
|
||
on a laptop would bypass harness profiles unless it also only sees the
|
||
assistant endpoint. **Network/policy gravity toward the assistant.**
|
||
|
||
### 5.4 llm-connect as tool hub
|
||
|
||
Reject for layering reasons (LLM vs domain tools). Route models through
|
||
llm-connect; route **bank actions** through qonto-assistant.
|
||
|
||
---
|
||
|
||
## 6. Phased delivery
|
||
|
||
### Phase 0 — Already done (BINKY-WP-0005)
|
||
|
||
- [x] Custody lane live
|
||
- [x] REST pull proven
|
||
- [x] CostRunRate TBC resolved for desk + plan
|
||
- [x] Catalog + CCR active
|
||
|
||
### Phase 1 — Policy kernel + REST (minimum useful product)
|
||
|
||
1. Service skeleton with a protocol-neutral capability core and shared
|
||
`decide(request, claims) -> Allow|Deny`.
|
||
2. REST adapters for `GET /v1/accounts`, `GET /v1/transactions`,
|
||
`GET /v1/snapshot`.
|
||
3. Hard deny list for any write/spend path (even if not implemented).
|
||
4. OpenBao fetch only inside service using a dedicated assistant runtime role.
|
||
5. Baseline guardrails ship in Phase 1: bounded pagination, timeouts, basic
|
||
rate limiting, bounded concurrency, and redaction tests.
|
||
6. Smoke: CI uses mock Qonto; manual live read stays explicitly opt-in.
|
||
7. Script replaces ad-hoc first-pull for CostRunRate refresh.
|
||
|
||
### Phase 2 — MCP surface for all harnesses
|
||
|
||
1. Streamable-HTTP MCP adapter on the same capability core and policy engine.
|
||
2. Document **one** client config snippet for Claude/Codex/Cursor/Grok:
|
||
URL + OIDC/workload auth — **no bank secrets**.
|
||
3. agent-harness tool profile `finance-qonto-read` → assistant only.
|
||
4. MCP and REST emit the same structured audit schema; State Hub receives
|
||
operator-level progress notes, not per-call hot-path events.
|
||
|
||
### Phase 3 — Flex-auth + fleet
|
||
|
||
1. flex-auth resource `finance.qonto.read` (+ later `.export`).
|
||
2. Graduated quotas, differentiated response redaction profiles, and finer
|
||
capability scopes such as `.export` if ever approved.
|
||
3. Optional Envoy/gateway in front for multi-assistant mesh.
|
||
|
||
### Phase 4 — Productization (dogfood → offer)
|
||
|
||
1. Multi-tenant path layout `tenants/<slug>/qonto-api`.
|
||
2. Policy packs per autonomy lane.
|
||
3. OfferCatalog entry: “Governed bank assistant for agent fleets.”
|
||
|
||
---
|
||
|
||
## 7. Harness integration pattern (the consistency trick)
|
||
|
||
**Wrong:**
|
||
|
||
```text
|
||
each harness → OpenBao qonto key → vendor MCP (local allow-list hope)
|
||
```
|
||
|
||
**Right:**
|
||
|
||
```text
|
||
each harness → auth to qonto-assistant → policy → Qonto
|
||
↑
|
||
single tool catalog + single deny rules
|
||
```
|
||
|
||
Checklist for any new harness:
|
||
|
||
1. Does **not** get `binky-qonto-api` bank secret.
|
||
2. Gets only assistant base URL + client credentials for `finance.qonto.read`.
|
||
3. Tool profile names assistant tools, not vendor tool names.
|
||
4. Offline/dev: point at assistant mock, not a copy of production key.
|
||
|
||
---
|
||
|
||
## 8. Risk register (architecture-level)
|
||
|
||
| Risk | Mitigation |
|
||
| --- | --- |
|
||
| Assistant is a high-value target | High-risk path already in agent-high-risk-boundary; no raw stream to coding agents; short-lived OpenBao tokens; network policy |
|
||
| Model jailbreak asks for transfer | Tool not registered + deny classes; never implement spend in v1 |
|
||
| Vendor adds new MCP write tools | We do not auto-mirror vendor catalog; allow-list is ours |
|
||
| Bypass via direct thirdparty from laptop | Platform policy + founder discipline; optional egress controls later; secrets not on laptops |
|
||
| Over-filtering legitimate finance work | Explicit allow tools + CostRunRate helpers; expand by policy PR with tests |
|
||
| Cross-tenant data mix-up later | Tenant id explicit in claims, policy, audit, and OpenBao path resolution from v1 |
|
||
| Audit/log sink leaks sensitive fields | Redaction-by-class policy + tests; State Hub not used as raw per-request event store |
|
||
|
||
---
|
||
|
||
## 9. Decision requests (for later DEC / workplan)
|
||
|
||
1. **Adopt domain-assistant architecture (B)** for Qonto (this blueprint).
|
||
2. **Repo home:** new `qonto-assistant` vs wait for generic `finance-connect`.
|
||
3. **v1 policy freeze:** no spend / no volume-cost tools — hard deny.
|
||
4. **Service identity:** dedicated assistant runtime role; no harness role reuse.
|
||
5. **MCP transport:** remote streamable-HTTP only for production clients.
|
||
6. **Whether** to ever run vendor MCP as internal backend (default: **no**).
|
||
|
||
Suggested workplan slug: `BINKY-WP-0006` or a Coulomb-side
|
||
`QONTO-WP-0001` once the repo exists.
|
||
|
||
---
|
||
|
||
## 10. References
|
||
|
||
### Internal
|
||
|
||
- `integrations/qonto-mcp.md` — variant decision, custody, first-pull results
|
||
- `finance/CostRunRate.md` — consumer of read path
|
||
- `AutonomyPolicy.md` — Green…Red lanes; spending defaults
|
||
- `agent-harness` INTENT + ADR-001 — single runtime; instances declarative
|
||
- `llm-connect` ARCHITECTURE-LAYERS — LLM only, not domain tools
|
||
- `flex-auth` INTENT — authorization registry
|
||
- ops-warden catalog `binky-qonto-api`, CCR-2026-0008
|
||
|
||
### External patterns (research snapshot 2026-07)
|
||
|
||
- Envoy AI Gateway MCPRoute: tool filtering, upstream key injection, OAuth,
|
||
CEL on MCP params
|
||
- Kong AI Gateway MCP Tool ACLs: default-deny per-consumer tool grants
|
||
- MCP gateway product literature (Portkey, Speakeasy, Permit): central
|
||
policy + audit for multi-client agents
|
||
- MCP security guidance: no token passthrough; tool-level scopes; proxy
|
||
consent / confused-deputy awareness
|
||
- CSA / industry agentic MCP guides: inventory of servers; JIT escalation
|
||
for high privilege
|
||
|
||
---
|
||
|
||
## 11. Summary recommendation
|
||
|
||
1. **Do not** wire Qonto MCP into each harness.
|
||
2. **Do** build a **Qonto Governed Assistant** with:
|
||
- sole possession of bank credentials,
|
||
- a protocol-neutral capability core behind REST and MCP adapters,
|
||
- default-deny tool catalog,
|
||
- **no spend / no volume-cost** policy as code,
|
||
- data classification/redaction rules,
|
||
- MCP + REST for all clients,
|
||
- flex-auth + OpenBao + audit.
|
||
3. Treat generic MCP gateways as a **later mesh layer**, not a substitute
|
||
for domain policy.
|
||
4. Align agent-harness so it only ever grants access to that assistant —
|
||
preserving ADR-001 without re-implementing bank rules per runtime.
|
||
|
||
This matches both industry MCP-gateway practice and your existing
|
||
platform boundaries (custody, flex-auth, harness instances, dogfood).
|