Skip to content

sign in to mcp servers that take a client id metadata document instead of registering apps - #163

Open
hkonsti wants to merge 1 commit into
mainfrom
hkonsti/mcp-cimd
Open

hkonsti wants to merge 1 commit into
mainfrom
hkonsti/mcp-cimd

Conversation

@hkonsti

@hkonsti hkonsti commented Oct 29, 2026

Copy link
Copy Markdown
Collaborator

Connecting a custom MCP server whose authorization server takes a Client ID Metadata Document (CIMD) but has no registration endpoint failed with "This server's sign-in doesn't let apps register themselves". https://ampcode.com/mcp is one: its authorization server, auth.ampcode.com, has client_id_metadata_document_supported: true and no registration_endpoint. These servers can now be signed in to.

The document. The api serves GET /oauth/client-metadata.json on APP_URL, next to its own /oauth/ and /.well-known/ routes. The document's client_id is its own URL. It lists the callback of every MCP sign-in (the custom server's and each built-in integration's), client_name, client_uri, logo_uri, the authorization_code and refresh_token grants, and token_endpoint_auth_method: none. /oauth/ already goes to the api in the Vite dev proxy, in serve-web.ts and through Caddy, so nothing else needed changing to serve it.

Which client a sign-in uses. Discovery now reads client_id_metadata_document_supported from the authorization server's metadata, and McpServer can say the server takes the document (clientIdMetadataDocument), has a registerUrl, or both. When starting a sign-in, the MCP spec's order applies (CIMD before Dynamic Client Registration, which the spec's draft deprecates). The document's URL is the client when the server takes one and APP_URL is https on a host that resolves only to public addresses. Otherwise a client is registered as before. A self-hosted instance on a private network, or one in development, keeps registering, because an authorization server out on the internet couldn't fetch the document from it. A server that takes only the document can't be signed in to from such an instance, and the form explains why. A server that offers neither is still turned away.

Refresh. Nothing changes in how refresh works. The client id a sign-in used is kept in the credential, as a registered one was, and sent on every token request. For a CIMD sign-in that client id is the document's URL. Existing connections keep their registered clients.

offline_access. Some authorization servers, Amp's among them, give a refresh token only for offline_access. Without it, a connection would end when its first access token expired. When the authorization server's scopes_supported lists it, discovered servers now add it to the scopes from the server's metadata, as the MCP spec allows.

The built-in integrations go through the same sign-in code. They still name their endpoints, so they keep registering. Granola, Notion, Linear and Exa also take the document, so each could switch to it by adding clientIdMetadataDocument: true. That is left for a separate change.

Testing

  • bun run typecheck, bun run test, bun run lint
  • New tests: discovery of an authorization server that takes only the document, or both; offline_access added only where the authorization server offers it; a sign-in sends the document's URL as client_id and registers nothing; with an APP_URL on a private address or on http it registers instead, and refuses a server that takes only the document; the callback keeps the document's URL as the client, and refreshing sends it; the document itself
  • Served the document through createHttpApp: 200, JSON, cached for an hour, open to any origin, about 800 bytes
  • Live: ampcode.com/mcp answers 401 pointing at its resource metadata. That metadata names auth.ampcode.com, whose metadata matches the description above. Discovery from this branch, run against it, gives clientIdMetadataDocument: true and scope offline_access. Its authorize endpoint takes a URL client_id (tried with a public CIMD client's document), with offline_access too. It turns down a redirect URI that isn't in the document, and a document it can't fetch (invalid_client).
  • Not tried: a full sign-in to Amp with this document, because the document has to be served from a public APP_URL

…horization server takes one

An MCP server's authorization server that advertises client_id_metadata_document_supported is
now given <APP_URL>/oauth/client-metadata.json as the client id, a document the api serves about
itself, rather than turned away when it has no registration endpoint. The document is preferred
over registering a client, as the MCP spec orders them, when APP_URL is a public https address the
authorization server can fetch it from; otherwise a client is registered as before. Discovered
servers also ask for offline_access where the authorization server offers it, so the sign-in comes
with a refresh token.

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant