# ADR-0090: MCP transport authorization boundaries

- **Status:** Accepted
- **Date:** 2026-09-16
- **Decision owners:** Bellona and Eve platform maintainers
- **Protocol target:** MCP 2026-07-28

## Context

The repository has one admitted MCP server, `oshun-workbench`, and it runs over
stdio. It also contains HTTP-shaped MCP libraries and custom hosts, but Task
16.2 found that none is a registered, current-version Streamable HTTP server.
Those source surfaces must not become remotely reachable merely because an HTTP
listener exists.

MCP defines different credential boundaries by transport. A protected HTTP MCP
server is an OAuth resource server. It publishes RFC 9728 protected-resource
metadata, accepts access tokens only in the `Authorization` header, validates
that each token was issued for its canonical resource, and returns OAuth
challenges for missing, invalid, or insufficient authorization. A stdio server
should not run that browser and redirect flow: its client launches a local
process and supplies narrowly scoped credentials through the process
environment.

## Decision

Any Bellona MCP endpoint made reachable over HTTP must be mounted behind
`authorizeBellonaMcpProtectedHttpRequest` and publish metadata through
`handleBellonaMcpProtectedResourceMetadata`. The endpoint remains unavailable
until an authorization server, token verifier, and revocation/rotation store are
configured. The gate enforces all of the following on every request:

- path-aware RFC 9728 protected-resource metadata and an explicit authorization
  server issuer;
- OAuth authorization-code planning with PKCE S256, state binding, issuer
  validation, and the RFC 8707 `resource` parameter on authorization and token
  requests;
- least-privilege initial scopes, one complete insufficient-scope challenge,
  bounded step-up retries, and explicit scope-hierarchy rules;
- header-only bearer tokens, mandatory expiry and not-before checks, exact
  issuer/resource audience validation, revocation, and minimum rotation
  generations;
- exact tenant and task binding for every request;
- a handler principal that contains a one-way token fingerprint but not the raw
  bearer token; and
- a downstream boundary that rejects authorization, proxy authorization,
  cookies, API keys, and credential-shaped query parameters.

The MCP resource server does not issue tokens and does not accept arbitrary
upstream tokens. A dedicated identity provider owns user interaction, client
registration, token issuance, refresh, and revocation propagation. The resource
server only verifies those tokens and enforces local authorization.

For stdio, the admitted process configuration remains `.mcp.json` plus the MCP
trust inventory. Credentials may enter only through declared environment
variables, never command arguments or URLs. The stdio process inherits the
launching user's local authority, so origin/audience discovery and redirect
OAuth are inapplicable; tool risk, destination, disclosure, and consent policy
still apply. Promoting a stdio integration to HTTP requires a new registry entry
and the protected HTTP gate—it is not a transport toggle.

## Consequences

The gate and OAuth planner are independently testable before a production HTTP
listener is admitted. This does not make the legacy Unity or Psyche custom HTTP
hosts conformant, and it does not register a Bellona HTTP MCP endpoint. Their
Task 16.2 quarantine remains in force until they adopt the current wire
protocol, mount this authorization boundary (or an equivalent reviewed one), and
pass independent conformance tests.

Token verification remains an injected boundary because JWT validation,
introspection, key rotation, and revocation delivery belong to the chosen
identity provider. A deployment must use a fail-closed verifier and durable,
shared token-status storage; the in-memory store is a deterministic test and
single-process reference implementation only.

## Verification

`protected-http-auth.test.ts` covers metadata discovery, OAuth planning,
resource indicators, PKCE/state/issuer checks, least scopes and step-up,
audience and expiry failures, rotation and revocation, tenant/task isolation,
query-token rejection, and credential-passthrough denial. The Bellona package
typecheck, lint, build, and focused MCP suites are release gates for changes to
this boundary.
