Repository navigation
feat(den-api): accept Client ID Metadata Document clients on the MCP server - #5293
Merged
Merged
Conversation
Contributor
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Automations to automatically generate PRs for you. |
Contributor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…authorization server MCP clients may now present the HTTPS URL of a metadata document they host as their client_id when authorizing against OpenWork Connect. The @better-auth/cimd plugin (same 1.7.0-beta.10 line as the rest of Den's Better Auth stack) fetches and validates the document, stores it as a public client, and advertises client_id_metadata_document_supported in authorization-server discovery. Dynamic client registration stays enabled as the fallback. Den keeps its own policy around the plugin: redirect URIs are still checked by the MCP redirect rules at authorize time, and a new pre-fetch gate refuses documents hosted on Den's own origins and, on hosted deployments, hostnames that do not resolve to public addresses. Tests: a MySQL-backed authorization-server suite for the URL client_id flow, a unit suite for the fetch policy, and the discovery assertion in the resource URL test. The Cursor Desktop dynamic-registration suite is refreshed for the current session cookie prefix and 201 registration status so it runs green again as the fallback witness. Docs describe the new registration option.
Native MCP clients that identify with a Client ID Metadata Document register port-less loopback redirect URIs (Claude Code lists http://localhost/callback and http://127.0.0.1/callback) and bind an ephemeral port per authorization, as RFC 8252 section 7.3 allows. Better Auth only relaxes the port for loopback IP literals, so a localhost redirect was refused with invalid_redirect. Den now wraps the cimd client discovery so the client resolved for the current request also lists the request's loopback redirect URI when scheme, host, path and query match a registered loopback entry. The stored record is unchanged and every other redirect keeps exact matching. Covered by a unit suite for the matcher and two authorization-server cases.
reachjalil
force-pushed
the
feat/den-mcp-cimd
branch
from
September 24, 2026 00:20
6b45c14 to
7d1f2a0
Compare
Contributor
benjaminshafii
approved these changes
Sep 24, 2026
benjaminshafii
added a commit
that referenced
this pull request
Sep 30, 2026
…5467) Claude's Client ID Metadata Document lists urn:ietf:params:oauth:grant-type:jwt-bearer next to authorization_code and refresh_token. @better-auth/cimd rejected the whole document, so connecting Claude to the MCP server failed with invalid_client since #5293 started advertising CIMD support. Patch the plugin to keep only the supported grant types before validation. Signed-off-by: Benjamin Shafii <ben@prologe.io>
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What is this about?
OpenWork Connect (
api.openworklabs.com/mcp/agent) is the OAuth server that MCP clients sign in to. The MCP authorization spec now recommends Client ID Metadata Documents (CIMD): instead of registering with every server first, a client hosts a small JSON document describing itself and uses that document's HTTPS URL as itsclient_id; the server fetches and validates it. This PR makes Den accept those clients, using the@better-auth/cimdplugin from the Better Auth line Den already runs. Dynamic client registration stays on as the fallback.What problem does it solve?
Claude Code, Codex, Cursor and VS Code prefer CIMD and only fall back to dynamic registration when a server does not advertise it. Every one of them had to mint a throwaway registration with OpenWork first, and servers that turn dynamic registration off could not be reached at all. CIMD also lets the consent screen show the client's real name from a document the client controls.
What was the situation before?
Discovery on
/.well-known/oauth-authorization-serverdid not advertiseclient_id_metadata_document_supported. A client that sent its metadata URL asclient_idwas turned away withinvalid_client; only dynamically registered or pre-registered client ids worked. Two things Den adds around the plugin: a pre-fetch policy that refuses documents hosted on Den's own origins or on private addresses, and loopback redirect matching on any port (RFC 8252 §7.3, includinglocalhost), which native clients such as Claude Code rely on because they bind an ephemeral port per sign-in.Release note
MCP clients such as Claude Code, Codex and Cursor can connect to OpenWork Connect with a Client ID Metadata Document instead of registering a client first.
Evidence
No E2E; unit-tested.
ee/apps/den-api/test/mcp-oauth-cimd.test.ts(MySQL) — before: a URLclient_idgetsinvalid_client; after: discovery advertises support, the code + PKCE exchange and a resource-less refresh succeed, the client is stored under its URL, a mismatched document, a foreign redirect and an unlisted loopback path are refused, and a document registeringhttp://localhost/callbackauthorizes on any port.test/cimd-loopback-redirects.test.tsandtest/cimd-policy.test.tscover the matcher and the fetch policy.test/mcp-oauth-cursor-desktop.test.tsstill passes the dynamic-registration path (session cookie name and 201 status refreshed).