> ## Documentation Index
> Fetch the complete documentation index at: https://docs.grantex.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Legacy MCP Auth 2.0.2

> Archived OAuth endpoint behavior and limitations for immutable MCP Auth 2.0.2. See the current deployment guide for upgrades.

## Overview

MCP clients use OAuth authorization-server metadata, Dynamic Client Registration,
PKCE, token exchange, introspection, and revocation endpoints.
`@grantex/mcp-auth` registers those six routes on a Fastify instance and provides
Express and Hono JWT-verification middleware.

Historical release documented on this page: **`@grantex/mcp-auth@2.0.2`**.
See [MCP Auth Server](/features/mcp-auth-server) for the current release guide.

```bash theme={null}
npm install @grantex/mcp-auth@2.0.2 @grantex/sdk@0.5.1
```

This exact command is reproducible. See [Release Status](/release-status) before
upgrading either independently versioned package.

<Note>
  This is an archive, not current installation guidance. Version 3.0.0 added
  durable storage and a rendered page; version 4.0.0 adds authenticated human
  binding and current issuer authority. Check [release status](/release-status)
  and follow [the current deployment and migration guide](/mcp-auth).
</Note>

<Info>
  The supplied `Grantex` client can point at Grantex Cloud or a self-hosted
  Grantex API. That backend choice does not make the MCP authorization server's
  own client-registration or authorization-code state managed or durable.
</Info>

<Warning>
  Release `2.0.2` is a single-process evaluation release, not a turnkey
  production deployment. Client registrations default to process memory and
  authorization codes always use a non-configurable in-memory store, so restarts
  lose state and multi-replica authorize/token flows can fail. `consentUi` only
  adds discovery metadata; no `/consent` page is registered. The package also
  does not persist the `auth.code` returned by the Grantex SDK and can fall back
  to an authorization-request ID during token exchange, which is not a valid
  exchange code. Do not rely on end-to-end token issuance until a corrected
  package release is published and validated with your backend.
</Warning>

## Deployment behavior in 2.0.2

| Concern | Current behavior |
| - | - |
| Grantex backend | Cloud or self-hosted, selected through the supplied SDK client |
| Client registrations | In-memory by default; a custom `ClientStore` can persist clients |
| Authorization codes | Always process-local `InMemoryCodeStore`; no `codeStore` option |
| Horizontal scaling | Unsupported for authorize/token flows in `2.0.2` |
| Consent UI | Metadata only; the package registers no consent route or page |
| Rate limiting | Fixed Fastify limits; not configurable through `McpAuthConfig` |
| Middleware/introspection | Signature and claim validation; no live revocation lookup |

## Quick Start

The following starts the published endpoint surface for local inspection and
evaluation. It does not remove the limitations above.

### 1. Create the auth server

```typescript theme={null}
import { Grantex } from '@grantex/sdk';
import { createMcpAuthServer } from '@grantex/mcp-auth';

const grantex = new Grantex({
  baseUrl: 'https://api.grantex.dev',
  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');
```

### 2. Protect your MCP routes

Use the Express or Hono middleware to validate tokens on every request:

```typescript theme={null}
import express from 'express';
import { requireMcpAuth } from '@grantex/mcp-auth/express';
import type { McpAuthRequest } from '@grantex/mcp-auth/express';

const app = express();

app.use('/mcp', requireMcpAuth({
  issuer: 'https://your-mcp-server.example.com',
  scopes: ['tools:execute'],
}));

app.post('/mcp/tools/call', (req: McpAuthRequest, res) => {
  const grant = req.mcpGrant!;
  console.log(`Agent: ${grant.agentDid}`);
  console.log(`Scopes: ${grant.scopes.join(', ')}`);
  res.json({ result: 'tool executed' });
});

app.listen(3000);
```

### 3. Verify it works

MCP clients discover your auth server automatically:

```bash theme={null}
curl https://your-mcp-server.example.com/.well-known/oauth-authorization-server
```

## Endpoints

`createMcpAuthServer()` registers six 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 | JWT signature/claim introspection; no live revocation lookup |
| `/revoke` | `POST` | RFC 7009 | Requests backend revocation by JWT `jti` |

### Well-Known Metadata

The metadata endpoint returns a JSON document that MCP clients use to discover all other endpoints:

```json theme={null}
{
  "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"
  ],
  "scopes_supported": ["tools:read", "tools:execute", "resources:read"]
}
```

### Token Introspection

The endpoint verifies JWT signatures and claims against the issuer JWKS. In `2.0.2` it does not query Grantex for current revocation state, so a revoked but otherwise valid token can remain `active` here until expiry:

```bash theme={null}
curl -X POST https://your-mcp-server.example.com/introspect \
  -H "Content-Type: application/json" \
  --data "$(jq -n --arg token "$GRANT_TOKEN" '{token: $token}')"
```

Active token response:

```json theme={null}
{
  "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_grant_id": "grnt_01HXYZ"
}
```

### Token Revocation

The revocation endpoint decodes the token `jti` and requests revocation from Grantex. It returns `200 OK` even if that backend call fails, and the package middleware/introspection paths do not consult the resulting revocation state:

```bash theme={null}
curl -X POST https://your-mcp-server.example.com/revoke \
  -u "$CLIENT_ID:$CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  --data "$(jq -n --arg token "$GRANT_TOKEN" '{token: $token}')"
```

Per RFC 7009, the endpoint always returns `200 OK`, even if the token was already revoked.

## Scope Definition

Define the scopes your MCP server supports when creating the auth server:

```typescript theme={null}
const authServer = await createMcpAuthServer({
  grantex,
  agentId: 'ag_your_server',
  scopes: [
    'tools:read',       // List available tools
    'tools:execute',    // Call tools
    'resources:read',   // Read resources
    'resources:write',  // Create/update resources
    'prompts:read',     // List prompts
    'prompts:execute',  // Execute prompts
  ],
  issuer: 'https://your-mcp-server.example.com',
});
```

Scopes are used at two implemented points in `2.0.2`:

1. **Authorization request**: The package forwards the requested scopes to the configured Grantex client.
2. **Middleware**: `requireMcpAuth()` rejects tokens that lack required scopes.

The package does not render a consent page. A complete human-consent handoff must
be implemented outside this release.

## Consent metadata (not a rendered page)

`consentUi` copies display metadata into the discovery document under
`grantex_extensions.consent_ui_config`:

```typescript theme={null}
const authServer = await createMcpAuthServer({
  grantex,
  agentId: 'ag_your_server',
  scopes: ['tools:read', 'tools:execute'],
  issuer: 'https://your-mcp-server.example.com',
  consentUi: {
    appName: 'My Calendar MCP',
    appLogo: 'https://example.com/logo.png',
    privacyUrl: 'https://example.com/privacy',
    termsUrl: 'https://example.com/terms',
  },
});
```

| Property | Type | Metadata meaning |
| - | - | - |
| `appName` | `string` | Application label advertised to discovery consumers |
| `appLogo` | `string` | Advertised application-logo URL |
| `privacyUrl` | `string` | Advertised privacy-policy URL |
| `termsUrl` | `string` | Advertised terms URL |

<Warning>
  `2.0.2` does not register the `/consent` URL advertised in
  `grantex_extensions.consent_ui`, and `consentUi` does not render or host a
  consent page. Supply that interaction separately and validate the complete
  authorization flow before use.
</Warning>

## Lifecycle hooks

Only `hooks.onRevocation` is invoked by `2.0.2`:

```typescript theme={null}
const authServer = await createMcpAuthServer({
  // ...required fields...
  hooks: {
    onRevocation: async (jti) => {
      console.log(`Token ${jti} was submitted for revocation`);
    },
  },
});
```

The exported configuration type also declares `onTokenIssued`, but the token
endpoint in `2.0.2` does not call it. Do not depend on that hook until a corrected
release is published.

## Express Middleware

<Note>
  Express and Hono middleware verify JWT signatures, claims, algorithms, and
  configured scopes. They do not perform an online revocation check in `2.0.2`.
</Note>

```typescript theme={null}
import { requireMcpAuth } from '@grantex/mcp-auth/express';
import type { McpAuthRequest, McpGrant } from '@grantex/mcp-auth/express';

app.use('/mcp', requireMcpAuth({
  issuer: 'https://your-mcp-server.example.com',
  scopes: ['tools:execute'],
}));
```

### `requireMcpAuth(options)`

| 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 |

## Hono Middleware

```typescript theme={null}
import { Hono } from 'hono';
import { requireMcpAuth } from '@grantex/mcp-auth/hono';

const app = new Hono();

app.use('/mcp/*', requireMcpAuth({
  issuer: 'https://your-mcp-server.example.com',
  scopes: ['tools:execute'],
}));

app.post('/mcp/tools/call', (c) => {
  const grant = c.get('mcpGrant');
  return c.json({ agent: grant.agentDid, scopes: grant.scopes });
});
```

## Custom Client Store

By default, client registrations are stored in process memory. A custom `ClientStore` can persist registrations, but authorization codes remain in a non-configurable process-local store in `2.0.2`; a custom client store alone does not make the server durable or horizontally scalable:

```typescript theme={null}
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;
  }
}
```

## Certification Program

Grantex accepts Bronze, Silver, and Gold certification applications for registered MCP servers. Applications begin in `pending_conformance_test`; submission alone is not certification. See the [MCP Certification Applications guide](/guides/mcp-certification) for the current API and limitations.

| Requested level | Application value |
| - | - |
| **Bronze** | `bronze` |
| **Silver** | `silver` |
| **Gold** | `gold` |

<Warning>
  Using `@grantex/mcp-auth` does not automatically certify a server. Treat the status returned by the certification API as authoritative and do not publish a badge for a pending application.
</Warning>

## Configuration Reference

### `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 |
| `allowedRedirectUris` | `string[]` | No | `[]` | Declared by the type but not enforced in `2.0.2`; authorization checks each client's DCR-provided URI |
| `allowedResources` | `string[]` | No | `[]` | Allowed resource indicators (RFC 8707) |
| `clientStore` | `ClientStore` | No | `InMemoryClientStore` | Custom client-registration store only; authorization codes remain in memory |
| `codeExpirationSeconds` | `number` | No | `600` | Authorization code TTL |
| `consentUi` | `object` | No | -- | Discovery metadata only; does not create a consent page |
| `hooks` | `object` | No | -- | `onRevocation` runs; declared `onTokenIssued` is not invoked in `2.0.2` |

## Rate Limits

Default per-endpoint rate limits:

| Endpoint | Max Requests | Window |
| - | - | - |
| `/authorize` | 10 | 1 minute |
| `/token` | 20 | 1 minute |
| `/introspect` | 20 | 1 minute |
| `/revoke` | 20 | 1 minute |
| All others | 100 | 1 minute |

## Security Considerations

`@grantex/mcp-auth` implements the following request and token-validation controls. These controls do not remove the deployment and end-to-end flow limitations documented above:

* **PKCE S256 is mandatory.** The `plain` method and implicit grant are rejected.
* **No password grant.** The `password` grant type is not supported.
* **No implicit grant.** Only `response_type=code` is accepted.
* **Authorization codes are single-use.** Replayed codes are rejected.
* **HS256 rejected.** Only asymmetric algorithms (RS256, ES256, PS256, EdDSA) are accepted.
* **Client secrets** are generated using `crypto.randomBytes(32)`.
* **JWKS verification** uses the `jose` library with remote key set fetching and caching.

## Related

* [MCP Certification Applications](/guides/mcp-certification) -- Application API and current limitations
* [MCP Server (17 tools)](/integrations/mcp) -- Grantex MCP server for Claude Desktop/Cursor
* [Express Middleware](/integrations/express) -- Grantex Express.js middleware
* [Self-Hosting Guide](/guides/self-hosting) -- Deploy the full Grantex stack
* [Security Best Practices](/guides/security-best-practices) -- Token verification, key rotation

## Ownership

Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.