MCP implementation design

Decisions this implementation follows

The design follows ADR-024, which is still proposed:

  • Authentication runs in Auth Boundary. The inner application receives verified internal identity.
  • Tool access requires the tenant flag and the user's MCP permissions. Discovery and direct calls use the same policy.
  • Access lifetime limits how long earlier WorkOS checks can be reused. Expired access cannot be extended during an outage.

The responsibilities and observable behavior below are requirements. Proposed class names and method signatures illustrate one implementation; they do not require a particular file or class layout.

MCP library options

The library and protocol revision are open decisions. The reference server uses Chimp to test client responses; it does not select the production library. The comparison below records the options checked on 22 October 2026.

Platform currently uses Scala 3.8.2, Tapir 1.13.31, Ox 1.0.4 and Netty. Compatibility with that combination, including the compiler and transitive dependencies, needs checking for each candidate.

Alternative MCP 2026-07-28 status Fit for Platform
Chimp 0.6.0 The published release supports 2025-06-18 and 2025-11-25, with November as its fallback; it does not implement 2026-07-28 or server/discover. Modern discovery and result handling are on the development branch, with further validation and HTTP work in the draft server PR. Closest match to the existing Tapir/Ox/Netty stack and already used by the response fixture. No additional effect runtime is needed for that integration. Modern support is still unreleased; check the eventual release and conformance results before relying on it. Version definitions.
Tachyon 1.0.1 Released; supports both 2025-11-25 and 2026-07-28. The project reports passing the official conformance suites for both. Java 21+, Netty and blocking handlers on virtual threads. No reactive framework required. It owns its HTTP listener and provides no authentication or authorization. Integration with Auth Boundary and verified per-request identity needs investigation; it is not a Tapir transport replacement. Runtime docs and security boundary.
scala-effect-mcp 0.4.1 Published; supports 2026-07-28 and compatibility with 2025-06-18 and 2025-11-25. Its conformance harness documents expected failures and unsupported features. Scala-native, but introduces Cats Effect, fs2 and http4s. The published artifact uses Scala 3.9.0, newer than Platform's compiler. Check compiler compatibility and how its request context and HTTP middleware would connect to our existing context and Auth Boundary. Dependency versions.
zio-http-mcp 0.10.0 Published on Maven Central; supports both 2025-11-25 and 2026-07-28. Scala-native, but introduces ZIO, ZIO HTTP and ZIO Schema. Built with Scala 3.10.0, so compiler compatibility also needs checking. Its routes and authentication hooks need an integration design that preserves our Auth Boundary. Versioned documentation and build.
Official Java SDK 2.2.0-M1 Prerelease modern server implementation. Stable 2.1.0 supports the older protocol revisions. The milestone still excludes some features, including SEP-2243 custom headers. JVM-compatible and offers synchronous or asynchronous handlers, but needs transport integration with our HTTP stack. Assess its Reactor dependencies and the milestone's missing features before choosing it. Stable version definitions.

Before selecting a library:

  • Verify the intended client's actual protocol mode, agreed revision and fallback behavior.
  • Show how Auth Boundary authenticates the request and how verified identity, permissions and expiry reach both discovery and tool handlers. Library authentication helpers do not replace that boundary.
  • Check compiler and dependency compatibility, transport integration and the cost of any additional runtime.
  • Review the required conformance results and test our access-denial and no-business-effects cases. Published support and upstream test reports do not establish that our integration meets its requirements.

Protocol and failure contract

Protocol versions

Select the production revision together with the library. It is not settled yet.

The recorded desktop experiments used a local Codex backend, not cloud-hosted ChatGPT. The currently installed desktop app is 26.1016.51333, with bundled Codex 0.163.0-alpha.11. That client's protocol modes use 2025-06-18 in legacy mode and can use 2026-07-28 in modern mode, with legacy fallback. Its HTTP compatibility tests also cover a server selecting 2025-11-25. Source support does not establish which mode the deployed client enables. The earlier trials did not record the agreed protocol version, and their app version cannot be inferred from today's install.

Test each proposed revision with the intended client and chosen server library before claiming support. Recognizing a version identifier is not enough. The two protocol families use different lifecycle rules:

Revision family What the implementation must follow if selected
2025-06-18 / 2025-11-25 Use that revision's initialization handshake, capability negotiation and subsequent HTTP version header. The November version-negotiation rules describe the handshake; its header rules require HTTP 400 for unsupported versions and recommend 2025-03-26 when a missing version cannot otherwise be inferred. Test the rules for every revision actually supported.
2026-07-28 Use per-request version metadata and server/discover, rather than assuming an initialization handshake. Follow the versioning and compatibility rules, including unsupported-version handling.

Record the client application version and active protocol mode, server-library version, supported revisions and the revision used in successful requests. Test incompatible versions, missing or invalid version metadata and any claimed fallback. Use the selected revision's transport and authorization rules; neither row selects a baseline for this delivery.

Tool contracts

Tool Required permissions Input and result
hello_read mcp:read Empty object input. Return the fixed greeting Hello..
hello_write mcp:read and mcp:write Empty object input. Return a fixed confirmation of write permission that explicitly says no data was changed.

Both tools require MCP to be enabled for the tenant. Reject additional input properties and bound the result size. Neither tool reads inventory, calls business handlers, persists data or emits domain events. Their non-mutating MCP annotations describe their behavior; annotations never determine access. Discovery omits tools the user cannot use. A tool's name or use of HTTP POST does not determine its permissions.

HTTP and lifecycle

Surface Required behavior
Public OAuth metadata Return the configured MCP resource and authorization server. Keep this information reachable when tenant MCP access is off; rate-limit it independently.
POST /mcp Use Streamable HTTP with JSON responses and the selected revision's required headers, discovery and lifecycle methods. Support tools/list and tools/call.
Notifications and responses Accept only message types supported by the selected revision and follow its HTTP status and response-body rules.
Protocol negotiation Implement the version-specific negotiation and HTTP header checks above. Advertise only implemented versions and capabilities.
GET /mcp Return 405 through the protected request path. No standalone SSE stream, legacy HTTP+SSE or stdio transport is introduced.
Sessions Do not issue an MCP session identifier in this delivery. Earlier requests never replace authentication of the current request.
Origin Check supplied Origin values against deployment-owned policy. Reject an invalid Origin with 403. Requests without Origin still require authentication and authorization.
Other invalid messages Follow the selected MCP specification's distinction between HTTP failures and JSON-RPC errors. Cover unknown methods, unknown tools, malformed messages and invalid arguments.

Failure replies

Failure Reply and recovery
Missing, invalid or expired credential HTTP 401 with the protected-resource metadata challenge. Never return a browser login page. The client renews its token or starts sign-in again.
MCP disabled for the tenant HTTP 403, code tenant_mcp_disabled. Explain that an administrator must enable MCP.
Missing MCP read or write permission HTTP 403, code mcp_read_required or mcp_write_required. Point to the administrator; the denied handler must not run.
Inactive or unentitled membership HTTP 403; a generic mcp_access_denied reason is sufficient.
Required WorkOS or signing-key lookup unavailable HTTP 503 with safe, bounded retry guidance. Do not grant new access or send the user through another consent flow.
Request exceeds a configured limit Reject it with the appropriate safe capacity response and bounded processing time.

Access-denial bodies contain bounded code, message and correlationId fields. Do not expose provider API details, credentials, permission dumps or malformed-message internals. Use the Product messages to explain the result; an MCP client may paraphrase or hide response text.

MCP permissions are not OAuth scopes. Missing permissions and a disabled tenant must not produce an insufficient_scope challenge, request scope=mcp:read or scope=mcp:write, or trigger repeated consent. This delivery introduces no additional product OAuth scopes. A separately configured, genuine OAuth scope deficit still follows the standard OAuth rules.

Client recovery

The client renews external tokens directly with WorkOS; Platform never stores the client's refresh token. If renewal cannot recover access, the user signs in again using the existing connection. Cancelling grants no restored access. Valid consent may be reused; it is not approval of every tool call.

An administrator can restore permissions without requiring new consent. Access returns after the next fresh, successful WorkOS check; an earlier snapshot can retain its old result only until its deadline. Test recovery in the chosen client without prescribing its button names or navigation. The manual test plan covers the visible experience. No new Platform UI is required.

Components and interfaces

Keep the existing module boundaries. Auth Boundary currently lives in web, but sharing that module does not put external authentication in MCP handlers. core remains free of HTTP, provider SDK, storage and messaging dependencies.

Responsibility Proposed implementation
Resolve the connection An Auth Boundary adapter in web uses trusted routing to find the resource, tenant, environment, WorkOS organization and issuer. It rejects ambiguous mappings and forged internal identity headers.
Verify the bearer A framework-free verifier interface in core, implemented in infra, accepts the bearer and expected connection binding. Keys and issuer come from configuration, never a token-supplied URL.
Resolve current access A second interface in core, implemented in infra, retrieves the organization's MCP flag, active membership and complete effective permissions. It returns an absolute expiry or a typed failure.
Issue and verify internal credentials Auth Boundary issues an MCP-only credential. The inner Platform path verifies it before building trusted request context. Browser credentials cannot substitute for it, or vice versa.
Handle the protocol and tools Inner endpoints in web decode MCP requests and apply one shared access policy before discovery or invocation. They do not verify external WorkOS tokens or call WorkOS.
Define and provision permissions A shared catalog in core supplies navigation and MCP policy. Bootstrap exports its definitions and wires the adapters, configuration and telemetry.

Important types and invariants

Use the existing RequestContext and authenticated request-lifetime model. A typed MCP wrapper can carry the extra client, consent and resource information without turning arbitrary caller claims into trusted context.

Information What must survive verification and internal encoding
Resource binding Canonical resource URI, tenant, environment, WorkOS organization and issuer. One request has one trusted binding.
External identity Human subject, organization, client, consent ID from verified WorkOS sid, and external expiry. The authenticated human remains the acting principal.
Access decision Organization MCP flag, active membership, effective permissions, lookup start time and absolute expiry.
Internal context Verified identity and access decision, correlation information and MCP channel attribution. Internal decoding must preserve these values without accepting replacements from the caller.
Tool definition Stable name, description, input/result schema, explicit required permissions and fixed handler. Exactly two tools initially.
Failure Distinct categories for authentication, denied access, provider unavailability, invalid protocol/input and capacity rejection.

For example, synchronous interfaces fit the existing request model:

trait McpTokenVerifier:
  def verify(token: ExternalBearer, expected: McpResourceBinding): Either[McpFailure, VerifiedMcpIdentity]

trait McpAuthorityResolver:
  def resolve(identity: VerifiedMcpIdentity): Either[McpFailure, McpAuthority]

The bearer is secret input with redacted diagnostics. Do not persist it as attribution. Provider adapters own bounded I/O. Missing human or consent identity is a verification failure, not a system-principal fallback. Preserve consent as a typed value through credential encoding and decoding; its presence alone does not prove that consent is still active.

Authority and dispatch flow

  1. Auth Boundary: resolve the trusted connection, discard forged internal headers and verify the external token.
  2. Auth Boundary: retrieve current WorkOS access, or reuse a snapshot that still satisfies the binding and expiry rules.
  3. Auth Boundary: issue an internal MCP credential that cannot outlive either external authentication or the access decision.
  4. Inner Platform: verify the internal credential and construct request context.
  5. MCP dispatch: check the access deadline, tenant flag and MCP read permission. For a tool call, validate input and check its additional permissions before running the handler.
  6. Response: return a safe protocol result and record the outcome. Denied calls never reach a handler or business store.

The same checks protect protocol lifecycle requests, discovery, direct calls and requests on an existing connection. Public metadata does not grant access to any of them.

WorkOS access resolution and withdrawal

The Connect token supplies verified identity, not a permission or feature-flag snapshot. Auth Boundary uses server credentials for the WorkOS APIs; never forward the client's bearer to management APIs.

  1. Resolve the configured organization and confirm it matches the verified token.
  2. Find the user's active membership in that organization. Resolve every assigned environment or organization-specific role and take the union of their permission slugs. Do not use one role name or maintain a second local role mapping.
  3. List the enabled flags for that exact organization through the organization feature-flags API. Follow all pages. Require entitlement-remote-mcp-org; an absent flag or missing mapping means MCP is off. Failed or incomplete lookups mean access is unavailable, not enabled.
  4. Build the bounded access decision. Disabled MCP and missing user permissions remain separate reasons for denial.

Use the organization endpoint, not the user-flag endpoint, which can include flags from other organizations or user targeting. A user-targeted flag must not bypass this tenant gate. Groups need no separate lookup for these rules. Verify the described API calls and role resolution in the actual WorkOS environment before activation.

Cached access

An in-memory cache is optional. If used:

  • Bind a snapshot to the exact verified external credential, issuer/environment, resource, tenant, human, organization, client and consent. Store normalized access information, not bearer credentials.
  • Start its maximum age at the first supporting lookup. Its deadline is the earlier of external expiry and lookup start plus the configured maximum access age.
  • Check that deadline at issuance and dispatch. A cache hit, internal credential reissue or client reconnect cannot reset it.
  • A new external token requires fresh flag, membership and permission checks. Hidden provider caches must not carry old access into that new decision.

Role changes, membership removal and tenant disablement may leave an existing snapshot usable until its deadline. Choose a finite maximum age and measure that withdrawal delay before activation. A required lookup that times out, fails or returns partial results produces 503; it never extends an expired snapshot. No local activation file, revision counter or acknowledgement protocol is needed.

Keep the endpoint, OAuth metadata and bearer authentication reachable when MCP is off. Once refreshed access shows the flag is disabled, authenticated protected requests receive 403.

Authority expiry during a request

Important

The ADR requires data release to stop when authorization expires, including during an ongoing request. Before accepting this design, specify how the synchronous request path enforces that deadline and test an expiry after dispatch. Reusing AuthenticatedRequestLifetime provides a deadline value; it does not enforce cancellation or prevent a late response by itself. The allowed delay for tenant-flag changes does not waive credential expiry.

Permission freshness, external token expiry and consent withdrawal are separate controls. Measure the actual consent-withdrawal behavior and token lifetime; do not promise immediate revocation.

Permission catalog and provisioning

Move permission definitions out of AppNavigation into a small framework-free catalog in core. Keep their existing slugs, names and descriptions, and add mcp:read and mcp:write. Navigation retains its menu/route mapping and any compatibility aliases; MCP permissions add no menu items. Tool policy and navigation reference the same definitions.

Bootstrap provides a deterministic JSON export for provisioning. Update the permission-sync guide and affected setup/preflight tools to consume it instead of extracting text from navigation code.

Before activation, compare the export with definitions in the intended WorkOS environment. Deliberate setup creates missing definitions. Preflight blocks MCP entry if read is missing, and write-tool exposure if write is missing. It must not assign roles, replace memberships, delete unrelated permissions or enable a tenant. Approved grants are separate. A new automated release-seeding pipeline is not required for this delivery.

Runtime observability and deployment verification

Sentry

Reuse Platform's HTTP tracing, per-request isolation, correlation, sampling and existing Sentry configuration. No additional Sentry project, DSN or environment-variable setting is proposed.

Add child spans for external-token verification, WorkOS access resolution and MCP dispatch. Suggested operation names are auth.mcp.verify, auth.mcp.access and mcp.dispatch; use fixed descriptions rather than request payloads. They should make it possible to separate provider lookup time from total request time and handler execution.

The following transaction/error-event tags are proposed, with bounded values:

Proposed tag Values
dd.mcp.method Supported JSON-RPC method names, with other for unknown methods. Never copy an arbitrary caller string.
dd.mcp.outcome allowed, authentication_required, access_denied, dependency_unavailable, invalid_request or capacity_rejected.
dd.mcp.access_source workos or cache, only after access resolution reaches that point.

These are new fields, not fields already accepted by Platform. Extend SentryPrivacy's tag allowlist and validate their values together with the instrumentation. Test the final sanitized event and transaction, not just the call that adds a tag. First-class Sentry Logs have a separate attribute allowlist; this design does not add fields to it.

Expected 401 and 403 outcomes must be distinguishable without becoming exception reports. Unexpected exceptions use existing error capture once; do not add a second capture in MCP handlers. Reuse the existing allowed user, tenant, principal-type and correlation context. Do not add raw client IDs, consent IDs, resource URLs, tokens, permission sets or business data as Sentry tags or metric labels.

Logs and operating signals

Structured request logs record verified human, tenant, client, consent, resource, channel, correlation and outcome where known. Leave identity unknown when authentication failed before establishing it. Apply the existing redaction rules.

Measure request and provider-lookup duration, lookup failures, capacity/timeouts and access-decision age at dispatch. Keep these measurements aggregate; do not create a series per person or consent. No separate metrics pipeline or business-event stream is required for the two fixed tools.

Name the operational owner and where release-check results will be kept. Check the signals against the deployed release and environment before activation. Confirm that a slow or failed WorkOS lookup can be distinguished from failures elsewhere, and that measured withdrawal delay meets the agreed limit. Disable MCP and investigate if a required control fails. Setup and shutdown procedures belong with the delivery plan.

Implementation test plan

The manual checklist covers setup, client replies, permission changes and recovery. The checks below cover the server and provider integration. Implement them in the narrowest relevant test layers; the component and suite names in this proposal are not mandatory test-file names.

Automated tool and protocol checks

Covers AC-2, AC-4 and AC-6.

  • Test all four read/write combinations with the tenant enabled and disabled. Check discovery and direct invocation, including a direct call to a hidden tool. Enabling MCP must grant no user permissions.
  • Check both fixed replies and reject unexpected arguments. Use integration fixtures that detect inventory reads, persisted changes and business events; none may occur on success or denial.
  • Test version selection, discovery, calls, GET, Origin and invalid messages against each supported revision. Cover initialization, notifications and ping only where that revision defines them. Exercise configured size, rate, concurrency and timeout limits, including requests beyond each limit.
  • Check HTTP status, OAuth challenge and bounded error body separately. Authentication, permission, tenant and provider failures remain distinct; user/tenant denials never request additional OAuth consent or scopes.

Automated identity and tenant checks

Covers AC-1 and AC-3.

  • Reject missing, expired, damaged or incorrectly signed tokens, missing required claims and machine-only identities.
  • Reject wrong issuer, resource, environment, organization or host. Use two tenant fixtures to show that tenant A's credential cannot access tenant B. Do not trust tenant arguments or injected internal headers.
  • Reject browser credentials on the MCP path and MCP credentials on browser paths. Test typed internal encode/decode round trips, including two distinct consents for the same human and client.
  • Extend the existing authentication, tenant, context and credential suites. Browser login, callback, refresh, logout and protected routes must retain their behavior. No business envelope, journal or projection changes are expected.

Automated permission and provider checks

Covers AC-2 and AC-5.

  • Simulate failed, incomplete and timed-out flag, membership, role and signing-key lookups through provider interfaces. Test multiple/custom roles, complete pagination, inactive membership and a flag enabled only in another organization.
  • Use a controlled clock for cache hits, expiry, permission changes, membership removal and tenant disablement. A new external token requires fresh access; internal credential reissue cannot prolong the old decision.
  • Test the expiry of authority after dispatch and enforce the chosen response/cancellation behavior.
  • Verify external renewal and consent withdrawal with the actual WorkOS integration. The response fixture does not provide that integration and cannot establish OAuth recovery.
  • Check unique catalog entries, unchanged existing permission slugs and grants, deterministic export, missing-definition rejection and unchanged navigation without MCP menu entries.

Sentry and deployment checks

Covers AC-5 and AC-6.

  • Test allowed and denied requests, a slow/failed provider lookup and an unexpected exception. Check the spans, outcome, correlation, per-request isolation and sanitized Sentry payload. Keep secrets and business data out of all emitted signals.
  • Verify that new Sentry tag keys and values survive only the intended privacy allowlist. Expected denials do not create exception reports, and one unexpected failure is not captured twice.
  • Against the selected deployed release, inspect setup, disable/re-enable, recovery and browser regression results. Record the resource, client/protocol versions, environment and actual results. A saved endpoint or successful OAuth exchange alone does not prove tool access.
  • Use the manual checklist to check how the client presents failures and retries. A raw JSON-body test alone cannot establish that the user can understand the failure or recover.

Implementation choices to confirm

  • Choose the MCP library and protocol revision using the comparison and real-client checks. Confirm support for the specified WorkOS API calls in the target environment.
  • Choose whether to cache access, the finite maximum access age, and request/provider timeouts and capacity limits.
  • Complete the ongoing-request expiry design described above before accepting this program design.
  • Review supporting changes to the permission catalog, internal credential codec, ingress and Sentry privacy filters against their existing consumers. Scope and rollout remain in the delivery plan.