ADR-024: Authenticate tenant-scoped remote MCP through WorkOS

1. Context

The platform needs authenticated remote agent access independently of an open application page. Internal inventory work and demonstrations of talking to organizational data motivate that access. A trusted connection and enforceable tenant/user permissions must exist before business capabilities are exposed.

Tenant routing and the app-owned authentication boundary provide the isolation model. WorkOS supplies OAuth authorization infrastructure, while DataDescriber owns tenant isolation and application authorization. Tenant-specific resources fit the required authorization boundary without introducing multi-tenant grants.

This decision establishes the remote authentication and channel-authorization boundary. Non-business permission probes can verify that boundary without reading inventory, changing business data or emitting business events. Business-operation architecture and browser-agent interfaces are separate decisions.

2. Decision

Tenant-bound resources and activation

Expose one canonical HTTPS /mcp resource per explicitly enabled tenant host and environment. The service may host many tenant resources. Each credential addresses exactly one tenant resource; another tenant requires authorization for its own resource.

Resolve the host, resource URI, tenant and WorkOS organization from trusted deployment configuration. Verify the external token's concrete audience and organization against that binding. Unknown or ambiguous hosts, environment audiences and mismatched organizations fail closed. Caller-supplied tenant arguments cannot select another tenant.

A tenant-scoped feature flag activates MCP independently of user permissions. WorkOS is the source of truth for this tenant capability. It is disabled by default. Keep the MCP authentication entry point and public OAuth metadata reachable. Verify the bearer and its trusted tenant/organization binding before resolving the tenant's MCP capability and the user's authority from WorkOS. A common server-side guard enforces both for protected protocol requests, protocol lifecycle methods, tool discovery and direct calls. Registering a host or OAuth resource grants no MCP access.

Capability and permission decisions may be reused only within an explicit bounded validity period. Tenant disablement can therefore take effect after a delay; it does not remove the route or promise immediate cancellation of existing access. Every protected request must pass a still-valid decision, and expired or indeterminate access fails closed. Deployment policy records the maximum delay and verifies actual withdrawal behavior before enabling MCP.

Wildcard resource registration is an administrative convenience, not an audience-validation rule. Tokens must still address a concrete configured resource. Custom domains and development resources require deliberate configuration; production credentials cannot authorize development resources.

Transport and protocol boundary

Use MCP Streamable HTTP at the tenant's canonical resource. Follow the version-selection, discovery and lifecycle rules of each supported protocol revision; the server advertises only implemented capabilities. Validate supplied Origin headers against trusted policy. Origin validation and MCP session identifiers do not authenticate a caller or replace request-level authentication, tenant activation or authorization.

Supported protocol versions, streaming configuration and client compatibility evidence belong in the feature design and delivery plan. Additional transports are not implied by exposing this remote resource.

External authentication and the internal boundary

WorkOS Connect is the external OAuth authorization server. The app-owned outer boundary verifies bearer signature, allowed algorithm, configured issuer, audience, lifetime, human identity, organization, client and consent claims. For delegated-human Connect tokens, verified sub, org_id, client_id and sid identify the human, organization, client and consent respectively. Missing or malformed required identity rejects the credential. Preserve the verified consent identity through the internal credential, request context and operational attribution; sid is neither a permission nor evidence of current consent validity or per-action approval. Browser cookies do not authenticate the remote MCP channel, and session IDs do not replace request authentication. OAuth protected-resource metadata and bearer challenges support discovery without redirecting requests into browser login. Missing or invalid credentials produce 401; verified identity lacking authority produces 403. Tenant disablement or missing MCP user permissions is an application-access denial, not an OAuth scope deficit. Return a safe, stable reason and the appropriate administrative recovery guidance. Do not challenge for additional OAuth scopes or initiate repeated consent to repair those denials. Dependency failures remain distinguishable from invalid credentials and denied access.

Resolve the tenant capability, active organization membership and complete effective role permissions server-side, including all assigned roles and organization-specific role definitions. Use the union of their permissions rather than checking one role slug or maintaining a competing local role-to-permission mapping. Connect token shape is not assumed to match browser identity state. Missing human identity or unresolvable authority cannot produce a system principal. The authenticated human is the acting principal; the verified OAuth client is separately attributed. Do not assume a Connect token contains application permissions, groups or tenant capabilities. Resolve only the additional data the channel needs; group lookup is not a prerequisite when effective permissions suffice.

The outer boundary normalizes verified authority into a DD internal credential with an MCP-only audience. The inner execution boundary verifies it and constructs authenticated request context. Browser and MCP internal credentials are not interchangeable. Strip caller-supplied internal identity headers, and prevent public access to internal dispatch that bypasses external verification. These are logical boundaries even in a single process.

The external bearer and internal credential belong to different trust boundaries. The internal credential is produced by the server boundary; it is not an OAuth credential issued to the remote client.

flowchart TB
  O["WorkOS Connect"]
  C["Remote MCP client acting for a human"]
  T["Trusted resource and tenant resolution"]
  B["Outer authentication boundary<br/>Verify bearer and resource / organization binding<br/>Resolve tenant capability and effective user permissions"]
  J["DD internal credential<br/>MCP-only audience, consent identity and bounded expiry"]
  I["Inner boundary verifies internal credential"]
  X["Verified principal, tenant, client and consent context<br/>Continue to MCP access checks"]
  O -->|"OAuth access token"| C
  C -->|"Request with external bearer"| T --> B
  B -. "Membership and role resolution" .-> O
  B --> J --> I --> X

Authentication and request sequence

sequenceDiagram
  autonumber
  actor Person as Human user
  participant Client as Remote MCP client
  participant WorkOS as WorkOS AuthKit / Connect
  participant Outer as Auth Boundary / MCP bearer adapter
  participant Context as Platform internal auth / request context
  participant Tools as MCP protocol and tools

  Note over WorkOS: Configure Connect, resource and client settings
  Note over Outer,Context: Reuse tenant routing and internal credentials<br/>Keep the MCP resource, audience, expiry, client and consent ID
  Note over Client,Outer: Route MCP to the bearer adapter<br/>Do not redirect to browser login

  Person->>Client: Connect to the tenant's MCP service
  Client->>Outer: MCP request without bearer
  Outer-->>Client: 401 challenge with protected-resource metadata location
  Client->>Outer: Discover protected resource
  Outer-->>Client: MCP resource identity and WorkOS authorization-server URL
  Client->>WorkOS: Discover authorization server and identify/register client
  Note over Client,WorkOS: Verify how this client identifies itself<br/>Reuse valid sign-in and consent for later calls
  Client->>WorkOS: Start authorization for the intended resource
  WorkOS->>Person: Browser sign-in and consent for the organization
  Note over Person,WorkOS: Cancellation creates no new authorization
  Person->>WorkOS: Complete required sign-in / connection consent
  WorkOS-->>Client: Authorization code via client redirect
  Client->>WorkOS: Exchange code with PKCE
  WorkOS-->>Client: Access token for this MCP resource
  Person->>Client: Use an MCP tool

  Client->>Outer: Authenticated MCP request with external bearer
  Outer->>Outer: Verify token, consent ID, resource, tenant and organization
  Outer->>WorkOS: Look up MCP flag, active membership and all effective permissions
  WorkOS-->>Outer: MCP flag and user permissions for the verified organization
  Note over Outer,WorkOS: Reuse a previous lookup only while it is valid<br/>OAuth scopes do not replace MCP permissions
  Outer->>Context: Issue server-only MCP credential with access expiry and consent ID
  Context->>Context: Verify credential and keep user, tenant, client and consent ID
  Context->>Tools: Pass on the authenticated MCP request
  Tools->>Tools: Check access expiry, MCP flag and MCP read<br/>hello_write also requires MCP write
  Note over Outer,Tools: Invalid token or resource: 401<br/>Missing permission or disabled MCP: 403 with administrator guidance<br/>Dependency unavailable: 503 with bounded retry guidance<br/>Denied requests never run a tool
  Tools-->>Client: Permitted tool list or fixed tool reply
  Note over Tools: No inventory reads, data changes or business events

  opt External token needs renewal
    Client->>WorkOS: Renew token directly with WorkOS
    alt Automatic renewal succeeds
      WorkOS-->>Client: Renewed external token
    else Interactive recovery required
      Client-->>Person: Authentication required
      Person->>Client: Re-authenticate
      Client->>WorkOS: Restart authorization / hosted browser sign-in
      Note over Person,WorkOS: Reuse existing valid consent<br/>Cancellation restores no access
      WorkOS-->>Client: Code and PKCE exchange provide a new access token
      Person->>Client: Retry the original request, unless client resumes it
    end
    Note over Client,Outer: Verify the new token and look up access again<br/>Platform does not store the client's refresh token
  end
  Note over Person,Tools: Connection consent does not prove approval of each tool call

User permissions and client delegation

Use mcp:read as the user permission for entry, protected protocol requests, discovery and the read-permission probe. The write-permission probe additionally requires mcp:write. These are organization-role permissions, not per-client OAuth capabilities. Write-capable role grants include both; execution checks both independently. There is no third mcp:access permission.

Channel operation Required gates
Protected protocol requests and tool discovery Tenant MCP enabled and user has mcp:read
Read-permission probe Above gates and explicit tool exposure
Write-permission probe Above gates, explicit tool exposure and user also has mcp:write

Each exposed probe has an explicit, trusted server-owned permission requirement. Both probes are non-mutating; the write-permission probe must state that no data changed. A tool's name, HTTP method, client annotations or input cannot choose its access policy. Neither probe depends on semantic commands, queries, projections or graph operations.

Discovery filters eligible tools using the same tenant/read/write policy as invocation. Direct calls independently check the configured exposure and required permissions before reaching a handler. A failed or indeterminate check denies execution, including when the user has browser permissions that would otherwise allow business writes.

flowchart TB
  T["Require valid WorkOS tenant capability decision<br/>MCP must be enabled"]
  A["Verify authentication and current bounded authority"]
  R["Require user mcp:read"]
  E["Require explicit tool exposure"]
  K{"Tool requires write permission?"}
  W["Additionally require user mcp:write"]
  H["Invoke fixed permission probe<br/>No business reads, mutations or events"]
  A --> T --> R --> E --> K
  K -->|"No"| H
  K -->|"Yes"| W --> H

Each authorized client connection can use the user's permitted MCP operations for its tenant. Per-client read-only versus write delegation and a server-side client allowlist are not required. The connection experience and documentation must disclose this broad delegation, including the fact that user permission changes can affect an existing connection. Operationally selecting clients for internal use does not imply a server-enforced client restriction.

Standard Connect scopes can support this policy. OIDC scopes do not themselves grant product permissions; server-side MCP and domain grants supply that authority. Any meaningful additional OAuth scope restrictions still apply where present. Finer client-specific delegation, repository-selection grants and service-account authority need separate decisions rather than being inferred from this user-permission model.

Boundary for future business tools

Business tools are outside this authentication decision. Exposing them requires a separately adopted shared semantic command/query contract and a reviewed domain-authorization and provenance design. MCP channel permissions cannot replace domain, target or effect permissions. This decision does not adopt that business-operation architecture or require it for authentication, protocol handling or non-business permission probes.

Freshness, revocation and provenance

Every authority decision has an absolute deadline no later than the verified external token's expiry. Deployment policy may set a shorter validity bound. Caching is optional. If authority is retained for reuse, it is bound to that exact verified external credential as well as its issuer/environment, resource, tenant, user, organization, client and consent. The snapshot includes the WorkOS tenant-capability decision and its validity bound. A cache miss, eviction or expired decision requires fresh capability and permission resolution. Membership, role and capability caches cannot carry stale access into a new credential's snapshot. Cached decisions contain normalized authority and validity information, not bearer credentials.

The OAuth client obtains renewed external tokens; the MCP server does not retain client refresh tokens. Each new token is fully verified and receives fresh capability and permission resolution, even for the same user and consent. Cache reuse and minting another internal credential cannot reset the supporting decision's deadline. Internal expiry is bounded by both external expiry and permission validity. Expired authority and failed resolution deny access, with no stale-permission outage fallback.

Permission removal, tenant deactivation, consent revocation and credential expiry are distinct controls. An unexpired snapshot may remain usable after tenant disablement, role changes or membership removal until its deadline unless invalidated earlier. Upstream consent revocation may likewise not invalidate a locally verified token before expiry. This bounded delay is an explicit trade-off, not an immediate-revocation guarantee. Deployment policy records the configured lifetimes and accepted maximum exposure, including streams; a short internal credential lifetime alone does not establish that bound. Each protected request checks the tenant capability and user permissions in its bounded authority decision. Ongoing data release stops when its authorization expires, and renewal revalidates access rather than preserving it indefinitely.

Audit request outcomes and denials with verified human, tenant, client, resource, consent and correlation attribution where established, excluding credentials and business payloads. Early failures retain unknown identity as unknown. The attribution means the client acted through MCP under the human's authority. Delegation does not mean the human approved each action. Connection consent and available permissions are not per-action approval evidence.

Permission probes emit no business events and require no command/event-envelope, journal or projection changes. Durable business-mutation attribution belongs to the separately reviewed business-operation design.

3. Rationale

Tenant-specific resources align the selected tenant, OAuth audience and repository boundary while preserving a shared deployment. They solve the demonstrated requirement without a cross-tenant routing and grant system.

Independent tenant activation and user read/write permissions support deliberate rollout and read-only demonstration access while restricting the write-gated capability to authorized staff. Broad client delegation is an explicit trade-off for these audiences; it avoids making unverified custom scope support a prerequisite. Domain permissions remain the ceiling.

Explicit server-owned probe requirements demonstrate different access levels without fabricating business operations or treating non-mutating tool annotations as authorization. The connection boundary can stand independently while future business-operation architecture receives its own review.

4. Alternatives Considered

Alternative Reason not selected
Reuse browser cookies or browser-compatible internal credentials Couples external agent access to browser sessions and permits credential reuse across channels.
Central endpoint with multi-tenant/repository grants Introduces unneeded grant storage, routing, consent and revocation machinery.
Central endpoint with one tenant per token, or tenant-specific paths Viable without broader grants, but needs a different routing contract without an established benefit here.
Only a tenant flag or only user permissions Cannot independently control tenant availability and individual access.
A third user mcp:access gate Read already provides the entry gate; write is an additional capability gate.
Per-client capability scopes or a custom grant service as prerequisites Finer delegation is not required for the selected audiences, and generic client scope support is unverified.
Client allowlist Operational choice of initial clients does not establish a need for a server-side restriction.

5. Consequences

Deployment manages resource identity, tenant mapping, WorkOS tenant-capability configuration and code-owned permission provisioning together. MCP remains disabled without valid configuration. Activation preflight rejects MCP access unless mcp:read is defined in the canonical permission registry and provisioned in the target environment; a write-gated probe additionally requires defined and provisioned mcp:write. User grants are deliberate rather than automatically added to default roles. Permission definitions have one canonical, navigation-independent source consumed by channel policy, navigation and provisioning. Channel permissions do not require navigation entries. Moving existing definitions preserves their slugs, grants and route behavior; defining a permission does not assign it to anyone.

A read-only MCP user cannot invoke a write-gated probe even with broader browser permissions. A user with both MCP permissions can invoke both probes; this proves neither a business mutation nor domain authorization. The policy does not offer per-client restriction within that user's grant. Operational guidance explains this delegation, the harmless probe results and the actual permission/consent revocation bounds.

Client interoperability, discovery, renewal, denied access and cross-tenant rejection need real-client and behavioral evidence. Vendor documentation establishes feasibility, not a tested environment. Implementation details, tool names and delivery milestones remain in the feature design and plan.

6. References