{"_id":"@grantex/mcp-auth","_rev":"6-6eb3186702aa47a5272ed0eacb787d4e","name":"@grantex/mcp-auth","dist-tags":{"latest":"2.0.2"},"versions":{"0.1.0":{"name":"@grantex/mcp-auth","version":"0.1.0","keywords":["grantex","mcp","oauth","pkce","authorization-server"],"license":"Apache-2.0","_id":"@grantex/mcp-auth@0.1.0","maintainers":[{"name":"mishrasanjeev","email":"mishra.sanjeev@gmail.com"}],"homepage":"https://github.com/mishrasanjeev/grantex#readme","bugs":{"url":"https://github.com/mishrasanjeev/grantex/issues"},"dist":{"shasum":"8ee63d719c16c38a67e0a1b8ccc1fb033f4d2229","tarball":"https://registry.npmjs.org/@grantex/mcp-auth/-/mcp-auth-0.1.0.tgz","fileCount":41,"integrity":"sha512-LK4PdZBbhqb3gTmfbresR9u/vmMBn74b5b+Pj7+DeFz8CPtsjebJVfdPPXKvQr06kwoNwq4quGhzuOr/M/Qjhg==","signatures":[{"sig":"MEUCIQCqyBSLP0cORcCJzc7qmKqctwC+cS2cdK11u8T5rcyWCwIgaXYxnsiA0637a+decuYTncb2ma37d3e4jCRhhSIly/0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":35776},"type":"module","engines":{"node":">=18.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"c82c493d52f7bcdbe31a904d25bb22523e14191b","scripts":{"test":"vitest run","build":"tsc -p tsconfig.build.json","typecheck":"tsc --noEmit","test:watch":"vitest"},"_npmUser":{"name":"mishrasanjeev","email":"mishra.sanjeev@gmail.com"},"repository":{"url":"git+https://github.com/mishrasanjeev/grantex.git","type":"git"},"_npmVersion":"10.9.2","description":"OAuth 2.1 + PKCE authorization server for MCP servers, powered by Grantex","directories":{},"_nodeVersion":"22.14.0","dependencies":{"fastify":"^5.2.1","@grantex/sdk":"^0.1.8"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^1.3.1","typescript":"^5.3.3","@types/node":"^20.11.0"},"_npmOperationalInternal":{"tmp":"tmp/mcp-auth_0.1.0_1772379031432_0.3531803702647842","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@grantex/mcp-auth","version":"0.1.1","keywords":["grantex","mcp","oauth","pkce","authorization-server"],"license":"Apache-2.0","_id":"@grantex/mcp-auth@0.1.1","maintainers":[{"name":"mishrasanjeev","email":"mishra.sanjeev@gmail.com"}],"homepage":"https://grantex.dev/for/mcp","bugs":{"url":"https://github.com/mishrasanjeev/grantex/issues"},"dist":{"shasum":"209fe56d682324bb0f663d850f496322eeff13cb","tarball":"https://registry.npmjs.org/@grantex/mcp-auth/-/mcp-auth-0.1.1.tgz","fileCount":41,"integrity":"sha512-IpxKcV03fPe2lBH4vlKjvq8qHvXmnAY5kPJ74cpxyrOLaksoV9SedXsbr+TfwTEV+giMS8zwEbQE2XFfw3BABQ==","signatures":[{"sig":"MEUCICs3mSvrLiNwcoWmPaAb3GLYZqZQbuLxDpDmMcTJW/5CAiEA59y6JUm5wYmNGS1DE2h4dzLaIFsfDHyzkHNdnTlHhBw=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":36608},"type":"module","engines":{"node":">=18.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"b7c76534ccc54447a2ef16c297ae79a16cf74c7b","scripts":{"test":"vitest run","build":"tsc -p tsconfig.build.json","typecheck":"tsc --noEmit","test:watch":"vitest"},"_npmUser":{"name":"mishrasanjeev","email":"mishra.sanjeev@gmail.com"},"overrides":{"esbuild":">=0.25.0"},"repository":{"url":"git+https://github.com/mishrasanjeev/grantex.git","type":"git"},"_npmVersion":"10.9.2","description":"OAuth 2.1 + PKCE authorization server for MCP servers, powered by Grantex","directories":{},"_nodeVersion":"22.14.0","dependencies":{"fastify":"^5.2.1","@grantex/sdk":"^0.1.8","@fastify/rate-limit":"^10.2.1"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^4.0.18","typescript":"^5.3.3","@types/node":"^20.11.0"},"_npmOperationalInternal":{"tmp":"tmp/mcp-auth_0.1.1_1772637524154_0.09374857576741014","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@grantex/mcp-auth","version":"0.1.2","keywords":["grantex","mcp","oauth","pkce","authorization-server","model-context-protocol","ai-agents","oauth2.1","claude","cursor"],"license":"Apache-2.0","_id":"@grantex/mcp-auth@0.1.2","maintainers":[{"name":"mishrasanjeev","email":"mishra.sanjeev@gmail.com"}],"homepage":"https://grantex.dev/for/mcp","bugs":{"url":"https://github.com/mishrasanjeev/grantex/issues"},"dist":{"shasum":"7ed56b1bd84e25ed9d93df53ee3a661a34f4d38d","tarball":"https://registry.npmjs.org/@grantex/mcp-auth/-/mcp-auth-0.1.2.tgz","fileCount":41,"integrity":"sha512-54aZOuHqPr2Ctajmh4oL3CAWroTlSmml6DXCRlw2f6RceXDWapdekGgc2RPcuwRS4vz6ELR+UjypScWsi2Tckg==","signatures":[{"sig":"MEUCIQC6vhFAQwCJl+jMwsz94AmuykM3gN6OlVjEm5N7I438dwIgI6XVGNhyTQZGExOFky49ShEujVGH9+96kqOg7gOPsDc=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":36704},"type":"module","engines":{"node":">=18.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"76829b2f6111d18a490dce1766323a39552b15c7","scripts":{"test":"vitest run","build":"tsc -p tsconfig.build.json","typecheck":"tsc --noEmit","test:watch":"vitest"},"_npmUser":{"name":"mishrasanjeev","email":"mishra.sanjeev@gmail.com"},"overrides":{"esbuild":">=0.25.0"},"repository":{"url":"git+https://github.com/mishrasanjeev/grantex.git","type":"git"},"_npmVersion":"10.9.2","description":"OAuth 2.1 + PKCE authorization server for MCP servers, powered by Grantex","directories":{},"_nodeVersion":"22.14.0","dependencies":{"fastify":"^5.2.1","@grantex/sdk":"^0.1.8","@fastify/rate-limit":"^10.2.1"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^4.0.18","typescript":"^5.3.3","@types/node":"^20.11.0"},"_npmOperationalInternal":{"tmp":"tmp/mcp-auth_0.1.2_1774288365729_0.72449870981518","host":"s3://npm-registry-packages-npm-production"}},"2.0.0":{"name":"@grantex/mcp-auth","version":"2.0.0","keywords":["grantex","mcp","oauth","pkce","authorization-server","model-context-protocol","ai-agents","oauth2.1","claude","cursor"],"license":"Apache-2.0","_id":"@grantex/mcp-auth@2.0.0","maintainers":[{"name":"mishrasanjeev","email":"mishra.sanjeev@gmail.com"}],"homepage":"https://grantex.dev/for/mcp","bugs":{"url":"https://github.com/mishrasanjeev/grantex/issues"},"dist":{"shasum":"4b36c9c48831a5f69216834e859901529a3e3f6f","tarball":"https://registry.npmjs.org/@grantex/mcp-auth/-/mcp-auth-2.0.0.tgz","fileCount":58,"integrity":"sha512-HGAP7NpHaAit2RrelGsOWC22IRPxAFWe9OubewXgSZqz5AtwRSCGw6ViI8qvBave48VNdoPhc/Krfh0r/CK0Bw==","signatures":[{"sig":"MEUCIE7No58Q0OjLQaCNQAV9SgMVrWoBFAFHMijBlyMpw/YGAiEAzo1ntdj/uOwW4eaNLB3VuVLwbeP0k89SljV7AeTjvmE=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":98303},"type":"module","engines":{"node":">=18.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./hono":{"types":"./dist/middleware/hono.d.ts","import":"./dist/middleware/hono.js"},"./express":{"types":"./dist/middleware/express.d.ts","import":"./dist/middleware/express.js"}},"gitHead":"be175538c9f693f704d7070da8b8252c6d1afcfb","scripts":{"test":"vitest run","build":"tsc -p tsconfig.build.json","typecheck":"tsc --noEmit","test:watch":"vitest"},"_npmUser":{"name":"mishrasanjeev","email":"mishra.sanjeev@gmail.com"},"overrides":{"esbuild":">=0.25.0"},"repository":{"url":"git+https://github.com/mishrasanjeev/grantex.git","type":"git"},"_npmVersion":"10.9.2","description":"OAuth 2.1 + PKCE authorization server for MCP servers, powered by Grantex","directories":{},"_nodeVersion":"22.14.0","dependencies":{"jose":"^5.10.0","fastify":"^5.2.1","@grantex/sdk":"^0.1.8","@fastify/rate-limit":"^10.2.1"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^4.0.18","typescript":"^5.3.3","@types/node":"^20.11.0"},"_npmOperationalInternal":{"tmp":"tmp/mcp-auth_2.0.0_1775226027938_0.4861779440288829","host":"s3://npm-registry-packages-npm-production"}},"2.0.1":{"name":"@grantex/mcp-auth","version":"2.0.1","keywords":["grantex","mcp","oauth","pkce","authorization-server","model-context-protocol","ai-agents","oauth2.1","claude","cursor"],"license":"Apache-2.0","_id":"@grantex/mcp-auth@2.0.1","maintainers":[{"name":"mishrasanjeev","email":"mishra.sanjeev@gmail.com"}],"homepage":"https://grantex.dev/for/mcp","bugs":{"url":"https://github.com/mishrasanjeev/grantex/issues"},"dist":{"shasum":"22de10a882ba75ecc104b3d74e2ba313c78edc71","tarball":"https://registry.npmjs.org/@grantex/mcp-auth/-/mcp-auth-2.0.1.tgz","fileCount":58,"integrity":"sha512-hcwquHIZ1RlZJF1TnqMX6AD16nuaPCJsVi4Fc0oTYrGfTRGeSp0NjNYK4glL2+Eh2a0HrTDIJWokGBQe9kbqoQ==","signatures":[{"sig":"MEQCICQ9g7QR00XyQjBfoQxM8PQ7oTYyuIm2rG5yfmhPHZ8WAiBnssF6Ei4xNd1k5/WBg8QtixdZ8a/ccf+32tchfyV2yA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":98304},"type":"module","engines":{"node":">=18.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./hono":{"types":"./dist/middleware/hono.d.ts","import":"./dist/middleware/hono.js"},"./express":{"types":"./dist/middleware/express.d.ts","import":"./dist/middleware/express.js"}},"gitHead":"785ec2017c83bc37b73217df60a26d5ccc99deab","scripts":{"test":"vitest run","build":"tsc -p tsconfig.build.json","typecheck":"tsc --noEmit","test:watch":"vitest"},"_npmUser":{"name":"mishrasanjeev","email":"mishra.sanjeev@gmail.com"},"overrides":{"esbuild":">=0.25.0"},"repository":{"url":"git+https://github.com/mishrasanjeev/grantex.git","type":"git"},"_npmVersion":"10.9.2","description":"OAuth 2.1 + PKCE authorization server for MCP servers, powered by Grantex","directories":{},"_nodeVersion":"22.14.0","dependencies":{"jose":"^5.10.0","fastify":"^5.2.1","@grantex/sdk":">=0.1.8","@fastify/rate-limit":"^10.2.1"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^4.0.18","typescript":"^5.3.3","@types/node":"^20.11.0"},"_npmOperationalInternal":{"tmp":"tmp/mcp-auth_2.0.1_1775376574539_0.9277059062561201","host":"s3://npm-registry-packages-npm-production"}},"2.0.2":{"name":"@grantex/mcp-auth","version":"2.0.2","description":"OAuth 2.1 + PKCE authorization server for MCP servers, powered by Grantex","homepage":"https://grantex.dev/for/mcp","bugs":{"url":"https://github.com/mishrasanjeev/grantex/issues"},"type":"module","exports":{".":{"import":"./dist/index.js","types":"./dist/index.d.ts"},"./express":{"import":"./dist/middleware/express.js","types":"./dist/middleware/express.d.ts"},"./hono":{"import":"./dist/middleware/hono.js","types":"./dist/middleware/hono.d.ts"}},"scripts":{"build":"tsc -p tsconfig.build.json","typecheck":"tsc --noEmit","test":"vitest run","test:watch":"vitest"},"dependencies":{"@fastify/rate-limit":"^11.0.0","@grantex/sdk":">=0.1.8","fastify":"^5.2.1","jose":"^6.2.3"},"devDependencies":{"@types/node":"^26.0.0","typescript":"^6.0.3","vitest":"^4.0.18"},"overrides":{"esbuild":">=0.25.0"},"engines":{"node":">=18.0.0"},"keywords":["grantex","mcp","oauth","pkce","authorization-server","model-context-protocol","ai-agents","oauth2.1","claude","cursor"],"license":"Apache-2.0","repository":{"type":"git","url":"git+https://github.com/mishrasanjeev/grantex.git"},"gitHead":"a99bb501d0817f2c56e1f5451226040eca5e6c97","_id":"@grantex/mcp-auth@2.0.2","_nodeVersion":"24.15.0","_npmVersion":"11.12.1","dist":{"integrity":"sha512-Xc/tUIoD+H3TzH/HsubJE/Lz1PZ/CI0Dr5RJBSURYbjh3cMK17dRoiV/UCgKlcVBKxV0e5QdimFW8m8939iTsQ==","shasum":"33524cd0efad865148f90baf77239d522f5644aa","tarball":"https://registry.npmjs.org/@grantex/mcp-auth/-/mcp-auth-2.0.2.tgz","fileCount":58,"unpackedSize":98301,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIAjT+KWl+LoiRD8bzHvhvXOy+JAXbwJXJ5uNAe9PUf+aAiB70t6gdwZtzPOrp00FddNfTbP2csK2E/JaznpShRLEbA=="}]},"_npmUser":{"name":"mishrasanjeev","email":"mishra.sanjeev@gmail.com"},"directories":{},"maintainers":[{"name":"mishrasanjeev","email":"mishra.sanjeev@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/mcp-auth_2.0.2_1782397717202_0.3804656513450124"},"_hasShrinkwrap":false}},"time":{"created":"2026-03-01T15:30:31.344Z","modified":"2026-06-25T14:28:37.469Z","0.1.0":"2026-03-01T15:30:31.580Z","0.1.1":"2026-03-04T15:18:44.345Z","0.1.2":"2026-03-23T17:52:45.869Z","2.0.0":"2026-04-03T14:20:28.095Z","2.0.1":"2026-04-05T08:09:34.685Z","2.0.2":"2026-06-25T14:28:37.324Z"},"bugs":{"url":"https://github.com/mishrasanjeev/grantex/issues"},"license":"Apache-2.0","homepage":"https://grantex.dev/for/mcp","keywords":["grantex","mcp","oauth","pkce","authorization-server","model-context-protocol","ai-agents","oauth2.1","claude","cursor"],"repository":{"type":"git","url":"git+https://github.com/mishrasanjeev/grantex.git"},"description":"OAuth 2.1 + PKCE authorization server for MCP servers, powered by Grantex","maintainers":[{"name":"mishrasanjeev","email":"mishra.sanjeev@gmail.com"}],"readme":"# @grantex/mcp-auth\r\n\r\n[![npm version](https://img.shields.io/npm/v/@grantex/mcp-auth)](https://www.npmjs.com/package/@grantex/mcp-auth)\r\n[![License](https://img.shields.io/badge/license-Apache--2.0-blue)](https://github.com/mishrasanjeev/grantex/blob/main/LICENSE)\r\n[![npm downloads](https://img.shields.io/npm/dm/@grantex/mcp-auth)](https://www.npmjs.com/package/@grantex/mcp-auth)\r\n\r\n**OAuth 2.1 + PKCE authorization server for MCP servers, powered by Grantex.**\r\n\r\nTurn any [Model Context Protocol](https://modelcontextprotocol.io/) server into a fully-compliant OAuth 2.1 authorization server in under 10 lines of code. Built on the Grantex delegated authorization protocol, `@grantex/mcp-auth` handles token issuance, introspection, revocation, Dynamic Client Registration (DCR), and PKCE -- so you can focus on building tools, not auth infrastructure.\r\n\r\n## Why @grantex/mcp-auth?\r\n\r\nThe MCP specification mandates OAuth 2.1 for transport-level auth. Implementing it correctly is hard:\r\n\r\n- **PKCE S256** is mandatory (no `plain`, no implicit flow)\r\n- **Dynamic Client Registration** (RFC 7591) for zero-config MCP clients\r\n- **Token introspection** (RFC 7662) for resource servers to validate tokens\r\n- **Token revocation** (RFC 7009) for secure logout\r\n- **Rate limiting** on all sensitive endpoints\r\n- **Grantex integration** for delegated, auditable, scope-controlled authorization\r\n\r\n`@grantex/mcp-auth` handles all of this out of the box, with a single function call.\r\n\r\n## Installation\r\n\r\n```bash\r\nnpm install @grantex/mcp-auth\r\n```\r\n\r\n## Quick Start\r\n\r\n### 1. Create the server\r\n\r\n```typescript\r\nimport { Grantex } from '@grantex/sdk';\r\nimport { createMcpAuthServer } from '@grantex/mcp-auth';\r\n\r\nconst grantex = new Grantex({\r\n  baseUrl: 'https://grantex-auth-dd4mtrt2gq-uc.a.run.app',\r\n  apiKey: process.env.GRANTEX_API_KEY!,\r\n});\r\n\r\nconst authServer = await createMcpAuthServer({\r\n  grantex,\r\n  agentId: 'ag_your_mcp_server',\r\n  scopes: ['tools:read', 'tools:execute', 'resources:read'],\r\n  issuer: 'https://your-mcp-server.example.com',\r\n});\r\n```\r\n\r\n### 2. Start listening\r\n\r\n```typescript\r\nawait authServer.listen({ port: 3001 });\r\nconsole.log('MCP Auth Server running on http://localhost:3001');\r\n```\r\n\r\n### 3. Protect your MCP server routes\r\n\r\n```typescript\r\nimport { requireMcpAuth } from '@grantex/mcp-auth/express';\r\n\r\napp.use('/mcp', requireMcpAuth({\r\n  issuer: 'https://your-mcp-server.example.com',\r\n  scopes: ['tools:execute'],\r\n}));\r\n```\r\n\r\nThat's it. MCP clients can now discover your auth server via `/.well-known/oauth-authorization-server`, register dynamically, and obtain tokens.\r\n\r\n## Endpoints\r\n\r\n`createMcpAuthServer` registers the following endpoints on the Fastify instance:\r\n\r\n| Endpoint | Method | RFC | Description |\r\n|----------|--------|-----|-------------|\r\n| `/.well-known/oauth-authorization-server` | GET | RFC 8414 | Authorization server metadata discovery |\r\n| `/register` | POST | RFC 7591 | Dynamic Client Registration |\r\n| `/authorize` | GET | OAuth 2.1 | Authorization endpoint (PKCE required) |\r\n| `/token` | POST | OAuth 2.1 | Token endpoint (authorization_code, refresh_token) |\r\n| `/introspect` | POST | RFC 7662 | Token introspection |\r\n| `/revoke` | POST | RFC 7009 | Token revocation |\r\n\r\n## API Reference\r\n\r\n### `createMcpAuthServer(config)`\r\n\r\nCreates and returns a Fastify instance with all OAuth 2.1 endpoints registered.\r\n\r\n```typescript\r\nimport { createMcpAuthServer } from '@grantex/mcp-auth';\r\n\r\nconst server = await createMcpAuthServer(config);\r\n```\r\n\r\n#### `McpAuthConfig`\r\n\r\n| Property | Type | Required | Default | Description |\r\n|----------|------|----------|---------|-------------|\r\n| `grantex` | `Grantex` | Yes | - | Grantex SDK client instance |\r\n| `agentId` | `string` | Yes | - | Agent ID for Grantex authorization |\r\n| `scopes` | `string[]` | Yes | - | Scopes to request from Grantex |\r\n| `issuer` | `string` | Yes | - | Base URL for this auth server (used in metadata) |\r\n| `allowedRedirectUris` | `string[]` | No | `[]` | Allowed redirect URIs (empty = all allowed) |\r\n| `allowedResources` | `string[]` | No | `[]` | Allowed resource indicators (RFC 8707) |\r\n| `clientStore` | `ClientStore` | No | `InMemoryClientStore` | Custom client registration store |\r\n| `codeExpirationSeconds` | `number` | No | `600` | Authorization code TTL in seconds |\r\n| `consentUi` | `object` | No | - | Consent UI customization (appName, appLogo, privacyUrl, termsUrl) |\r\n| `hooks` | `object` | No | - | Lifecycle hooks (onTokenIssued, onRevocation) |\r\n\r\n#### Consent UI\r\n\r\nCustomize the consent page shown to users:\r\n\r\n```typescript\r\nconst server = await createMcpAuthServer({\r\n  // ...required fields...\r\n  consentUi: {\r\n    appName: 'My MCP Server',\r\n    appLogo: 'https://example.com/logo.png',\r\n    privacyUrl: 'https://example.com/privacy',\r\n    termsUrl: 'https://example.com/terms',\r\n  },\r\n});\r\n```\r\n\r\n#### Lifecycle Hooks\r\n\r\nReact to authorization events:\r\n\r\n```typescript\r\nconst server = await createMcpAuthServer({\r\n  // ...required fields...\r\n  hooks: {\r\n    onTokenIssued: async (event) => {\r\n      console.log(`Token issued for client ${event.clientId}`);\r\n      console.log(`Scopes: ${event.scopes.join(', ')}`);\r\n      console.log(`Grant ID: ${event.grantId}`);\r\n      // Send to your analytics, audit log, etc.\r\n    },\r\n    onRevocation: async (jti) => {\r\n      console.log(`Token ${jti} was revoked`);\r\n      // Invalidate cached sessions, notify downstream, etc.\r\n    },\r\n  },\r\n});\r\n```\r\n\r\n### Custom Client Store\r\n\r\nBy default, client registrations are stored in memory. For production, implement the `ClientStore` interface backed by your database:\r\n\r\n```typescript\r\nimport type { ClientStore, ClientRegistration } from '@grantex/mcp-auth';\r\n\r\nclass PostgresClientStore implements ClientStore {\r\n  async get(clientId: string): Promise<ClientRegistration | undefined> {\r\n    const row = await db.query('SELECT * FROM oauth_clients WHERE id = $1', [clientId]);\r\n    return row ?? undefined;\r\n  }\r\n\r\n  async set(clientId: string, reg: ClientRegistration): Promise<void> {\r\n    await db.query(\r\n      'INSERT INTO oauth_clients (id, data) VALUES ($1, $2) ON CONFLICT (id) DO UPDATE SET data = $2',\r\n      [clientId, JSON.stringify(reg)],\r\n    );\r\n  }\r\n\r\n  async delete(clientId: string): Promise<boolean> {\r\n    const result = await db.query('DELETE FROM oauth_clients WHERE id = $1', [clientId]);\r\n    return result.rowCount > 0;\r\n  }\r\n}\r\n\r\nconst server = await createMcpAuthServer({\r\n  // ...\r\n  clientStore: new PostgresClientStore(),\r\n});\r\n```\r\n\r\n## Express.js Middleware\r\n\r\nProtect your Express routes with JWT validation:\r\n\r\n```typescript\r\nimport express from 'express';\r\nimport { requireMcpAuth } from '@grantex/mcp-auth/express';\r\nimport type { McpAuthRequest } from '@grantex/mcp-auth/express';\r\n\r\nconst app = express();\r\n\r\n// Protect all /mcp routes\r\napp.use('/mcp', requireMcpAuth({\r\n  issuer: 'https://your-mcp-server.example.com',\r\n  scopes: ['tools:execute'],\r\n}));\r\n\r\n// Access the decoded grant in your handlers\r\napp.post('/mcp/tools/call', (req: McpAuthRequest, res) => {\r\n  const grant = req.mcpGrant!;\r\n  console.log(`Agent: ${grant.agentDid}`);\r\n  console.log(`Scopes: ${grant.scopes.join(', ')}`);\r\n  console.log(`Subject: ${grant.sub}`);\r\n  res.json({ result: 'tool executed' });\r\n});\r\n\r\napp.listen(3000);\r\n```\r\n\r\n### `requireMcpAuth(options)` (Express)\r\n\r\n| Option | Type | Required | Default | Description |\r\n|--------|------|----------|---------|-------------|\r\n| `issuer` | `string` | Yes | - | Issuer URL (JWKS fetched from `{issuer}/.well-known/jwks.json`) |\r\n| `scopes` | `string[]` | No | `[]` | Required scopes (all must be present) |\r\n| `algorithms` | `string[]` | No | `['RS256', 'ES256', 'PS256', 'EdDSA']` | Allowed JWT algorithms |\r\n\r\n### `McpGrant` (decoded token claims)\r\n\r\n| Property | Type | Description |\r\n|----------|------|-------------|\r\n| `sub` | `string` | Subject (principal ID) |\r\n| `iss` | `string` | Issuer |\r\n| `jti` | `string` | Token ID |\r\n| `scopes` | `string[]` | Granted scopes |\r\n| `agentDid` | `string?` | Agent DID |\r\n| `developerId` | `string?` | Developer ID |\r\n| `grantId` | `string?` | Grant ID |\r\n| `delegationDepth` | `number?` | Delegation depth (0 = root) |\r\n| `exp` | `number` | Expiry (Unix timestamp) |\r\n| `iat` | `number` | Issued at (Unix timestamp) |\r\n| `raw` | `JWTPayload` | All raw JWT claims |\r\n\r\n## Hono Middleware\r\n\r\nSame protection for Hono applications:\r\n\r\n```typescript\r\nimport { Hono } from 'hono';\r\nimport { requireMcpAuth } from '@grantex/mcp-auth/hono';\r\n\r\nconst app = new Hono();\r\n\r\n// Protect routes\r\napp.use('/mcp/*', requireMcpAuth({\r\n  issuer: 'https://your-mcp-server.example.com',\r\n  scopes: ['tools:execute'],\r\n}));\r\n\r\n// Access decoded grant via context\r\napp.post('/mcp/tools/call', (c) => {\r\n  const grant = c.get('mcpGrant');\r\n  return c.json({\r\n    agent: grant.agentDid,\r\n    scopes: grant.scopes,\r\n  });\r\n});\r\n\r\nexport default app;\r\n```\r\n\r\n## Token Introspection (RFC 7662)\r\n\r\nResource servers can validate tokens by calling the introspection endpoint:\r\n\r\n```bash\r\ncurl -X POST https://your-mcp-server.example.com/introspect \\\r\n  -H \"Content-Type: application/json\" \\\r\n  -d '{\"token\": \"eyJhbGciOiJSUzI1NiIs...\"}'\r\n```\r\n\r\nResponse for a valid token:\r\n\r\n```json\r\n{\r\n  \"active\": true,\r\n  \"scope\": \"tools:read tools:execute\",\r\n  \"sub\": \"user_abc\",\r\n  \"exp\": 1743670800,\r\n  \"iat\": 1743667200,\r\n  \"jti\": \"grnt_01HXYZ\",\r\n  \"token_type\": \"bearer\",\r\n  \"grantex_agent_did\": \"did:grantex:ag_01HXYZ\",\r\n  \"grantex_delegation_depth\": 0,\r\n  \"grantex_grant_id\": \"grnt_01HXYZ\"\r\n}\r\n```\r\n\r\nResponse for an invalid/expired token:\r\n\r\n```json\r\n{\r\n  \"active\": false\r\n}\r\n```\r\n\r\n### Client Authentication\r\n\r\nIntrospection optionally accepts Basic auth for client identification:\r\n\r\n```bash\r\ncurl -X POST https://your-mcp-server.example.com/introspect \\\r\n  -u \"client_id:client_secret\" \\\r\n  -H \"Content-Type: application/json\" \\\r\n  -d '{\"token\": \"eyJhbGciOiJSUzI1NiIs...\"}'\r\n```\r\n\r\n## Token Revocation (RFC 7009)\r\n\r\nRevoke tokens when a user logs out or an agent is deauthorized:\r\n\r\n```bash\r\ncurl -X POST https://your-mcp-server.example.com/revoke \\\r\n  -u \"client_id:client_secret\" \\\r\n  -H \"Content-Type: application/json\" \\\r\n  -d '{\"token\": \"eyJhbGciOiJSUzI1NiIs...\"}'\r\n```\r\n\r\nPer RFC 7009, the endpoint always returns `200 OK`, even if the token was already revoked or unknown.\r\n\r\n## Managed vs Self-Hosted\r\n\r\n| Feature | Managed (Grantex Cloud) | Self-Hosted |\r\n|---------|------------------------|-------------|\r\n| **Setup** | `createMcpAuthServer({ grantex, ... })` | Same API, your infrastructure |\r\n| **Client Store** | In-memory (stateless, horizontal scale) | Bring your own (Postgres, Redis, etc.) |\r\n| **JWKS** | Hosted by Grantex | Your JWKS endpoint |\r\n| **Token Signing** | Grantex signs tokens | Grantex signs tokens (delegated) |\r\n| **Rate Limiting** | Built-in per-endpoint limits | Built-in, configurable |\r\n| **Consent UI** | Grantex-hosted consent page | Custom consent page via `consentUi` config |\r\n| **Audit Trail** | Full audit via Grantex events | Full audit via Grantex events |\r\n| **Uptime SLA** | 99.9% | Your responsibility |\r\n| **Compliance** | SOC 2, GDPR ready | Your responsibility |\r\n\r\n### Managed Mode (Recommended)\r\n\r\nUse the Grantex Cloud auth service. Zero infrastructure to manage:\r\n\r\n```typescript\r\nconst server = await createMcpAuthServer({\r\n  grantex: new Grantex({\r\n    baseUrl: 'https://grantex-auth-dd4mtrt2gq-uc.a.run.app',\r\n    apiKey: process.env.GRANTEX_API_KEY!,\r\n  }),\r\n  agentId: 'ag_your_server',\r\n  scopes: ['tools:read', 'tools:execute'],\r\n  issuer: 'https://your-domain.example.com',\r\n});\r\n```\r\n\r\n### Self-Hosted Mode\r\n\r\nRun your own Grantex auth service and point the SDK at it:\r\n\r\n```typescript\r\nconst server = await createMcpAuthServer({\r\n  grantex: new Grantex({\r\n    baseUrl: 'https://auth.your-company.internal',\r\n    apiKey: process.env.GRANTEX_API_KEY!,\r\n  }),\r\n  agentId: 'ag_internal_server',\r\n  scopes: ['internal:read', 'internal:write'],\r\n  issuer: 'https://auth.your-company.internal',\r\n  clientStore: new PostgresClientStore(),  // Persistent storage\r\n});\r\n```\r\n\r\n## MCP Server Certification\r\n\r\nGrantex offers a certification program for MCP servers that implement OAuth 2.1 correctly:\r\n\r\n### Bronze\r\n\r\n- OAuth 2.1 + PKCE S256 for all flows\r\n- Dynamic Client Registration (RFC 7591)\r\n- Server metadata discovery (RFC 8414)\r\n- Rate limiting on token and authorize endpoints\r\n\r\n### Silver\r\n\r\nAll Bronze requirements, plus:\r\n\r\n- Token introspection (RFC 7662)\r\n- Token revocation (RFC 7009)\r\n- Consent UI customization\r\n- Lifecycle hooks for audit logging\r\n\r\n### Gold\r\n\r\nAll Silver requirements, plus:\r\n\r\n- Custom client store (persistent, production-grade)\r\n- Resource indicators (RFC 8707)\r\n- Delegation support (Grantex SPEC Section 9)\r\n- Budget enforcement\r\n- Full Grantex conformance suite pass\r\n\r\nUsing `@grantex/mcp-auth` with all features enabled gets you to Gold certification automatically.\r\n\r\n## Security Considerations\r\n\r\n`@grantex/mcp-auth` enforces OAuth 2.1 security requirements:\r\n\r\n- **PKCE S256 is mandatory.** The `plain` method and implicit grant are rejected.\r\n- **No password grant.** The `password` grant type is not supported.\r\n- **No implicit grant.** Only `response_type=code` is accepted.\r\n- **Authorization codes are single-use.** Replayed codes are rejected.\r\n- **HS256 rejected.** Only asymmetric algorithms (RS256, ES256, PS256, EdDSA) are accepted for token verification.\r\n- **Rate limiting** is applied to all endpoints (configurable per-endpoint).\r\n- **Client secrets** are generated using `crypto.randomBytes(32)`.\r\n- **JWKS verification** uses the `jose` library with remote key set fetching and caching.\r\n\r\n### Algorithm Policy\r\n\r\nThe introspection and middleware endpoints only accept tokens signed with:\r\n\r\n- `RS256` (RSA PKCS#1 v1.5)\r\n- `ES256` (ECDSA P-256)\r\n- `PS256` (RSA-PSS)\r\n- `EdDSA` (Ed25519)\r\n\r\nSymmetric algorithms (`HS256`, `HS384`, `HS512`) are explicitly rejected.\r\n\r\n## Discovery\r\n\r\nMCP clients discover your auth server via the well-known metadata endpoint:\r\n\r\n```bash\r\ncurl https://your-mcp-server.example.com/.well-known/oauth-authorization-server\r\n```\r\n\r\n```json\r\n{\r\n  \"issuer\": \"https://your-mcp-server.example.com\",\r\n  \"authorization_endpoint\": \"https://your-mcp-server.example.com/authorize\",\r\n  \"token_endpoint\": \"https://your-mcp-server.example.com/token\",\r\n  \"registration_endpoint\": \"https://your-mcp-server.example.com/register\",\r\n  \"introspection_endpoint\": \"https://your-mcp-server.example.com/introspect\",\r\n  \"revocation_endpoint\": \"https://your-mcp-server.example.com/revoke\",\r\n  \"response_types_supported\": [\"code\"],\r\n  \"grant_types_supported\": [\"authorization_code\", \"refresh_token\"],\r\n  \"code_challenge_methods_supported\": [\"S256\"],\r\n  \"token_endpoint_auth_methods_supported\": [\"client_secret_post\", \"client_secret_basic\", \"none\"],\r\n  \"introspection_endpoint_auth_methods_supported\": [\"client_secret_basic\", \"none\"],\r\n  \"revocation_endpoint_auth_methods_supported\": [\"client_secret_basic\", \"client_secret_post\"],\r\n  \"scopes_supported\": [\"tools:read\", \"tools:execute\", \"resources:read\"],\r\n  \"grantex_extensions\": {\r\n    \"consent_ui\": \"https://your-mcp-server.example.com/consent\",\r\n    \"audit_stream\": \"https://your-mcp-server.example.com/events/stream\"\r\n  }\r\n}\r\n```\r\n\r\n## Full Example: MCP Server with Auth\r\n\r\n```typescript\r\nimport { Grantex } from '@grantex/sdk';\r\nimport { createMcpAuthServer } from '@grantex/mcp-auth';\r\nimport express from 'express';\r\nimport { requireMcpAuth } from '@grantex/mcp-auth/express';\r\nimport type { McpAuthRequest } from '@grantex/mcp-auth/express';\r\n\r\n// 1. Create Grantex client\r\nconst grantex = new Grantex({\r\n  baseUrl: 'https://grantex-auth-dd4mtrt2gq-uc.a.run.app',\r\n  apiKey: process.env.GRANTEX_API_KEY!,\r\n});\r\n\r\n// 2. Start OAuth 2.1 auth server\r\nconst authServer = await createMcpAuthServer({\r\n  grantex,\r\n  agentId: 'ag_calendar_mcp',\r\n  scopes: ['calendar:read', 'calendar:write'],\r\n  issuer: 'https://calendar-mcp.example.com',\r\n  hooks: {\r\n    onTokenIssued: async (event) => {\r\n      await grantex.audit.log({\r\n        action: 'mcp.token.issued',\r\n        agentId: event.agentDid,\r\n        grantId: event.grantId,\r\n        scopes: event.scopes,\r\n      });\r\n    },\r\n    onRevocation: async (jti) => {\r\n      await grantex.audit.log({\r\n        action: 'mcp.token.revoked',\r\n        tokenId: jti,\r\n      });\r\n    },\r\n  },\r\n});\r\n\r\nawait authServer.listen({ port: 3001 });\r\n\r\n// 3. Create MCP tool server with auth middleware\r\nconst app = express();\r\n\r\napp.use('/mcp', requireMcpAuth({\r\n  issuer: 'https://calendar-mcp.example.com',\r\n  scopes: ['calendar:read'],\r\n}));\r\n\r\napp.post('/mcp/tools/list', (req: McpAuthRequest, res) => {\r\n  res.json({\r\n    tools: [\r\n      { name: 'get_events', description: 'Get calendar events' },\r\n      { name: 'create_event', description: 'Create a calendar event' },\r\n    ],\r\n  });\r\n});\r\n\r\napp.post('/mcp/tools/call', requireMcpAuth({\r\n  issuer: 'https://calendar-mcp.example.com',\r\n  scopes: ['calendar:write'],\r\n}), (req: McpAuthRequest, res) => {\r\n  const grant = req.mcpGrant!;\r\n  // grant.agentDid, grant.scopes, grant.sub are available\r\n  res.json({ result: 'Event created' });\r\n});\r\n\r\napp.listen(3000, () => {\r\n  console.log('MCP Tool Server on :3000, Auth Server on :3001');\r\n});\r\n```\r\n\r\n## Troubleshooting\r\n\r\n### \"JWKS fetch failed\"\r\n\r\nThe middleware fetches JWKS from `{issuer}/.well-known/jwks.json`. Ensure:\r\n\r\n1. Your issuer URL is correct and accessible\r\n2. The JWKS endpoint returns valid JSON with a `keys` array\r\n3. Network connectivity allows outbound HTTPS from your server\r\n\r\n### \"Token verification failed\" / `active: false`\r\n\r\nCommon causes:\r\n\r\n- **Token expired** -- check the `exp` claim\r\n- **Wrong issuer** -- the token's `iss` claim must match\r\n- **Algorithm mismatch** -- only RS256, ES256, PS256, EdDSA are accepted\r\n- **Key rotation** -- JWKS is cached; restart or wait for cache refresh\r\n\r\n### \"Invalid client\" on introspect/revoke\r\n\r\nClient authentication uses Basic auth (`Authorization: Basic base64(client_id:client_secret)`) or body parameters. Verify your client credentials match what was returned by `/register`.\r\n\r\n### Rate limiting (429)\r\n\r\nDefault limits per endpoint:\r\n\r\n| Endpoint | Max requests | Window |\r\n|----------|-------------|--------|\r\n| `/authorize` | 10 | 1 minute |\r\n| `/token` | 20 | 1 minute |\r\n| `/introspect` | 30 | 1 minute |\r\n| `/revoke` | 20 | 1 minute |\r\n| All others | 100 | 1 minute |\r\n\r\n## Related Packages\r\n\r\n| Package | Description |\r\n|---------|-------------|\r\n| [`@grantex/sdk`](https://www.npmjs.com/package/@grantex/sdk) | Core TypeScript SDK |\r\n| [`@grantex/express`](https://www.npmjs.com/package/@grantex/express) | Express.js middleware for Grantex |\r\n| [`@grantex/gateway`](https://www.npmjs.com/package/@grantex/gateway) | Reverse-proxy gateway with YAML config |\r\n| [`@grantex/mcp`](https://www.npmjs.com/package/@grantex/mcp) | MCP server with 13 Grantex tools |\r\n| [`@grantex/cli`](https://www.npmjs.com/package/@grantex/cli) | CLI for managing grants, tokens, and agents |\r\n| [`@grantex/conformance`](https://www.npmjs.com/package/@grantex/conformance) | Protocol conformance test suite |\r\n\r\n## License\r\n\r\nApache-2.0\r\n","readmeFilename":"README.md"}