Skip to content

feat(mcp-tools): add @clerk/mcp-tools v2 for MCP SDK v2 - #9795

Draft
djgould wants to merge 8 commits into
mainfrom
devin/clerk-mcp-tools-v2-23f24b
Draft

djgould wants to merge 8 commits into
mainfrom
devin/clerk-mcp-tools-v2-23f24b

Conversation

@djgould

@djgould djgould commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Description

Adds @clerk/mcp-tools to the monorepo as a rewrite around the MCP TypeScript SDK v2 (@modelcontextprotocol/server), replacing the standalone clerk/mcp-tools package and the resource-server code the Clerk remote MCP worker hand-rolls today.

createClerkMcpAuth() is the single entry point. Framework subpaths (/hono, /express, /next) return the same object with handlers in that framework's shape; the root export is the plain fetch binding for Cloudflare Workers, Deno and Bun.

const clerkMcp = createClerkMcpAuth({
  resource: 'https://mcp.example.com/mcp',
  scopes: [{ scope: 'notes:read', label: 'Read notes' }, 'notes:write'],
  baselineScopes: ['notes:read'],
  tools: { list_notes: ['notes:read'], create_note: ['notes:write'] },
});

app.get('/.well-known/oauth-protected-resource/mcp', clerkMcp.protectedResourceMetadata());
app.all('/mcp', clerkMcp.mcpHandler(createServer));
server.registerTool('list_notes', config, clerkMcp.withScopes('list_notes', async (args, ctx) => { ... }));
const downstream = await clerkMcp.exchangeToken(ctx.http.authInfo, { resource: 'https://api.example.com', scopes: ['notes:read'] });
  • Challenges: 401 with resource_metadata and a catalog-ordered scope list of the baseline plus the requested tools' scopes, and 403 insufficient_scope for step-up, or a tool error with insufficientScope: 'tool-error'. Tool calls are read from the body via the SDK's classifyInboundRequest, so routing headers cannot bypass the check.
  • Verification through @clerk/backend's verifyMachineAuthToken. Tokens must be issued for resource, which can be a function of the request. Opaque tokens bind through the verified aud once feat(backend): enforce audience binding for OAuth access tokens #9794 lands; until then they need JWT access tokens.
  • mcpHandler() authorizes every request in every binding, then dispatches to a fresh server. withScopes() checks again at call time and records telemetry.
  • Metadata: an RFC 9728 document that advertises baselineScopes, and a relay of the authorization server's RFC 8414 document for clients that look for it on the MCP server's origin.
  • exchangeToken(): an RFC 8693 client cached per subject, resource and scope set, with a scope-escalation check and stable ClerkMcpError codes.
  • Configuration: invalid scopes, tools and resource URLs fail at startup. Keys and credentials are read on first use, so builds and tests can import a server without them.

Review by commit: there are eight, one per module, and each typechecks and passes its tests.

Out of scope: the MCP client helpers and stores, migrating the remote MCP worker (demo in clerk/cloudflare-workers#2811), and resource registration in Clerk.

Release notes: the package is listed in .changeset/config.json ignore, following the @clerk/hono precedent, so nothing publishes until the standalone repo's publish workflow is retired. The version base is the published 0.6.0.

Checklist

  • pnpm test runs as expected.
  • pnpm build runs as expected.
  • (If applicable) JSDoc comments have been added or updated for any package exports
  • (If applicable) Documentation has been updated

Type of change

  • 🐛 Bug fix
  • 🌟 New feature
  • 🔨 Breaking change
  • 📖 Refactoring / dependency upgrade / documentation
  • other:

🤖 Generated with Claude Code

@vercel

vercel Bot commented Sep 16, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated
clerk-js-sandbox Ready Ready Preview Sep 17, 2026 4:13pm UTC
swingset Ready Ready Preview Sep 17, 2026 4:13pm UTC

Request Review

@coderabbitai

coderabbitai Bot commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Comment @coderabbitai help to get the list of available commands.

@changeset-bot

changeset-bot Bot commented Sep 16, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: af7fec9

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 0 packages

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Comment thread packages/mcp-tools/src/__tests__/express.test.ts Dismissed
Comment thread packages/mcp-tools/src/__tests__/express.test.ts Fixed
Comment thread packages/mcp-tools/src/metadata.ts Fixed
Comment thread packages/mcp-tools/src/scopes.ts Fixed
@pkg-pr-new

pkg-pr-new Bot commented Sep 16, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@9795

@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@9795

@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@9795

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@9795

@clerk/electron

npm i https://pkg.pr.new/@clerk/electron@9795

@clerk/electron-passkeys

npm i https://pkg.pr.new/@clerk/electron-passkeys@9795

@clerk/eslint-plugin

npm i https://pkg.pr.new/@clerk/eslint-plugin@9795

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@9795

@clerk/expo-google-signin

npm i https://pkg.pr.new/@clerk/expo-google-signin@9795

@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@9795

@clerk/express

npm i https://pkg.pr.new/@clerk/express@9795

@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@9795

@clerk/hono

npm i https://pkg.pr.new/@clerk/hono@9795

@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@9795

@clerk/mcp-tools

npm i https://pkg.pr.new/@clerk/mcp-tools@9795

@clerk/mosaic

npm i https://pkg.pr.new/@clerk/mosaic@9795

@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@9795

@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@9795

@clerk/react

npm i https://pkg.pr.new/@clerk/react@9795

@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@9795

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@9795

@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@9795

@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@9795

@clerk/ui

npm i https://pkg.pr.new/@clerk/ui@9795

@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@9795

@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@9795

commit: af7fec9

Comment thread packages/mcp-tools/src/__tests__/express.test.ts Dismissed
Comment thread packages/mcp-tools/src/__tests__/express.test.ts Dismissed
Comment thread packages/mcp-tools/src/__tests__/express.test.ts Dismissed
Comment thread packages/mcp-tools/src/__tests__/express.test.ts Dismissed
Comment thread packages/mcp-tools/src/__tests__/express.test.ts Dismissed
djgould and others added 8 commits September 17, 2026 12:10
Package manifest, build, test and TypeScript configuration. The package is listed in the changeset ignore list, so nothing publishes yet.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Reads the tools/call messages of a request through the SDK's classifyInboundRequest, resolves per-tool scopes from a Map, and orders scopes by the configured catalog.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
An MCP SDK token verifier backed by @clerk/backend. It reports the token's single audience as AuthInfo.resource and enforces its own resource option.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ation server metadata

RFC 9728 document with only the fields the RFC defines. The RFC 8414 document is relayed from the authorization server and cached, so it cannot drift from the instance's settings.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Cached per subject, resource and scope set, with a scope-escalation check and stable error codes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The 401 and 403 challenges, audience binding, the per-tool scope gate, withScopes(), mcpHandler() and exchangeToken(). Keys and credentials are read on first use.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Each returns the same object with handlers in the framework's shape. mcpHandler() authorizes in every binding, and the Express binding parses JSON itself so the scope gate always sees the body.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Usage per runtime, how requests are authorized, troubleshooting, and the migration table.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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