From 17aa1579f7f63c49745e881189dc1a5835db6155 Mon Sep 17 00:00:00 2001 From: Xiao Yijun Date: Sat, 29 Aug 2026 12:41:12 +0800 Subject: [PATCH 1/4] docs(mcp-auth): add quick links and opaque token example --- packages/mcp-auth/README.md | 108 +++++++++++++++++++++++++++++++++++- 1 file changed, 107 insertions(+), 1 deletion(-) diff --git a/packages/mcp-auth/README.md b/packages/mcp-auth/README.md index 18cf537..d9e5ebe 100644 --- a/packages/mcp-auth/README.md +++ b/packages/mcp-auth/README.md @@ -2,6 +2,8 @@ > The MCP SDK asks you to bring two things: a token verifier and your auth metadata. mcp-auth gives you both, for any OAuth / OIDC provider. +[Get started](https://mcp-auth.dev/docs) · [Sample servers](https://github.com/mcp-auth/js/tree/master/packages/sample-servers) · [Provider list](https://mcp-auth.dev/provider-list) + The MCP TypeScript SDK v2 (`@modelcontextprotocol/server`) ships the entire HTTP layer of MCP authorization itself: `requireBearerAuth`, `verifyBearerToken`, `oauthMetadataResponse`, and official framework adapters like `@modelcontextprotocol/express`. What it leaves to you is provider integration — verifying the access tokens your OAuth 2.0 / OpenID Connect provider issues, and describing that provider in your server's metadata. That is exactly what mcp-auth does: @@ -113,7 +115,7 @@ app.all( app.listen(3000); ``` -See [the documentation](https://mcp-auth.dev) for the full guide, and the [sample servers](https://github.com/mcp-auth/js/tree/master/packages/sample-servers) in this repository for complete runnable examples. +The fastest way to get started is the [step-by-step guide](https://mcp-auth.dev/docs). For complete runnable examples — `whoami` and `todo-manager` as Cloudflare Workers, plus an Express variant — see the [sample servers](https://github.com/mcp-auth/js/tree/master/packages/sample-servers) in this repository. ## Configuration highlights @@ -123,6 +125,110 @@ See [the documentation](https://mcp-auth.dev) for the full guide, and the [sampl - `jwtVerifyOptions` passes options through to [jose](https://github.com/panva/jose)'s `jwtVerify` for advanced tuning (clock tolerance, required claims, etc.); `issuer` and `audience` are excluded — they always come from the metadata declaration. - Verified tokens are surfaced as `McpAuthInfo` — the SDK's `AuthInfo` plus guaranteed `issuer`, `subject`, and the full `claims` payload. +## Opaque access tokens + +`MCPAuth` verifies JWT access tokens against your provider's JWKS. Some authorization servers issue opaque access tokens instead — random strings with nothing to verify locally. The two halves of mcp-auth are decoupled, so this case is covered by bringing your own verifier: implement the SDK's `OAuthTokenVerifier` against your server's token introspection endpoint ([RFC 7662](https://datatracker.ietf.org/doc/html/rfc7662)), and keep using the metadata half — the discovery documents, the challenge URL, and `getAuthInfo()` all work unchanged. + +```ts +import { + OAuthError, + OAuthErrorCode, + requireBearerAuth, + type OAuthTokenVerifier, +} from '@modelcontextprotocol/server'; +import { MCPAuth, type McpAuthInfo } from 'mcp-auth'; + +const issuer = 'https://auth.example.com/oidc'; +const resource = 'https://api.example.com/mcp'; + +// The metadata half works exactly as in the examples above +const mcpAuth = new MCPAuth({ + protectedResourceMetadata: { + resource, + authorizationServer: { issuer, type: 'oidc' }, + scopesSupported: ['read:notes'], + }, +}); + +const introspectionEndpoint = 'https://auth.example.com/oidc/token/introspection'; +// A confidential client registered with the authorization server (e.g. a machine-to-machine +// app) — most servers require one to introspect tokens issued to other clients +const clientId = 'your-m2m-client-id'; +const clientSecret = 'your-m2m-client-secret'; + +const introspectionVerifier: OAuthTokenVerifier = { + async verifyAccessToken(token): Promise { + let response: Response; + + try { + response = await fetch(introspectionEndpoint, { + method: 'POST', + headers: { + 'content-type': 'application/x-www-form-urlencoded', + authorization: `Basic ${btoa(`${clientId}:${clientSecret}`)}`, + }, + body: new URLSearchParams({ token, token_type_hint: 'access_token' }), + signal: AbortSignal.timeout(5000), + }); + } catch (error) { + /* + * A plain `Error`, not an `OAuthError`: the SDK answers 500. The token could not be + * verified, which is different from being invalid — a 401 would send a client with a + * perfectly fine token into a pointless re-authorization. + */ + throw new Error('Failed to reach the token introspection endpoint.', { cause: error }); + } + + if (!response.ok) { + throw new Error(`Introspection request failed with status ${response.status}.`); + } + + const data = (await response.json()) as McpAuthInfo['claims']; + + // The MCP spec still requires these checks — introspection does not exempt them + if (data.active !== true) { + throw new OAuthError(OAuthErrorCode.InvalidToken, 'The token is not active.'); + } + + if (!(Array.isArray(data.aud) ? data.aud : [data.aud]).includes(resource)) { + throw new OAuthError(OAuthErrorCode.InvalidToken, 'The token audience does not match.'); + } + + if (typeof data.iss === 'string' && data.iss !== issuer) { + throw new OAuthError(OAuthErrorCode.InvalidToken, 'The token issuer is not trusted.'); + } + + if (typeof data.sub !== 'string' || typeof data.exp !== 'number') { + throw new OAuthError(OAuthErrorCode.InvalidToken, 'The token has no `sub` or `exp`.'); + } + + // The `McpAuthInfo` shape, so `getAuthInfo()` in tool callbacks works unchanged + return { + token, + issuer, + subject: data.sub, + clientId: typeof data.client_id === 'string' ? data.client_id : '', + scopes: typeof data.scope === 'string' ? data.scope.split(' ').filter(Boolean) : [], + expiresAt: data.exp, + claims: data, + }; + }, +}; + +// Only the gate changes; the discovery documents still come from `mcpAuth` as shown above +const gate = requireBearerAuth({ + verifier: introspectionVerifier, + resourceMetadataUrl: mcpAuth.resourceMetadataUrl, + requiredScopes: ['read:notes'], +}); +``` + +A few things to know: + +- **The endpoint**: some servers advertise it as `introspection_endpoint` in their metadata, others keep it off the public discovery document entirely (e.g. an internal admin API). Configure whatever yours is. +- **The credentials**: most servers only let authenticated confidential clients introspect tokens issued to other clients; some deployments protect the endpoint at the network level instead. Check your server's policy. +- **The cost**: every request is an introspection round-trip. That is also the point — revoked tokens are rejected immediately. Add caching only if you accept the revocation delay. + ## Join the discussion Join the [MCP Auth org discussion](https://github.com/orgs/mcp-auth/discussions) to ask questions or share your feedback. From aa3b1ba5b329cd3d0161ffee3ae13747e830de01 Mon Sep 17 00:00:00 2001 From: Xiao Yijun Date: Sat, 29 Aug 2026 12:47:09 +0800 Subject: [PATCH 2/4] docs(mcp-auth): streamline README get started example --- packages/mcp-auth/README.md | 78 +++++++++---------------------------- 1 file changed, 18 insertions(+), 60 deletions(-) diff --git a/packages/mcp-auth/README.md b/packages/mcp-auth/README.md index d9e5ebe..1a749bd 100644 --- a/packages/mcp-auth/README.md +++ b/packages/mcp-auth/README.md @@ -2,7 +2,7 @@ > The MCP SDK asks you to bring two things: a token verifier and your auth metadata. mcp-auth gives you both, for any OAuth / OIDC provider. -[Get started](https://mcp-auth.dev/docs) · [Sample servers](https://github.com/mcp-auth/js/tree/master/packages/sample-servers) · [Provider list](https://mcp-auth.dev/provider-list) +[Docs & tutorials](https://mcp-auth.dev) · [Sample servers](https://github.com/mcp-auth/js/tree/master/packages/sample-servers) The MCP TypeScript SDK v2 (`@modelcontextprotocol/server`) ships the entire HTTP layer of MCP authorization itself: `requireBearerAuth`, `verifyBearerToken`, `oauthMetadataResponse`, and official framework adapters like `@modelcontextprotocol/express`. What it leaves to you is provider integration — verifying the access tokens your OAuth 2.0 / OpenID Connect provider issues, and describing that provider in your server's metadata. @@ -11,7 +11,7 @@ That is exactly what mcp-auth does: 1. **A token verifier** — `MCPAuth` implements the SDK's `OAuthTokenVerifier` interface. It discovers your provider's metadata, fetches its JWKS, and verifies JWT access tokens (signature, issuer, audience, expiration, and the claims MCP servers need), with sensible caching throughout. `mcpAuth.getBearerAuthOptions()` bundles the verifier with the RFC 9728 metadata URL into the SDK's `BearerAuthOptions`, ready for `requireBearerAuth`. 2. **Your auth metadata** — `mcpAuth.getAuthMetadataOptions()` returns the SDK's `AuthMetadataOptions`, ready to serve the OAuth discovery documents. -It implements the authorization requirements of the [latest MCP specification](https://modelcontextprotocol.io/specification/latest/basic/authorization) and works with any OAuth 2.0 / OpenID Connect provider that meets them. Check out the [MCP-compatible providers](https://mcp-auth.dev/docs/provider-list) list for real-time compatibility checks. +It implements the authorization requirements of the [latest MCP specification](https://modelcontextprotocol.io/specification/latest/basic/authorization) and works with any OAuth 2.0 / OpenID Connect provider that meets them. ## Installation @@ -23,9 +23,7 @@ npm install mcp-auth @modelcontextprotocol/server Still on MCP SDK v1 (`@modelcontextprotocol/sdk`)? Stay on the 0.2 line — `npm install mcp-auth@0.2` — and see the [v0.2.0 code and samples](https://github.com/mcp-auth/js/tree/v0.2.0). -## Usage - -### Fetch-native runtimes (Cloudflare Workers, Deno, Bun, Node.js) +## Get started ```ts import { @@ -36,6 +34,7 @@ import { } from '@modelcontextprotocol/server'; import { getAuthInfo, MCPAuth } from 'mcp-auth'; +// 1. Declare this MCP server and the authorization server it trusts const mcpAuth = new MCPAuth({ protectedResourceMetadata: { resource: 'https://api.example.com/mcp', @@ -44,78 +43,37 @@ const mcpAuth = new MCPAuth({ }, }); -const createServer = () => { +// 2. Gate your MCP endpoint — signature, issuer, audience, expiration, and scopes all enforced +const gate = requireBearerAuth(mcpAuth.getBearerAuthOptions({ requiredScopes: ['read:notes'] })); + +// 3. Read the verified identity in your tools with `getAuthInfo` +const handler = createMcpHandler(() => { const server = new McpServer({ name: 'Notes', version: '1.0.0' }); server.registerTool('whoami', { description: 'Get the current user' }, (ctx) => { + // Pass { requiredScopes: [...] } as the second argument for per-tool authorization const { subject, claims } = getAuthInfo(ctx); return { content: [{ type: 'text', text: JSON.stringify({ subject, claims }) }] }; }); return server; -}; - -const handler = createMcpHandler(createServer); -const gate = requireBearerAuth(mcpAuth.getBearerAuthOptions({ requiredScopes: ['read:notes'] })); +}); +// 4. Wire it up (Cloudflare Workers, Deno, Bun, Node.js) export default { async fetch(request: Request): Promise { - // Serve the OAuth discovery documents; the path guard keeps the (lazily fetched) - // metadata resolution off the request path of regular MCP traffic if (new URL(request.url).pathname.startsWith('/.well-known/')) { - const metadataResponse = oauthMetadataResponse(request, await mcpAuth.getAuthMetadataOptions()); - if (metadataResponse) { - return metadataResponse; - } + // Serve the OAuth discovery documents + const metadata = oauthMetadataResponse(request, await mcpAuth.getAuthMetadataOptions()); + if (metadata) return metadata; } - // Require a valid Bearer token for everything else const auth = await gate(request); - if (auth instanceof Response) { - return auth; - } - + if (auth instanceof Response) return auth; return handler.fetch(request, { authInfo: auth }); }, }; ``` -### Express (via `@modelcontextprotocol/express`) - -```ts -import { - createMcpExpressApp, - mcpAuthMetadataRouter, - requireBearerAuth, -} from '@modelcontextprotocol/express'; -import { toNodeHandler } from '@modelcontextprotocol/node'; -import { createMcpHandler } from '@modelcontextprotocol/server'; -import { MCPAuth } from 'mcp-auth'; - -const mcpAuth = new MCPAuth({ - protectedResourceMetadata: { - resource: 'https://api.example.com/mcp', - authorizationServer: { issuer: 'https://auth.example.com/oidc', type: 'oidc' }, - scopesSupported: ['read:notes'], - }, -}); - -// Reuses `createServer` from the fetch-native example above -const mcpNodeHandler = toNodeHandler(createMcpHandler(createServer)); - -const app = createMcpExpressApp(); -app.use(mcpAuthMetadataRouter(await mcpAuth.getAuthMetadataOptions())); -app.all( - '/mcp', - requireBearerAuth(mcpAuth.getBearerAuthOptions({ requiredScopes: ['read:notes'] })), - /* - * `createMcpExpressApp` applies `express.json()`, which drains the request stream, so the - * parsed body is passed along explicitly. - */ - async (request, response) => mcpNodeHandler(request, response, request.body) -); -app.listen(3000); -``` - -The fastest way to get started is the [step-by-step guide](https://mcp-auth.dev/docs). For complete runnable examples — `whoami` and `todo-manager` as Cloudflare Workers, plus an Express variant — see the [sample servers](https://github.com/mcp-auth/js/tree/master/packages/sample-servers) in this repository. +Head to [mcp-auth.dev](https://mcp-auth.dev) for tutorials and the full documentation. For complete runnable projects — `whoami` and `todo-manager` as Cloudflare Workers, plus an Express variant built with `@modelcontextprotocol/express` — see the [sample servers](https://github.com/mcp-auth/js/tree/master/packages/sample-servers) in this repository. ## Configuration highlights @@ -141,7 +99,7 @@ import { MCPAuth, type McpAuthInfo } from 'mcp-auth'; const issuer = 'https://auth.example.com/oidc'; const resource = 'https://api.example.com/mcp'; -// The metadata half works exactly as in the examples above +// The metadata half works exactly as in the example above const mcpAuth = new MCPAuth({ protectedResourceMetadata: { resource, From a1a9b8b6e7f71b2cd3260a9c7c8f3760cb4c9f04 Mon Sep 17 00:00:00 2001 From: Xiao Yijun Date: Sat, 29 Aug 2026 12:51:29 +0800 Subject: [PATCH 3/4] docs(mcp-auth): move SDK v1 note under the tagline --- packages/mcp-auth/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/mcp-auth/README.md b/packages/mcp-auth/README.md index 1a749bd..c0c7307 100644 --- a/packages/mcp-auth/README.md +++ b/packages/mcp-auth/README.md @@ -2,6 +2,8 @@ > The MCP SDK asks you to bring two things: a token verifier and your auth metadata. mcp-auth gives you both, for any OAuth / OIDC provider. +> Still on MCP SDK v1 (`@modelcontextprotocol/sdk`)? Stay on the 0.2 line — `npm install mcp-auth@0.2` — and see the [v0.2.0 code and samples](https://github.com/mcp-auth/js/tree/v0.2.0). + [Docs & tutorials](https://mcp-auth.dev) · [Sample servers](https://github.com/mcp-auth/js/tree/master/packages/sample-servers) The MCP TypeScript SDK v2 (`@modelcontextprotocol/server`) ships the entire HTTP layer of MCP authorization itself: `requireBearerAuth`, `verifyBearerToken`, `oauthMetadataResponse`, and official framework adapters like `@modelcontextprotocol/express`. What it leaves to you is provider integration — verifying the access tokens your OAuth 2.0 / OpenID Connect provider issues, and describing that provider in your server's metadata. @@ -21,8 +23,6 @@ npm install mcp-auth @modelcontextprotocol/server `@modelcontextprotocol/server` v2 is a peer dependency. Node.js >= 20; ESM only. -Still on MCP SDK v1 (`@modelcontextprotocol/sdk`)? Stay on the 0.2 line — `npm install mcp-auth@0.2` — and see the [v0.2.0 code and samples](https://github.com/mcp-auth/js/tree/v0.2.0). - ## Get started ```ts From cb6c7f33892235683e0befcb72f5fb1f64485c04 Mon Sep 17 00:00:00 2001 From: Xiao Yijun Date: Sat, 29 Aug 2026 12:57:36 +0800 Subject: [PATCH 4/4] docs(mcp-auth): tidy README blockquotes and punctuation --- packages/mcp-auth/README.md | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/packages/mcp-auth/README.md b/packages/mcp-auth/README.md index c0c7307..8237274 100644 --- a/packages/mcp-auth/README.md +++ b/packages/mcp-auth/README.md @@ -2,16 +2,14 @@ > The MCP SDK asks you to bring two things: a token verifier and your auth metadata. mcp-auth gives you both, for any OAuth / OIDC provider. -> Still on MCP SDK v1 (`@modelcontextprotocol/sdk`)? Stay on the 0.2 line — `npm install mcp-auth@0.2` — and see the [v0.2.0 code and samples](https://github.com/mcp-auth/js/tree/v0.2.0). - [Docs & tutorials](https://mcp-auth.dev) · [Sample servers](https://github.com/mcp-auth/js/tree/master/packages/sample-servers) -The MCP TypeScript SDK v2 (`@modelcontextprotocol/server`) ships the entire HTTP layer of MCP authorization itself: `requireBearerAuth`, `verifyBearerToken`, `oauthMetadataResponse`, and official framework adapters like `@modelcontextprotocol/express`. What it leaves to you is provider integration — verifying the access tokens your OAuth 2.0 / OpenID Connect provider issues, and describing that provider in your server's metadata. +The MCP TypeScript SDK v2 (`@modelcontextprotocol/server`) ships the entire HTTP layer of MCP authorization itself: `requireBearerAuth`, `verifyBearerToken`, `oauthMetadataResponse`, and official framework adapters like `@modelcontextprotocol/express`. What it leaves to you is provider integration: verifying the access tokens your OAuth 2.0 / OpenID Connect provider issues, and describing that provider in your server's metadata. That is exactly what mcp-auth does: -1. **A token verifier** — `MCPAuth` implements the SDK's `OAuthTokenVerifier` interface. It discovers your provider's metadata, fetches its JWKS, and verifies JWT access tokens (signature, issuer, audience, expiration, and the claims MCP servers need), with sensible caching throughout. `mcpAuth.getBearerAuthOptions()` bundles the verifier with the RFC 9728 metadata URL into the SDK's `BearerAuthOptions`, ready for `requireBearerAuth`. -2. **Your auth metadata** — `mcpAuth.getAuthMetadataOptions()` returns the SDK's `AuthMetadataOptions`, ready to serve the OAuth discovery documents. +1. **A token verifier**: `MCPAuth` implements the SDK's `OAuthTokenVerifier` interface. It discovers your provider's metadata, fetches its JWKS, and verifies JWT access tokens (signature, issuer, audience, expiration, and the claims MCP servers need), with sensible caching throughout. `mcpAuth.getBearerAuthOptions()` bundles the verifier with the RFC 9728 metadata URL into the SDK's `BearerAuthOptions`, ready for `requireBearerAuth`. +2. **Your auth metadata**: `mcpAuth.getAuthMetadataOptions()` returns the SDK's `AuthMetadataOptions`, ready to serve the OAuth discovery documents. It implements the authorization requirements of the [latest MCP specification](https://modelcontextprotocol.io/specification/latest/basic/authorization) and works with any OAuth 2.0 / OpenID Connect provider that meets them. @@ -21,7 +19,7 @@ It implements the authorization requirements of the [latest MCP specification](h npm install mcp-auth @modelcontextprotocol/server ``` -`@modelcontextprotocol/server` v2 is a peer dependency. Node.js >= 20; ESM only. +`@modelcontextprotocol/server` v2 is a peer dependency. Node.js >= 20; ESM only. Still on MCP SDK v1 (`@modelcontextprotocol/sdk`)? Use [`mcp-auth@0.2`](https://github.com/mcp-auth/js/tree/v0.2.0). ## Get started @@ -43,7 +41,7 @@ const mcpAuth = new MCPAuth({ }, }); -// 2. Gate your MCP endpoint — signature, issuer, audience, expiration, and scopes all enforced +// 2. Gate your MCP endpoint: signature, issuer, audience, expiration, and scopes all enforced const gate = requireBearerAuth(mcpAuth.getBearerAuthOptions({ requiredScopes: ['read:notes'] })); // 3. Read the verified identity in your tools with `getAuthInfo` @@ -73,19 +71,19 @@ export default { }; ``` -Head to [mcp-auth.dev](https://mcp-auth.dev) for tutorials and the full documentation. For complete runnable projects — `whoami` and `todo-manager` as Cloudflare Workers, plus an Express variant built with `@modelcontextprotocol/express` — see the [sample servers](https://github.com/mcp-auth/js/tree/master/packages/sample-servers) in this repository. +Head to [mcp-auth.dev](https://mcp-auth.dev) for tutorials and the full documentation. The [sample servers](https://github.com/mcp-auth/js/tree/master/packages/sample-servers) in this repository are complete runnable projects: `whoami` and `todo-manager` as Cloudflare Workers, plus an Express variant built with `@modelcontextprotocol/express`. ## Configuration highlights -- `protectedResourceMetadata` is your RFC 9728 Protected Resource Metadata declaration: everything in it is published through the SDK's metadata helpers, and the token verifier enforces what it declares — the `aud` claim must match `resource`, the `iss` claim must match the configured authorization server. -- `authorizationServer` accepts a discovery config (`{ issuer, type }`, metadata fetched lazily and cached — safe for edge runtimes where module-init network calls are not allowed) or a resolved config with metadata (hardcoded or pre-fetched via `fetchServerConfig()`). +- `protectedResourceMetadata` is your RFC 9728 Protected Resource Metadata declaration. Everything in it is published through the SDK's metadata helpers, and the token verifier enforces what it declares: the `aud` claim must match `resource` and the `iss` claim must match the configured authorization server. +- `authorizationServer` accepts a discovery config (`{ issuer, type }`, metadata fetched lazily and cached, safe for edge runtimes where module-init network calls are not allowed) or a resolved config with metadata (hardcoded or pre-fetched via `fetchServerConfig()`). - Audience (`aud`) validation always expects your `resource` identifier and cannot be redirected or disabled: the MCP authorization specification requires access tokens to be bound to the resource they are issued for (RFC 8707), so tokens without a matching `aud` claim are rejected. -- `jwtVerifyOptions` passes options through to [jose](https://github.com/panva/jose)'s `jwtVerify` for advanced tuning (clock tolerance, required claims, etc.); `issuer` and `audience` are excluded — they always come from the metadata declaration. -- Verified tokens are surfaced as `McpAuthInfo` — the SDK's `AuthInfo` plus guaranteed `issuer`, `subject`, and the full `claims` payload. +- `jwtVerifyOptions` passes options through to [jose](https://github.com/panva/jose)'s `jwtVerify` for advanced tuning (clock tolerance, required claims, etc.); `issuer` and `audience` are excluded since they always come from the metadata declaration. +- Verified tokens are surfaced as `McpAuthInfo`, the SDK's `AuthInfo` plus guaranteed `issuer`, `subject`, and the full `claims` payload. ## Opaque access tokens -`MCPAuth` verifies JWT access tokens against your provider's JWKS. Some authorization servers issue opaque access tokens instead — random strings with nothing to verify locally. The two halves of mcp-auth are decoupled, so this case is covered by bringing your own verifier: implement the SDK's `OAuthTokenVerifier` against your server's token introspection endpoint ([RFC 7662](https://datatracker.ietf.org/doc/html/rfc7662)), and keep using the metadata half — the discovery documents, the challenge URL, and `getAuthInfo()` all work unchanged. +`MCPAuth` verifies JWT access tokens against your provider's JWKS. Some authorization servers issue opaque access tokens instead: random strings with nothing to verify locally. The two halves of mcp-auth are decoupled, so this case is covered by bringing your own verifier: implement the SDK's `OAuthTokenVerifier` against your server's token introspection endpoint ([RFC 7662](https://datatracker.ietf.org/doc/html/rfc7662)), and keep using the metadata half. The discovery documents, the challenge URL, and `getAuthInfo()` all work unchanged. ```ts import { @@ -109,8 +107,8 @@ const mcpAuth = new MCPAuth({ }); const introspectionEndpoint = 'https://auth.example.com/oidc/token/introspection'; -// A confidential client registered with the authorization server (e.g. a machine-to-machine -// app) — most servers require one to introspect tokens issued to other clients +// Most servers require a confidential client (e.g. a machine-to-machine app) to +// introspect tokens issued to other clients const clientId = 'your-m2m-client-id'; const clientSecret = 'your-m2m-client-secret'; @@ -131,7 +129,7 @@ const introspectionVerifier: OAuthTokenVerifier = { } catch (error) { /* * A plain `Error`, not an `OAuthError`: the SDK answers 500. The token could not be - * verified, which is different from being invalid — a 401 would send a client with a + * verified, which is different from being invalid; a 401 would send a client with a * perfectly fine token into a pointless re-authorization. */ throw new Error('Failed to reach the token introspection endpoint.', { cause: error }); @@ -143,7 +141,7 @@ const introspectionVerifier: OAuthTokenVerifier = { const data = (await response.json()) as McpAuthInfo['claims']; - // The MCP spec still requires these checks — introspection does not exempt them + // The MCP spec still requires these checks; introspection does not exempt them if (data.active !== true) { throw new OAuthError(OAuthErrorCode.InvalidToken, 'The token is not active.'); } @@ -185,7 +183,7 @@ A few things to know: - **The endpoint**: some servers advertise it as `introspection_endpoint` in their metadata, others keep it off the public discovery document entirely (e.g. an internal admin API). Configure whatever yours is. - **The credentials**: most servers only let authenticated confidential clients introspect tokens issued to other clients; some deployments protect the endpoint at the network level instead. Check your server's policy. -- **The cost**: every request is an introspection round-trip. That is also the point — revoked tokens are rejected immediately. Add caching only if you accept the revocation delay. +- **The cost**: every request is an introspection round-trip. That is also the point: revoked tokens are rejected immediately. Add caching only if you accept the revocation delay. ## Join the discussion