Skip to content

feat(den-api): accept Client ID Metadata Document clients on the MCP server - #5293

Merged
benjaminshafii merged 2 commits into
devfrom
feat/den-mcp-cimd
Sep 24, 2026
Merged

benjaminshafii merged 2 commits into
devfrom
feat/den-mcp-cimd

Conversation

@reachjalil

@reachjalil reachjalil commented Sep 23, 2026 •

Copy link
Copy Markdown
Member

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 its client_id; the server fetches and validates it. This PR makes Den accept those clients, using the @better-auth/cimd plugin 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-server did not advertise client_id_metadata_document_supported. A client that sent its metadata URL as client_id was turned away with invalid_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, including localhost), 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 URL client_id gets invalid_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 registering http://localhost/callback authorizes on any port. test/cimd-loopback-redirects.test.ts and test/cimd-policy.test.ts cover the matcher and the fetch policy. test/mcp-oauth-cursor-desktop.test.ts still passes the dynamic-registration path (session cookie name and 201 status refreshed).

@reachjalil
reachjalil requested a review from a team September 23, 2026 22:36
@mintlify

mintlify Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated
differentai 🟢 Ready View Preview Sep 24, 2026, 12:21 AM

💡 Tip: Enable Automations to automatically generate PRs for you.

@vercel

vercel Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
openwork-app Ready Ready Preview Sep 24, 2026 12:22am UTC
openwork-den Ready Ready Preview Sep 24, 2026 12:22am UTC
openwork-diagnostics Ready Ready Preview Sep 24, 2026 12:22am UTC
openwork-landing Ready Ready Preview, v0 Sep 24, 2026 12:22am UTC

…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.
@github-actions

github-actions Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

OpenWork evidence

Status Commit Preview Updated
⚪ No new preview 7d1f2a0 View run 2026-09-24 00:21:44 UTC

No change-specific evidence selected

@benjaminshafii
benjaminshafii merged commit fb210a1 into dev Sep 24, 2026
35 of 37 checks passed
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

5 active deployments
Preview – openwork-den — 7d1f2a01 Deployed Sep 24, 2026 by vercel[bot]
Preview – openwork-app — 7d1f2a01 Deployed Sep 24, 2026 by vercel[bot]
Preview – openwork-landing — 7d1f2a01 Deployed Sep 24, 2026 by vercel[bot]
Preview – openwork-diagnostics — 7d1f2a01 Deployed Sep 24, 2026 by vercel[bot]
staging - packages/docs — 7d1f2a01 Deployed Sep 24, 2026 by mintlify[bot]
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.

2 participants