Satchel MCP docs

Authentication & security

OAuth 2.1, 2FA login, the encrypted store, the closed pilot model, writes-off-by-default, the kill switch, and the audit stream.

Satchel MCP's hosted remote server is deliberately more conservative than "connect and go" — real account access is gated by several independent controls, described below. None of these apply to the local sandbox, which needs no real credentials at all.

OAuth 2.1 authorization server

The hosted remote server is a full OAuth 2.1 authorization server: authorize, token, dynamic client registration, and revoke endpoints, with:

  • PKCE (Proof Key for Code Exchange) on every authorization code flow — the standard mitigation against authorization-code interception, and mandatory under OAuth 2.1;
  • protected-resource and authorization-server metadata, published per RFC 9728 and RFC 8414, so compliant MCP clients can discover the flow automatically instead of needing hardcoded configuration;
  • dynamic client registration, so a new MCP client can register itself before the first authorization request, rather than requiring a manually-provisioned client id.

The remote MCP endpoint itself is an OAuth resource server: every request must carry a valid bearer token, checked per call.

Client Office authentication and conditional 2FA

Authorizing a connection uses Satchel's own Client Office login. When Client Office presents a two-factor challenge, the user completes the same verification step a customer would go through logging into the Satchel app directly, not a Satchel-MCP-specific shortcut.

The credential is exchanged, never retained

Consenting to a connection exchanges a submitted Client Office credential for a short-lived session, once. The plain credential is not persisted anywhere — not in the OAuth store, not in logs, not in chat history on the client side. When a live session has expired, the user must reauthorize; the product does not attempt to silently extend access using a stored password.

Encrypted OAuth store

The persistent OAuth store (registered clients, grants, token hashes) is encrypted with AES-256-GCM at rest, with restrictive file permissions. A 32-byte key is required to run in live mode — there is deliberately no way to start a live deployment with encryption silently disabled.

The closed pilot model

Live access is not self-serve. Two independent gates apply together:

  • who may authorize — an explicit allowlist of permitted subjects (SATCHEL_LIVE_ALLOWLIST) gates the closed cohort;
  • where results may land and under which legal classificationSATCHEL_LIVE_CLIENT_POLICIES binds an exact origin (or an explicitly approved loopback local policy) to a verified entity, client mode, external notices and evidence references. Hosted external clients require written recipient/AISP/transfer dispositions. A Satchel-selected or managed provider additionally requires contract ownership and DORA-register coverage. The consent screen uses this verified identity, never the name supplied by dynamic client registration.

Both must be satisfied; neither alone is sufficient. This is what keeps live access a closed, invite-only pilot rather than an open registration flow.

A customer-selected local/customer-hosted AI is not automatically Satchel's ICT vendor. That boundary does not waive the DPIA, minimisation, SCA, audit, notice or corporate- authority controls on Satchel's own interface. Synthetic UAT without confirmed provider evidence is restricted to isolated local/in-process mocks; external AI, FintechLab, Telegram and external monitoring must remain unused.

Writes are off by default — everywhere

satchel_move_money refuses to run in every mode, sandbox and live alike, unless SATCHEL_ALLOW_WRITES=true is explicitly set in the server's own environment. On the hosted remote server this question doesn't even arise: the tool is never registered there at all, so SATCHEL_ALLOW_WRITES has no effect on that surface. See satchel_move_money for the full detail.

The read-only boundary on the hosted pilot

The hosted remote server exposes exactly the 6 read tools and nothing else — there is no configuration flag that adds write access back to that surface. Read access itself is scoped per authenticated subject: an authorization grant only lets its holder read their own accounts.

Kill switch

Live access can be disabled by an authorized operator without a deploy or restart. The runtime refreshes kill state on guarded requests with a cache window of up to one second, and the system fails closed: while the kill switch is engaged, every request to the live remote MCP endpoint, and consent/token issuance, is refused. Revocation of an existing connection stays reachable even while the switch is engaged, so a user can disconnect during an incident regardless.

Rate limiting

The hosted remote server enforces two request budgets per minute, refused with HTTP 429 and a Retry-After header once spent: one per subject (default 60/min — an interactive assistant makes a handful of calls per user turn, so sustained one-request-per-second is a loop, not a person) and one per OAuth client across all its subjects (default 180/min). The check runs before any upstream call to the Client Office API, so a runaway or compromised assistant burns local 429s rather than relaying its loop to a real account. The refusal is deliberately not a 401: a 401 would push a well-behaved MCP client back through the consent flow. Each refusal is submitted to the audit stream; it remains refused if the audit sink is unavailable. Operators are alerted once per window rather than once per refused request.

Retention and erasure

OAuth records live only as long as the tokens they belong to can still be accepted: expired and revoked records are swept automatically on every token issuance and readiness check. On request, all authorization records for one subject — including the bound Client Office session — can be erased in one operator action (the GDPR art. 17 path). The pseudonymized audit stream is not automatically rewritten. Its subject reference remains personal data and follows the approved retention, rights and legal-exception assessment. What a tool already returned to your assistant is retained by your AI provider under their policy — deleting data on the Satchel side does not remove it from your assistant's conversation history.

Audit stream

Successful live connections and allowed read-tool outcomes are returned only after their persisted audit events succeed. Resource-access denials are submitted to the same append-only sink on a best-effort basis and remain denied if the sink is unavailable. OAuth form-validation failures outside the resource server are not claimed as complete audit evidence.

What's recorded, per event: a schema version, a unique event id, a UTC timestamp, the action (for example, a connection being authorized, a connection being denied, a read tool call completing, a token being revoked, or the kill switch engaging), and its outcome (allowed / denied / revoked / blocked). A denial additionally carries a reason drawn from a closed, fixed vocabulary (for example: no bearer token presented; the token was invalid, expired, or revoked; the subject isn't on the live allowlist; or the underlying tool call itself failed upstream) — closed specifically so that "why was this denied" questions can be answered by querying a fixed set of values, never by parsing free text.

What's pseudonymized: the record ties activity to a subject via an HMAC of their identity, stable per subject for a given deployment key, so repeat activity can be correlated without storing who it was.

What's never present, by construction: email addresses, account numbers, IBANs, transaction narratives, session tokens, credentials, or access/refresh tokens. This is enforced by an automated test, not just a convention.

Retention is enforced by log rotation outside the application, over a documented retention window, on an append-only file — a deliberately simple, migration-free design: any future downstream consumer of the audit stream reads the stream and keys off its schema version, rather than sharing a live database schema with this service.

On this page