
Security News
GPT-6 Astra Attempts Supply Chain Attacks Against Open Source Maintainers in Testing
GPT-6 Astra hits 100% on ExploitBench and finds zero-days autonomously, while independent tests reveal scope violations and monitoring gaps.
@grantex/mcp-auth
Advanced tools
OAuth 2.1 + PKCE authorization server for MCP servers, powered by Grantex
OAuth 2.1 + PKCE authorization server for MCP servers, powered by Grantex.
Turn any Model Context Protocol 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.
The MCP specification mandates OAuth 2.1 for transport-level auth. Implementing it correctly is hard:
plain, no implicit flow)@grantex/mcp-auth handles all of this out of the box, with a single function call.
npm install @grantex/mcp-auth
import { Grantex } from '@grantex/sdk';
import { createMcpAuthServer } from '@grantex/mcp-auth';
const grantex = new Grantex({
baseUrl: 'https://grantex-auth-dd4mtrt2gq-uc.a.run.app',
apiKey: process.env.GRANTEX_API_KEY!,
});
const authServer = await createMcpAuthServer({
grantex,
agentId: 'ag_your_mcp_server',
scopes: ['tools:read', 'tools:execute', 'resources:read'],
issuer: 'https://your-mcp-server.example.com',
});
await authServer.listen({ port: 3001 });
console.log('MCP Auth Server running on http://localhost:3001');
import { requireMcpAuth } from '@grantex/mcp-auth/express';
app.use('/mcp', requireMcpAuth({
issuer: 'https://your-mcp-server.example.com',
scopes: ['tools:execute'],
}));
That's it. MCP clients can now discover your auth server via /.well-known/oauth-authorization-server, register dynamically, and obtain tokens.
createMcpAuthServer registers the following endpoints on the Fastify instance:
| Endpoint | Method | RFC | Description |
|---|---|---|---|
/.well-known/oauth-authorization-server | GET | RFC 8414 | Authorization server metadata discovery |
/register | POST | RFC 7591 | Dynamic Client Registration |
/authorize | GET | OAuth 2.1 | Authorization endpoint (PKCE required) |
/token | POST | OAuth 2.1 | Token endpoint (authorization_code, refresh_token) |
/introspect | POST | RFC 7662 | Token introspection |
/revoke | POST | RFC 7009 | Token revocation |
createMcpAuthServer(config)Creates and returns a Fastify instance with all OAuth 2.1 endpoints registered.
import { createMcpAuthServer } from '@grantex/mcp-auth';
const server = await createMcpAuthServer(config);
McpAuthConfig| Property | Type | Required | Default | Description |
|---|---|---|---|---|
grantex | Grantex | Yes | - | Grantex SDK client instance |
agentId | string | Yes | - | Agent ID for Grantex authorization |
scopes | string[] | Yes | - | Scopes to request from Grantex |
issuer | string | Yes | - | Base URL for this auth server (used in metadata) |
allowedRedirectUris | string[] | No | [] | Allowed redirect URIs (empty = all allowed) |
allowedResources | string[] | No | [] | Allowed resource indicators (RFC 8707) |
clientStore | ClientStore | No | InMemoryClientStore | Custom client registration store |
codeExpirationSeconds | number | No | 600 | Authorization code TTL in seconds |
consentUi | object | No | - | Consent UI customization (appName, appLogo, privacyUrl, termsUrl) |
hooks | object | No | - | Lifecycle hooks (onTokenIssued, onRevocation) |
Customize the consent page shown to users:
const server = await createMcpAuthServer({
// ...required fields...
consentUi: {
appName: 'My MCP Server',
appLogo: 'https://example.com/logo.png',
privacyUrl: 'https://example.com/privacy',
termsUrl: 'https://example.com/terms',
},
});
React to authorization events:
const server = await createMcpAuthServer({
// ...required fields...
hooks: {
onTokenIssued: async (event) => {
console.log(`Token issued for client ${event.clientId}`);
console.log(`Scopes: ${event.scopes.join(', ')}`);
console.log(`Grant ID: ${event.grantId}`);
// Send to your analytics, audit log, etc.
},
onRevocation: async (jti) => {
console.log(`Token ${jti} was revoked`);
// Invalidate cached sessions, notify downstream, etc.
},
},
});
By default, client registrations are stored in memory. For production, implement the ClientStore interface backed by your database:
import type { ClientStore, ClientRegistration } from '@grantex/mcp-auth';
class PostgresClientStore implements ClientStore {
async get(clientId: string): Promise<ClientRegistration | undefined> {
const row = await db.query('SELECT * FROM oauth_clients WHERE id = $1', [clientId]);
return row ?? undefined;
}
async set(clientId: string, reg: ClientRegistration): Promise<void> {
await db.query(
'INSERT INTO oauth_clients (id, data) VALUES ($1, $2) ON CONFLICT (id) DO UPDATE SET data = $2',
[clientId, JSON.stringify(reg)],
);
}
async delete(clientId: string): Promise<boolean> {
const result = await db.query('DELETE FROM oauth_clients WHERE id = $1', [clientId]);
return result.rowCount > 0;
}
}
const server = await createMcpAuthServer({
// ...
clientStore: new PostgresClientStore(),
});
Protect your Express routes with JWT validation:
import express from 'express';
import { requireMcpAuth } from '@grantex/mcp-auth/express';
import type { McpAuthRequest } from '@grantex/mcp-auth/express';
const app = express();
// Protect all /mcp routes
app.use('/mcp', requireMcpAuth({
issuer: 'https://your-mcp-server.example.com',
scopes: ['tools:execute'],
}));
// Access the decoded grant in your handlers
app.post('/mcp/tools/call', (req: McpAuthRequest, res) => {
const grant = req.mcpGrant!;
console.log(`Agent: ${grant.agentDid}`);
console.log(`Scopes: ${grant.scopes.join(', ')}`);
console.log(`Subject: ${grant.sub}`);
res.json({ result: 'tool executed' });
});
app.listen(3000);
requireMcpAuth(options) (Express)| Option | Type | Required | Default | Description |
|---|---|---|---|---|
issuer | string | Yes | - | Issuer URL (JWKS fetched from {issuer}/.well-known/jwks.json) |
scopes | string[] | No | [] | Required scopes (all must be present) |
algorithms | string[] | No | ['RS256', 'ES256', 'PS256', 'EdDSA'] | Allowed JWT algorithms |
McpGrant (decoded token claims)| Property | Type | Description |
|---|---|---|
sub | string | Subject (principal ID) |
iss | string | Issuer |
jti | string | Token ID |
scopes | string[] | Granted scopes |
agentDid | string? | Agent DID |
developerId | string? | Developer ID |
grantId | string? | Grant ID |
delegationDepth | number? | Delegation depth (0 = root) |
exp | number | Expiry (Unix timestamp) |
iat | number | Issued at (Unix timestamp) |
raw | JWTPayload | All raw JWT claims |
Same protection for Hono applications:
import { Hono } from 'hono';
import { requireMcpAuth } from '@grantex/mcp-auth/hono';
const app = new Hono();
// Protect routes
app.use('/mcp/*', requireMcpAuth({
issuer: 'https://your-mcp-server.example.com',
scopes: ['tools:execute'],
}));
// Access decoded grant via context
app.post('/mcp/tools/call', (c) => {
const grant = c.get('mcpGrant');
return c.json({
agent: grant.agentDid,
scopes: grant.scopes,
});
});
export default app;
Resource servers can validate tokens by calling the introspection endpoint:
curl -X POST https://your-mcp-server.example.com/introspect \
-H "Content-Type: application/json" \
-d '{"token": "eyJhbGciOiJSUzI1NiIs..."}'
Response for a valid token:
{
"active": true,
"scope": "tools:read tools:execute",
"sub": "user_abc",
"exp": 1743670800,
"iat": 1743667200,
"jti": "grnt_01HXYZ",
"token_type": "bearer",
"grantex_agent_did": "did:grantex:ag_01HXYZ",
"grantex_delegation_depth": 0,
"grantex_grant_id": "grnt_01HXYZ"
}
Response for an invalid/expired token:
{
"active": false
}
Introspection optionally accepts Basic auth for client identification:
curl -X POST https://your-mcp-server.example.com/introspect \
-u "client_id:client_secret" \
-H "Content-Type: application/json" \
-d '{"token": "eyJhbGciOiJSUzI1NiIs..."}'
Revoke tokens when a user logs out or an agent is deauthorized:
curl -X POST https://your-mcp-server.example.com/revoke \
-u "client_id:client_secret" \
-H "Content-Type: application/json" \
-d '{"token": "eyJhbGciOiJSUzI1NiIs..."}'
Per RFC 7009, the endpoint always returns 200 OK, even if the token was already revoked or unknown.
| Feature | Managed (Grantex Cloud) | Self-Hosted |
|---|---|---|
| Setup | createMcpAuthServer({ grantex, ... }) | Same API, your infrastructure |
| Client Store | In-memory (stateless, horizontal scale) | Bring your own (Postgres, Redis, etc.) |
| JWKS | Hosted by Grantex | Your JWKS endpoint |
| Token Signing | Grantex signs tokens | Grantex signs tokens (delegated) |
| Rate Limiting | Built-in per-endpoint limits | Built-in, configurable |
| Consent UI | Grantex-hosted consent page | Custom consent page via consentUi config |
| Audit Trail | Full audit via Grantex events | Full audit via Grantex events |
| Uptime SLA | 99.9% | Your responsibility |
| Compliance | SOC 2, GDPR ready | Your responsibility |
Use the Grantex Cloud auth service. Zero infrastructure to manage:
const server = await createMcpAuthServer({
grantex: new Grantex({
baseUrl: 'https://grantex-auth-dd4mtrt2gq-uc.a.run.app',
apiKey: process.env.GRANTEX_API_KEY!,
}),
agentId: 'ag_your_server',
scopes: ['tools:read', 'tools:execute'],
issuer: 'https://your-domain.example.com',
});
Run your own Grantex auth service and point the SDK at it:
const server = await createMcpAuthServer({
grantex: new Grantex({
baseUrl: 'https://auth.your-company.internal',
apiKey: process.env.GRANTEX_API_KEY!,
}),
agentId: 'ag_internal_server',
scopes: ['internal:read', 'internal:write'],
issuer: 'https://auth.your-company.internal',
clientStore: new PostgresClientStore(), // Persistent storage
});
Grantex offers a certification program for MCP servers that implement OAuth 2.1 correctly:
All Bronze requirements, plus:
All Silver requirements, plus:
Using @grantex/mcp-auth with all features enabled gets you to Gold certification automatically.
@grantex/mcp-auth enforces OAuth 2.1 security requirements:
plain method and implicit grant are rejected.password grant type is not supported.response_type=code is accepted.crypto.randomBytes(32).jose library with remote key set fetching and caching.The introspection and middleware endpoints only accept tokens signed with:
RS256 (RSA PKCS#1 v1.5)ES256 (ECDSA P-256)PS256 (RSA-PSS)EdDSA (Ed25519)Symmetric algorithms (HS256, HS384, HS512) are explicitly rejected.
MCP clients discover your auth server via the well-known metadata endpoint:
curl https://your-mcp-server.example.com/.well-known/oauth-authorization-server
{
"issuer": "https://your-mcp-server.example.com",
"authorization_endpoint": "https://your-mcp-server.example.com/authorize",
"token_endpoint": "https://your-mcp-server.example.com/token",
"registration_endpoint": "https://your-mcp-server.example.com/register",
"introspection_endpoint": "https://your-mcp-server.example.com/introspect",
"revocation_endpoint": "https://your-mcp-server.example.com/revoke",
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code", "refresh_token"],
"code_challenge_methods_supported": ["S256"],
"token_endpoint_auth_methods_supported": ["client_secret_post", "client_secret_basic", "none"],
"introspection_endpoint_auth_methods_supported": ["client_secret_basic", "none"],
"revocation_endpoint_auth_methods_supported": ["client_secret_basic", "client_secret_post"],
"scopes_supported": ["tools:read", "tools:execute", "resources:read"],
"grantex_extensions": {
"consent_ui": "https://your-mcp-server.example.com/consent",
"audit_stream": "https://your-mcp-server.example.com/events/stream"
}
}
import { Grantex } from '@grantex/sdk';
import { createMcpAuthServer } from '@grantex/mcp-auth';
import express from 'express';
import { requireMcpAuth } from '@grantex/mcp-auth/express';
import type { McpAuthRequest } from '@grantex/mcp-auth/express';
// 1. Create Grantex client
const grantex = new Grantex({
baseUrl: 'https://grantex-auth-dd4mtrt2gq-uc.a.run.app',
apiKey: process.env.GRANTEX_API_KEY!,
});
// 2. Start OAuth 2.1 auth server
const authServer = await createMcpAuthServer({
grantex,
agentId: 'ag_calendar_mcp',
scopes: ['calendar:read', 'calendar:write'],
issuer: 'https://calendar-mcp.example.com',
hooks: {
onTokenIssued: async (event) => {
await grantex.audit.log({
action: 'mcp.token.issued',
agentId: event.agentDid,
grantId: event.grantId,
scopes: event.scopes,
});
},
onRevocation: async (jti) => {
await grantex.audit.log({
action: 'mcp.token.revoked',
tokenId: jti,
});
},
},
});
await authServer.listen({ port: 3001 });
// 3. Create MCP tool server with auth middleware
const app = express();
app.use('/mcp', requireMcpAuth({
issuer: 'https://calendar-mcp.example.com',
scopes: ['calendar:read'],
}));
app.post('/mcp/tools/list', (req: McpAuthRequest, res) => {
res.json({
tools: [
{ name: 'get_events', description: 'Get calendar events' },
{ name: 'create_event', description: 'Create a calendar event' },
],
});
});
app.post('/mcp/tools/call', requireMcpAuth({
issuer: 'https://calendar-mcp.example.com',
scopes: ['calendar:write'],
}), (req: McpAuthRequest, res) => {
const grant = req.mcpGrant!;
// grant.agentDid, grant.scopes, grant.sub are available
res.json({ result: 'Event created' });
});
app.listen(3000, () => {
console.log('MCP Tool Server on :3000, Auth Server on :3001');
});
The middleware fetches JWKS from {issuer}/.well-known/jwks.json. Ensure:
keys arrayactive: falseCommon causes:
exp claimiss claim must matchClient authentication uses Basic auth (Authorization: Basic base64(client_id:client_secret)) or body parameters. Verify your client credentials match what was returned by /register.
Default limits per endpoint:
| Endpoint | Max requests | Window |
|---|---|---|
/authorize | 10 | 1 minute |
/token | 20 | 1 minute |
/introspect | 30 | 1 minute |
/revoke | 20 | 1 minute |
| All others | 100 | 1 minute |
| Package | Description |
|---|---|
@grantex/sdk | Core TypeScript SDK |
@grantex/express | Express.js middleware for Grantex |
@grantex/gateway | Reverse-proxy gateway with YAML config |
@grantex/mcp | MCP server with 13 Grantex tools |
@grantex/cli | CLI for managing grants, tokens, and agents |
@grantex/conformance | Protocol conformance test suite |
Apache-2.0
FAQs
OAuth 2.1 + PKCE authorization server for MCP servers, powered by Grantex
The npm package @grantex/mcp-auth receives a total of 17 weekly downloads. As such, @grantex/mcp-auth popularity was classified as not popular.
We found that @grantex/mcp-auth demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Security News
GPT-6 Astra hits 100% on ExploitBench and finds zero-days autonomously, while independent tests reveal scope violations and monitoring gaps.

Product
Socket can now send alerts and supply chain attack notifications to Microsoft Teams, with filters that route the right updates to each channel.

Security News
pnpm 12 rewrites the package manager in Rust, cutting install times by up to 90% while preserving pnpm 11 workflows and lockfiles.