Skip to content

Workspace deploy: root /.well-known/* is routed to Dispatch and ignores the app path suffix, so OAuth discovery for a spoke app's MCP server fails with invalid_request #4866

Description

@efernandezr

Workspace deploy: root /.well-known/* is routed to Dispatch and ignores the app path suffix, so OAuth discovery for any spoke app's MCP server lands on Dispatch's authorization server and fails

Version: @agent-native/core 0.178.1, dist/deploy/workspace-deploy.js (Vercel routes: { src: "/\\.well-known/(.*)", dest: "/dispatch-server" }; the Netlify and gateway variants do the same), dist/mcp/oauth-route.js (handleAuthorize, getMcpOAuthAudiences).

What happens. In a path-mounted workspace (/chat, /documents, /brand-assets, /dispatch on one host), a spoke app's MCP endpoint advertises itself correctly: GET /chat/mcp returns WWW-Authenticate: Bearer resource_metadata="https://host/chat/.well-known/oauth-protected-resource", and that document names authorization_servers: ["https://host/chat"], whose /chat/.well-known/oauth-authorization-server points at /chat/mcp/oauth/authorize. Claude (claude.ai custom connector) resolves the metadata with the RFC 8414 / RFC 9728 path-insertion form at the site root instead: GET https://host/.well-known/oauth-authorization-server/chat (and /.well-known/oauth-protected-resource/chat/mcp). The deploy routes every /.well-known/* request to the Dispatch server, and Dispatch's handler ignores the suffix, so both documents come back with issuer: https://host/dispatch and authorization_endpoint: https://host/dispatch/mcp/oauth/authorize. Claude then opens /dispatch/mcp/oauth/authorize?...&resource=https://host/chat/mcp; Dispatch's getMcpOAuthAudiences only contains its own /dispatch/mcp prefixes, so handleAuthorize answers {"error":"invalid_request","error_description":"Invalid OAuth authorization request"}. Observed 2026-09-12 on a Vercel production deployment for the Chat, Documents and Assets connectors; the Dispatch connector works because its resource is its own.

Why. Path-insertion discovery is what the RFCs prescribe for resources that live under a path, and popular hosts use it. The root well-known route exists so the workspace has one discovery document, but it is not suffix-aware, and the spoke apps' authorization servers are never reachable through it.

Expected. One of: the root /.well-known/oauth-authorization-server/<app>[/mcp] and /.well-known/oauth-protected-resource/<app>/mcp routes forward to that app's own documents (a route per registered workspace app, or a suffix-aware handler in Dispatch); or Dispatch acts as the workspace authorization server for every app and getMcpOAuthAudiences accepts every registered app's /<app>/mcp resource; or the docs state that in a path-mounted workspace only the Dispatch connector supports OAuth hosts and spoke apps need the bearer fallback.

Impact. In a path-mounted workspace no spoke app can be added to claude.ai (or any host using path-insertion discovery) as its own connector; users get an opaque invalid_request page after clicking Connect.

Workaround in Joney. Use the Dispatch connector (/dispatch/mcp, verbs list_apps, ask_app, open_app) from claude.ai; for hosts that accept a static header (Claude Code, Cursor), mint a token on /<app>/mcp/connect.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions