ADR-10: MCP server

Context

TINO’s vision is to make document production a first-class participant in AI workflows. The Model Context Protocol (MCP) has emerged as the standard way to expose an application’s capabilities as tools an AI model can call.

Two questions had to be answered:

  1. Location: Where should the MCP server live?

  2. Authentication: How do AI clients authenticate?

For authentication specifically, the MCP specification builds on OAuth 2.0, and clients such as Claude do not support arbitrary static-token schemes. They discover an authorization server and obtain a client_id through one of two mechanisms:

  • Client ID Metadata Document (CIMD): the client_id is an HTTPS URL the authorization server dereferences, so no client registration is needed.

  • Dynamic Client Registration (DCR): the client registers itself with the authorization server at runtime and receives a client_id in return.

Decision

TINO embeds the MCP server in the main FastAPI process as a Streamable HTTP sub-application mounted at /mcp, reusing the existing services and access model.

For authentication, TINO acts as an OAuth 2.0 Resource Server and delegates authentication entirely to the same OpenID Connect provider used for the web UI (ADR-07: OIDC for authentication). Authorization and the per-bucket access model (ADR-04: The bucket model) remains TINO’s.

Clients discover the provider from TINO’s protected-resource metadata, identify themselves with a CIMD, and complete an authorization-code flow with PKCE. TINO validates the resulting access token against the provider’s signing keys and runs each tool as the authenticated user, so group-based bucket access applies unchanged.

The protocol itself is handled by the official MCP Python SDK rather than a hand-rolled implementation.

Note

Several alternatives were considered and explicitly rejected during the evaluation, such as:

TINO as its own AS (OAuth proxy)
Many applications today embed a small authorization server that accepts CIMD from MCP clients and proxies authentication to the real identity provider. This is a pragmatic workaround because most providers do not support CIMD natively yet. However, it inverts TINO’s role, pulls token issuance into the application, and duplicates what the provider already does. Keeping TINO a pure resource server is simpler and keeps a single source of identity.
DCR instead of CIMD
Dynamic Client Registration is supported by more providers today, but every MCP session creates a new client record in the provider’s database. Over time this pollutes the client registry with a large number of abandoned entries that are never cleaned up. CIMD avoids this entirely — the client identifies itself via a URL, no state is written, and nothing accumulates.
Static API keys
MCP clients such as Claude do not support static token authentication, as they expect a standard OAuth flow. Static API keys therefore serve a different purpose (automating the REST API from CI) and are not accepted by the MCP server.

Consequences

Positive

  • One access model
    MCP tools reuse the same services and group-based authorization as the REST API.
    There is no second permission system to keep in sync.
  • True user context
    Authentication tokens are issued to real users.
    An agent inherits exactly the bucket roles of the person who authorised it.
  • Standard protocol
    Any MCP-capable client (e.g. Claude) connects out of the box. TINO does not dictate the client.
  • No new secrets
    Authentication and token issuance are the identity provider’s job.
    TINO stores no MCP credentials and issues no tokens of its own.
  • Single process
    The server is mounted into the existing FastAPI app (see ADR-05).
    No extra deployment or runtime to operate.

Negative

  • Provider must support CIMD
    The identity provider has to act as a CIMD-capable MCP authorization server.
    CIMD is an emerging and still-experimental OAuth 2.0 / MCP capability.
  • Maturing ecosystem
    Both the CIMD draft and MCP OAuth client support are still stabilising.
    Interoperability can vary between versions and vendors.
  • Discovery metadata proxy
    Not every provider advertises the required "none" in token_endpoint_auth_methods_supported. Keycloak, for example, omits it (see tino#23 & keycloak#49730).
    Where it is missing, TINO must work around it by proxying the authorization server metadata and injecting the value, which adds (authentication) complexity TINO shouldn’t have.
    The proxy can be removed once the providers advertise "none" natively (tracked in tino#23).
  • No audience binding
    A provider may issue CIMD-flow tokens without an aud claim — the ephemeral client has no registered entry to carry an audience mapper. Keycloak, for example, omits it (see tino#24).
    Without aud, TINO cannot bind a token to the /mcp resource: a valid token issued to another client in the same realm is accepted, constrained only by TINO’s per-bucket authorization.
    Strict audience validation must first be enabled on the provider — via RFC 8707 resource indicators that stamp the requested resource into aud — before TINO can enforce it (for Keycloak, tracked in tino#24, resp. keycloak#41526).

Note

The negative points above are largely a matter of timing. CIMD, DCR, and MCP OAuth support are all under active development with strong industry momentum. As these drafts mature into stable standards and identity providers adopt them, the interoperability concerns will diminish on their own.