v2 backcompat: server legacy package (#2206)

Co-authored-by: Felix Weinberger <3823880+felixweinberger@users.noreply.github.com>
This commit is contained in:
Konstantin Konstantinov 2026-06-01 17:33:32 +03:00 • committed by GitHub
commit e03bca90c1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
46 changed files with 5797 additions and 71 deletions

View file

@ -9,7 +9,8 @@ const PACKAGE_DIRS: Record<string, string> = {
'@modelcontextprotocol/client': 'client',
'@modelcontextprotocol/server': 'server',
'@modelcontextprotocol/node': 'middleware/node',
'@modelcontextprotocol/express': 'middleware/express'
'@modelcontextprotocol/express': 'middleware/express',
'@modelcontextprotocol/server-legacy': 'server-legacy'
};
const versions: Record<string, string> = {};

View file

@ -3,5 +3,6 @@ export const V2_PACKAGE_VERSIONS: Record<string, string> = {
'@modelcontextprotocol/client': '^2.0.0-alpha.2',
'@modelcontextprotocol/server': '^2.0.0-alpha.2',
'@modelcontextprotocol/node': '^2.0.0-alpha.2',
'@modelcontextprotocol/express': '^2.0.0-alpha.2'
'@modelcontextprotocol/express': '^2.0.0-alpha.2',
'@modelcontextprotocol/server-legacy': '^2.0.0-alpha.2'
};

View file

@ -7,6 +7,8 @@ export interface ImportMapping {
removalMessage?: string;
/** No entries currently set this; scaffolding for when a v1 symbol has no v2 equivalent yet. */
isV2Gap?: boolean;
/** Emitted as an info diagnostic after a successful move, suggesting eventual migration to v2 equivalents. */
migrationHint?: string;
/**
* Subpath suffix appended after `RESOLVE_BY_CONTEXT` resolves the base package (e.g. `/validators/ajv`).
* The final target becomes `@modelcontextprotocol/{client,server}<subpathSuffix>`.
@ -74,9 +76,10 @@ export const IMPORT_MAP: Record<string, ImportMapping> = {
status: 'moved'
},
'@modelcontextprotocol/sdk/server/sse.js': {
target: '',
status: 'removed',
removalMessage: 'SSE server transport removed in v2. Migrate to NodeStreamableHTTPServerTransport from @modelcontextprotocol/node.'
target: '@modelcontextprotocol/server-legacy/sse',
status: 'moved',
migrationHint:
'SSEServerTransport is deprecated. Migrate to NodeStreamableHTTPServerTransport from @modelcontextprotocol/node or WebStandardStreamableHTTPServerTransport from @modelcontextprotocol/server.'
},
'@modelcontextprotocol/sdk/server/middleware.js': {
target: '@modelcontextprotocol/express',
@ -90,34 +93,29 @@ export const IMPORT_MAP: Record<string, ImportMapping> = {
},
'@modelcontextprotocol/sdk/server/auth/types.js': {
target: '',
status: 'removed',
removalMessage:
'Server auth removed in v2. AuthInfo is now re-exported by @modelcontextprotocol/client and @modelcontextprotocol/server.'
target: '@modelcontextprotocol/server-legacy/auth',
status: 'moved',
migrationHint: 'Legacy auth types. AuthInfo is also re-exported by @modelcontextprotocol/server.'
},
'@modelcontextprotocol/sdk/server/auth/provider.js': {
target: '',
status: 'removed',
removalMessage:
'Server auth provider removed in v2. For Resource-Server auth (token verification), see @modelcontextprotocol/express. For full OAuth AS, see @modelcontextprotocol/server-auth-legacy (PR #1908).'
target: '@modelcontextprotocol/server-legacy/auth',
status: 'moved',
migrationHint: 'Legacy OAuth AS provider. For RS-only auth, see requireBearerAuth from @modelcontextprotocol/express.'
},
'@modelcontextprotocol/sdk/server/auth/router.js': {
target: '',
status: 'removed',
removalMessage:
'Server auth router removed in v2. For metadata endpoints, see mcpAuthMetadataRouter from @modelcontextprotocol/express. For full OAuth AS router, see @modelcontextprotocol/server-auth-legacy (PR #1908).'
target: '@modelcontextprotocol/server-legacy/auth',
status: 'moved',
migrationHint: 'Legacy OAuth AS router. For metadata-only endpoints, see mcpAuthMetadataRouter from @modelcontextprotocol/express.'
},
'@modelcontextprotocol/sdk/server/auth/middleware.js': {
target: '',
status: 'removed',
removalMessage:
'Server auth middleware removed in v2. For bearer token validation, see requireBearerAuth from @modelcontextprotocol/express. For full OAuth AS, see @modelcontextprotocol/server-auth-legacy (PR #1908).'
target: '@modelcontextprotocol/server-legacy/auth',
status: 'moved',
migrationHint: 'Legacy OAuth AS middleware. For bearer-only auth, see requireBearerAuth from @modelcontextprotocol/express.'
},
'@modelcontextprotocol/sdk/server/auth/errors.js': {
target: '',
status: 'removed',
removalMessage:
'Auth error subclasses consolidated in v2. Use OAuthError + OAuthErrorCode from @modelcontextprotocol/server. See also @modelcontextprotocol/server-auth-legacy (PR #1908).'
target: '@modelcontextprotocol/server-legacy/auth',
status: 'moved',
migrationHint: 'Legacy error subclasses. v2 consolidates to OAuthError + OAuthErrorCode in @modelcontextprotocol/server.'
},
'@modelcontextprotocol/sdk/types.js': {

View file

@ -2,7 +2,7 @@ import type { SourceFile } from 'ts-morph';
import type { Diagnostic, Transform, TransformContext, TransformResult } from '../../../types.js';
import { renameAllReferences } from '../../../utils/astUtils.js';
import { v2Gap, warning } from '../../../utils/diagnostics.js';
import { info, v2Gap, warning } from '../../../utils/diagnostics.js';
import { addOrMergeImport, getSdkExports, getSdkImports, isTypeOnlyImport } from '../../../utils/importUtils.js';
import { resolveTypesPackage } from '../../../utils/projectAnalyzer.js';
import { IMPORT_MAP, isAuthImport } from '../mappings/importMap.js';
@ -75,10 +75,9 @@ export const importPathsTransform: Transform = {
if (!mapping && isAuthImport(specifier)) {
mapping = {
target: '',
status: 'removed',
removalMessage:
'Server auth removed in v2. For RS auth, see @modelcontextprotocol/express. For full OAuth AS, see @modelcontextprotocol/server-auth-legacy (PR #1908).'
target: '@modelcontextprotocol/server-legacy/auth',
status: 'moved',
migrationHint: 'Legacy auth module. For RS-only auth, see @modelcontextprotocol/express.'
};
}
@ -156,6 +155,9 @@ export const importPathsTransform: Transform = {
}
}
changesCount++;
if (mapping.migrationHint) {
diagnostics.push(info(filePath, line, mapping.migrationHint));
}
for (const [oldName, newName] of symbolsToRenameInFile) {
renameAllReferences(sourceFile, oldName, newName);
}
@ -172,6 +174,9 @@ export const importPathsTransform: Transform = {
}
imp.remove();
changesCount++;
if (mapping.migrationHint) {
diagnostics.push(info(filePath, line, mapping.migrationHint));
}
for (const [oldName, newName] of symbolsToRenameInFile) {
renameAllReferences(sourceFile, oldName, newName);
}
@ -222,10 +227,9 @@ function rewriteExportDeclarations(
if (!mapping && isAuthImport(specifier)) {
mapping = {
target: '',
status: 'removed',
removalMessage:
'Server auth removed in v2. For RS auth, see @modelcontextprotocol/express. For full OAuth AS, see @modelcontextprotocol/server-auth-legacy (PR #1908).'
target: '@modelcontextprotocol/server-legacy/auth',
status: 'moved',
migrationHint: 'Legacy auth module. For RS-only auth, see @modelcontextprotocol/express.'
};
}
@ -288,6 +292,9 @@ function rewriteExportDeclarations(
}
}
changesCount++;
if (mapping.migrationHint) {
diagnostics.push(info(filePath, line, mapping.migrationHint));
}
}
return changesCount;

View file

@ -49,7 +49,7 @@ function resolveTarget(
| { removed: true; isV2Gap?: boolean; removalMessage?: string }
| null {
const mapping = IMPORT_MAP[specifier];
if (!mapping && isAuthImport(specifier)) return { removed: true };
if (!mapping && isAuthImport(specifier)) return { target: '@modelcontextprotocol/server-legacy/auth' };
if (!mapping) return null;
if (mapping.status === 'removed') return { removed: true, isV2Gap: mapping.isV2Gap, removalMessage: mapping.removalMessage };

View file

@ -23,7 +23,7 @@ afterEach(() => {
});
describe('CLI diagnostic behavior', () => {
it('warnings do not produce errors-level diagnostics', () => {
it('info diagnostics do not produce error-level diagnostics', () => {
const dir = createTempDir();
writeFileSync(
path.join(dir, 'server.ts'),
@ -36,9 +36,9 @@ describe('CLI diagnostic behavior', () => {
const result = run(migration, { targetDir: dir });
const warnings = result.diagnostics.filter(d => d.level === DiagnosticLevel.Warning);
const infos = result.diagnostics.filter(d => d.level === DiagnosticLevel.Info);
const errors = result.diagnostics.filter(d => d.level === DiagnosticLevel.Error);
expect(warnings.length).toBeGreaterThan(0);
expect(infos.length).toBeGreaterThan(0);
expect(errors.length).toBe(0);
});

View file

@ -573,7 +573,7 @@ describe('integration', () => {
expect(result.packageJsonChanges!.removed).toContain('@modelcontextprotocol/sdk');
});
it('emits diagnostics for removed imports', () => {
it('emits info diagnostics for legacy-moved imports', () => {
const dir = createTempDir();
const input = [
`import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';`,
@ -586,7 +586,7 @@ describe('integration', () => {
const result = run(migration, { targetDir: dir });
expect(result.diagnostics.length).toBeGreaterThan(0);
expect(result.diagnostics.some(d => d.level === DiagnosticLevel.Warning)).toBe(true);
expect(result.diagnostics.some(d => d.level === DiagnosticLevel.Info)).toBe(true);
});
it('transform ordering: critical dependencies are maintained', () => {

View file

@ -59,14 +59,19 @@ describe('import-paths transform', () => {
expect(result.diagnostics[0]!.message).toContain('WebSocketClientTransport');
});
it('removes SSE server import with warning', () => {
it('moves SSE server import to server-legacy/sse with info diagnostic', () => {
const input = `import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';\n`;
const ctx: TransformContext = { projectType: 'server' };
const project = new Project({ useInMemoryFileSystem: true });
const sourceFile = project.createSourceFile('test.ts', input);
const result = importPathsTransform.apply(sourceFile, ctx);
const output = sourceFile.getFullText();
expect(output).toContain('@modelcontextprotocol/server-legacy/sse');
expect(output).toContain('SSEServerTransport');
expect(output).not.toContain('@modelcontextprotocol/sdk');
expect(result.changesCount).toBeGreaterThan(0);
expect(result.diagnostics.length).toBeGreaterThan(0);
expect(result.diagnostics[0]!.message).toContain('SSE server transport');
expect(result.diagnostics[0]!.message).toContain('SSEServerTransport is deprecated');
});
it('resolves sdk/types.js based on sibling client imports', () => {
@ -270,13 +275,79 @@ describe('import-paths transform', () => {
expect(result).toContain('NodeStreamableHTTPServerTransport as StreamableHTTPServerTransport');
});
it('removes auth imports with warning', () => {
it('moves auth router import to server-legacy/auth with info diagnostic', () => {
const input = `import { mcpAuthRouter } from '@modelcontextprotocol/sdk/server/auth/router.js';\n`;
const project = new Project({ useInMemoryFileSystem: true });
const sourceFile = project.createSourceFile('test.ts', input);
const result = importPathsTransform.apply(sourceFile, { projectType: 'server' });
const output = sourceFile.getFullText();
expect(output).toContain('@modelcontextprotocol/server-legacy/auth');
expect(output).toContain('mcpAuthRouter');
expect(output).not.toContain('@modelcontextprotocol/sdk');
expect(result.changesCount).toBeGreaterThan(0);
expect(result.diagnostics.length).toBeGreaterThan(0);
expect(result.diagnostics[0]!.message).toContain('auth router removed');
expect(result.diagnostics[0]!.message).toContain('Legacy OAuth AS router');
});
it('moves auth provider import to server-legacy/auth', () => {
const input = `import type { OAuthServerProvider } from '@modelcontextprotocol/sdk/server/auth/provider.js';\n`;
const result = applyTransform(input, { projectType: 'server' });
expect(result).toContain('@modelcontextprotocol/server-legacy/auth');
expect(result).toContain('OAuthServerProvider');
expect(result).not.toContain('@modelcontextprotocol/sdk');
});
it('moves auth middleware import to server-legacy/auth', () => {
const input = `import { requireBearerAuth } from '@modelcontextprotocol/sdk/server/auth/middleware.js';\n`;
const result = applyTransform(input, { projectType: 'server' });
expect(result).toContain('@modelcontextprotocol/server-legacy/auth');
expect(result).toContain('requireBearerAuth');
expect(result).not.toContain('@modelcontextprotocol/sdk');
});
it('moves auth errors import to server-legacy/auth', () => {
const input = `import { InvalidTokenError, OAuthError } from '@modelcontextprotocol/sdk/server/auth/errors.js';\n`;
const result = applyTransform(input, { projectType: 'server' });
expect(result).toContain('@modelcontextprotocol/server-legacy/auth');
expect(result).toContain('InvalidTokenError');
expect(result).toContain('OAuthError');
expect(result).not.toContain('@modelcontextprotocol/sdk');
});
it('consolidates multiple auth subpath imports into single server-legacy/auth import', () => {
const input = [
`import { mcpAuthRouter } from '@modelcontextprotocol/sdk/server/auth/router.js';`,
`import { requireBearerAuth } from '@modelcontextprotocol/sdk/server/auth/middleware.js';`,
''
].join('\n');
const result = applyTransform(input, { projectType: 'server' });
expect(result).toContain('mcpAuthRouter');
expect(result).toContain('requireBearerAuth');
expect(result).toContain('@modelcontextprotocol/server-legacy/auth');
const importLines = result.split('\n').filter((l: string) => l.includes('@modelcontextprotocol/server-legacy/auth'));
expect(importLines.length).toBe(1);
});
it('moves SSE server re-export to server-legacy/sse', () => {
const input = `export { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';\n`;
const project = new Project({ useInMemoryFileSystem: true });
const sourceFile = project.createSourceFile('test.ts', input);
const result = importPathsTransform.apply(sourceFile, { projectType: 'server' });
const output = sourceFile.getFullText();
expect(output).toContain('@modelcontextprotocol/server-legacy/sse');
expect(output).toContain('SSEServerTransport');
expect(result.diagnostics.some(d => d.message.includes('SSEServerTransport is deprecated'))).toBe(true);
});
it('includes server-legacy in usedPackages for SSE import', () => {
const project = new Project({ useInMemoryFileSystem: true });
const sourceFile = project.createSourceFile(
'test.ts',
`import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';\n`
);
const result = importPathsTransform.apply(sourceFile, { projectType: 'server' });
expect(result.usedPackages).toBeDefined();
expect(result.usedPackages!.has('@modelcontextprotocol/server-legacy/sse')).toBe(true);
});
it('handles per-specifier type modifiers', () => {
@ -373,14 +444,17 @@ describe('import-paths transform', () => {
expect(result.diagnostics[0]!.message).toContain('removed');
});
it('handles unknown auth subpath via catch-all', () => {
it('moves unknown auth subpath to server-legacy/auth via catch-all', () => {
const input = `import { SomeType } from '@modelcontextprotocol/sdk/server/auth/handler.js';\n`;
const project = new Project({ useInMemoryFileSystem: true });
const sourceFile = project.createSourceFile('test.ts', input);
const result = importPathsTransform.apply(sourceFile, { projectType: 'server' });
expect(result.changesCount).toBe(1);
expect(sourceFile.getFullText()).not.toContain('@modelcontextprotocol/sdk');
expect(result.diagnostics.some(d => d.message.includes('Server auth removed'))).toBe(true);
const output = sourceFile.getFullText();
expect(output).toContain('@modelcontextprotocol/server-legacy/auth');
expect(output).toContain('SomeType');
expect(output).not.toContain('@modelcontextprotocol/sdk');
expect(result.diagnostics.some(d => d.message.includes('Legacy auth module'))).toBe(true);
});
it('rewrites InMemoryTransport to @modelcontextprotocol/server for server projects', () => {

View file

@ -193,13 +193,22 @@ describe('mock-paths transform', () => {
expect(result.diagnostics[0]!.message).toContain('Unknown SDK dynamic import path');
});
it('emits warning for removed SDK dynamic import path', () => {
it('rewrites SSE dynamic import to server-legacy/sse', () => {
const input = [`const m = await import('@modelcontextprotocol/sdk/server/sse.js');`, ''].join('\n');
const project = new Project({ useInMemoryFileSystem: true });
const sourceFile = project.createSourceFile('test.ts', input);
const result = mockPathsTransform.apply(sourceFile, ctx);
expect(result.changesCount).toBeGreaterThan(0);
expect(sourceFile.getFullText()).toContain('@modelcontextprotocol/server-legacy/sse');
});
it('emits warning for removed SDK dynamic import path', () => {
const input = [`const m = await import('@modelcontextprotocol/sdk/client/websocket.js');`, ''].join('\n');
const project = new Project({ useInMemoryFileSystem: true });
const sourceFile = project.createSourceFile('test.ts', input);
const result = mockPathsTransform.apply(sourceFile, ctx);
expect(result.diagnostics.length).toBeGreaterThan(0);
expect(result.diagnostics[0]!.message).toContain('SSE server transport removed in v2');
expect(result.diagnostics[0]!.message).toContain('WebSocketClientTransport removed in v2');
});
it('emits warning for unknown SDK mock path', () => {

View file

@ -0,0 +1,39 @@
# @modelcontextprotocol/server-legacy
> **Deprecated** — This package is a frozen copy of v1 code for migration purposes only. It will not receive new features and is planned for removal in v3.
Provides two pieces of v1 server functionality removed in v2:
- **SSE Transport** (`./sse`) — The `SSEServerTransport` class, replaced in v2 by `NodeStreamableHTTPServerTransport` (from `@modelcontextprotocol/node`) or `WebStandardStreamableHTTPServerTransport` (from `@modelcontextprotocol/server`)
- **OAuth Authorization Server** (`./auth`) — The `mcpAuthRouter` and related helpers, removed in v2 because MCP servers should use dedicated OAuth providers
## Installation
```bash
npm install @modelcontextprotocol/server-legacy
```
## Usage
### SSE Transport (no Express dependency required)
```ts
import { SSEServerTransport } from '@modelcontextprotocol/server-legacy/sse';
```
### OAuth Auth Router (requires Express)
```ts
import { mcpAuthRouter } from '@modelcontextprotocol/server-legacy/auth';
```
### Everything (requires Express)
```ts
import { SSEServerTransport, mcpAuthRouter } from '@modelcontextprotocol/server-legacy';
```
## Migration
- **SSE → StreamableHTTP**: Use `NodeStreamableHTTPServerTransport` from `@modelcontextprotocol/node` (Node.js) or `WebStandardStreamableHTTPServerTransport` from `@modelcontextprotocol/server` (Web Standard / Cloudflare Workers)
- **Auth router → Dedicated IdP**: Use a dedicated OAuth provider (Auth0, Keycloak, etc.) instead of the built-in OAuth Authorization Server

View file

@ -0,0 +1,12 @@
// @ts-check
import baseConfig from '@modelcontextprotocol/eslint-config';
export default [
...baseConfig,
{
settings: {
'import/internal-regex': '^@modelcontextprotocol/core'
}
}
];

View file

@ -0,0 +1,104 @@
{
"name": "@modelcontextprotocol/server-legacy",
"private": false,
"version": "2.0.0-alpha.2",
"description": "Frozen v1 SSE transport and OAuth Authorization Server helpers for the Model Context Protocol TypeScript SDK. Deprecated; use StreamableHTTP and a dedicated OAuth server in production.",
"deprecated": "This package is a frozen copy of v1's SSE transport and OAuth Authorization Server helpers for migration purposes only. Use StreamableHTTP from @modelcontextprotocol/server and a dedicated OAuth server in production. Will not receive new features.",
"license": "MIT",
"author": "Anthropic, PBC (https://anthropic.com)",
"homepage": "https://modelcontextprotocol.io",
"bugs": "https://github.com/modelcontextprotocol/typescript-sdk/issues",
"type": "module",
"repository": {
"type": "git",
"url": "git+https://github.com/modelcontextprotocol/typescript-sdk.git"
},
"engines": {
"node": ">=20"
},
"keywords": [
"modelcontextprotocol",
"mcp",
"server",
"legacy",
"sse",
"oauth"
],
"exports": {
".": {
"types": "./dist/index.d.mts",
"import": "./dist/index.mjs"
},
"./sse": {
"types": "./dist/sse/index.d.mts",
"import": "./dist/sse/index.mjs"
},
"./auth": {
"types": "./dist/auth/index.d.mts",
"import": "./dist/auth/index.mjs"
}
},
"types": "./dist/index.d.mts",
"typesVersions": {
"*": {
"sse": [
"dist/sse/index.d.mts"
],
"auth": [
"dist/auth/index.d.mts"
]
}
},
"files": [
"dist"
],
"scripts": {
"typecheck": "tsgo -p tsconfig.json --noEmit",
"build": "tsdown",
"build:watch": "tsdown --watch",
"prepack": "pnpm run build",
"lint": "eslint src/ && prettier --ignore-path ../../.prettierignore --check .",
"lint:fix": "eslint src/ --fix && prettier --ignore-path ../../.prettierignore --write .",
"check": "pnpm run typecheck && pnpm run lint",
"test": "vitest run",
"test:watch": "vitest"
},
"dependencies": {
"zod": "catalog:runtimeShared",
"raw-body": "catalog:runtimeServerOnly",
"content-type": "catalog:runtimeServerOnly",
"cors": "catalog:runtimeServerOnly",
"express-rate-limit": "^8.2.1",
"pkce-challenge": "catalog:runtimeShared"
},
"peerDependencies": {
"express": "^4.18.0 || ^5.0.0"
},
"peerDependenciesMeta": {
"express": {
"optional": true
}
},
"devDependencies": {
"@modelcontextprotocol/core": "workspace:^",
"@modelcontextprotocol/tsconfig": "workspace:^",
"@modelcontextprotocol/eslint-config": "workspace:^",
"@modelcontextprotocol/vitest-config": "workspace:^",
"@types/content-type": "catalog:devTools",
"@types/cors": "catalog:devTools",
"@types/express": "catalog:devTools",
"@types/express-serve-static-core": "catalog:devTools",
"@types/supertest": "catalog:devTools",
"@eslint/js": "catalog:devTools",
"@typescript/native-preview": "catalog:devTools",
"eslint": "catalog:devTools",
"eslint-config-prettier": "catalog:devTools",
"eslint-plugin-n": "catalog:devTools",
"prettier": "catalog:devTools",
"supertest": "catalog:devTools",
"tsdown": "catalog:devTools",
"typescript": "catalog:devTools",
"typescript-eslint": "catalog:devTools",
"vitest": "catalog:devTools"
}
}

View file

@ -0,0 +1,22 @@
import type { OAuthClientInformationFull } from '@modelcontextprotocol/core';
/**
* Stores information about registered OAuth clients for this server.
*/
export interface OAuthRegisteredClientsStore {
/**
* Returns information about a registered client, based on its ID.
*/
getClient(clientId: string): OAuthClientInformationFull | undefined | Promise<OAuthClientInformationFull | undefined>;
/**
* Registers a new client with the server. The client ID and secret will be automatically generated by the library. A modified version of the client information can be returned to reflect specific values enforced by the server.
*
* NOTE: Implementations should NOT delete expired client secrets in-place. Auth middleware provided by this library will automatically check the `client_secret_expires_at` field and reject requests with expired secrets. Any custom logic for authenticating clients should check the `client_secret_expires_at` field as well.
*
* If unimplemented, dynamic client registration is unsupported.
*/
registerClient?(
client: Omit<OAuthClientInformationFull, 'client_id' | 'client_id_issued_at'>
): OAuthClientInformationFull | Promise<OAuthClientInformationFull>;
}

View file

@ -0,0 +1,212 @@
import type { OAuthErrorResponse } from '@modelcontextprotocol/core';
/**
* Base class for all OAuth errors
*/
export class OAuthError extends Error {
static errorCode: string;
constructor(
message: string,
public readonly errorUri?: string
) {
super(message);
this.name = this.constructor.name;
}
/**
* Converts the error to a standard OAuth error response object
*/
toResponseObject(): OAuthErrorResponse {
const response: OAuthErrorResponse = {
error: this.errorCode,
error_description: this.message
};
if (this.errorUri) {
response.error_uri = this.errorUri;
}
return response;
}
get errorCode(): string {
return (this.constructor as typeof OAuthError).errorCode;
}
}
/**
* Invalid request error - The request is missing a required parameter,
* includes an invalid parameter value, includes a parameter more than once,
* or is otherwise malformed.
*/
export class InvalidRequestError extends OAuthError {
static override errorCode = 'invalid_request';
}
/**
* Invalid client error - Client authentication failed (e.g., unknown client, no client
* authentication included, or unsupported authentication method).
*/
export class InvalidClientError extends OAuthError {
static override errorCode = 'invalid_client';
}
/**
* Invalid grant error - The provided authorization grant or refresh token is
* invalid, expired, revoked, does not match the redirection URI used in the
* authorization request, or was issued to another client.
*/
export class InvalidGrantError extends OAuthError {
static override errorCode = 'invalid_grant';
}
/**
* Unauthorized client error - The authenticated client is not authorized to use
* this authorization grant type.
*/
export class UnauthorizedClientError extends OAuthError {
static override errorCode = 'unauthorized_client';
}
/**
* Unsupported grant type error - The authorization grant type is not supported
* by the authorization server.
*/
export class UnsupportedGrantTypeError extends OAuthError {
static override errorCode = 'unsupported_grant_type';
}
/**
* Invalid scope error - The requested scope is invalid, unknown, malformed, or
* exceeds the scope granted by the resource owner.
*/
export class InvalidScopeError extends OAuthError {
static override errorCode = 'invalid_scope';
}
/**
* Access denied error - The resource owner or authorization server denied the request.
*/
export class AccessDeniedError extends OAuthError {
static override errorCode = 'access_denied';
}
/**
* Server error - The authorization server encountered an unexpected condition
* that prevented it from fulfilling the request.
*/
export class ServerError extends OAuthError {
static override errorCode = 'server_error';
}
/**
* Temporarily unavailable error - The authorization server is currently unable to
* handle the request due to a temporary overloading or maintenance of the server.
*/
export class TemporarilyUnavailableError extends OAuthError {
static override errorCode = 'temporarily_unavailable';
}
/**
* Unsupported response type error - The authorization server does not support
* obtaining an authorization code using this method.
*/
export class UnsupportedResponseTypeError extends OAuthError {
static override errorCode = 'unsupported_response_type';
}
/**
* Unsupported token type error - The authorization server does not support
* the requested token type.
*/
export class UnsupportedTokenTypeError extends OAuthError {
static override errorCode = 'unsupported_token_type';
}
/**
* Invalid token error - The access token provided is expired, revoked, malformed,
* or invalid for other reasons.
*/
export class InvalidTokenError extends OAuthError {
static override errorCode = 'invalid_token';
}
/**
* Method not allowed error - The HTTP method used is not allowed for this endpoint.
* (Custom, non-standard error)
*/
export class MethodNotAllowedError extends OAuthError {
static override errorCode = 'method_not_allowed';
}
/**
* Too many requests error - Rate limit exceeded.
* (Custom, non-standard error based on RFC 6585)
*/
export class TooManyRequestsError extends OAuthError {
static override errorCode = 'too_many_requests';
}
/**
* Invalid client metadata error - The client metadata is invalid.
* (Custom error for dynamic client registration - RFC 7591)
*/
export class InvalidClientMetadataError extends OAuthError {
static override errorCode = 'invalid_client_metadata';
}
/**
* Insufficient scope error - The request requires higher privileges than provided by the access token.
*/
export class InsufficientScopeError extends OAuthError {
static override errorCode = 'insufficient_scope';
}
/**
* Invalid target error - The requested resource is invalid, missing, unknown, or malformed.
* (Custom error for resource indicators - RFC 8707)
*/
export class InvalidTargetError extends OAuthError {
static override errorCode = 'invalid_target';
}
/**
* A utility class for defining one-off error codes
*/
export class CustomOAuthError extends OAuthError {
constructor(
private readonly customErrorCode: string,
message: string,
errorUri?: string
) {
super(message, errorUri);
}
override get errorCode(): string {
return this.customErrorCode;
}
}
/**
* A full list of all OAuthErrors, enabling parsing from error responses
*/
export const OAUTH_ERRORS = {
[InvalidRequestError.errorCode]: InvalidRequestError,
[InvalidClientError.errorCode]: InvalidClientError,
[InvalidGrantError.errorCode]: InvalidGrantError,
[UnauthorizedClientError.errorCode]: UnauthorizedClientError,
[UnsupportedGrantTypeError.errorCode]: UnsupportedGrantTypeError,
[InvalidScopeError.errorCode]: InvalidScopeError,
[AccessDeniedError.errorCode]: AccessDeniedError,
[ServerError.errorCode]: ServerError,
[TemporarilyUnavailableError.errorCode]: TemporarilyUnavailableError,
[UnsupportedResponseTypeError.errorCode]: UnsupportedResponseTypeError,
[UnsupportedTokenTypeError.errorCode]: UnsupportedTokenTypeError,
[InvalidTokenError.errorCode]: InvalidTokenError,
[MethodNotAllowedError.errorCode]: MethodNotAllowedError,
[TooManyRequestsError.errorCode]: TooManyRequestsError,
[InvalidClientMetadataError.errorCode]: InvalidClientMetadataError,
[InsufficientScopeError.errorCode]: InsufficientScopeError,
[InvalidTargetError.errorCode]: InvalidTargetError
} as const;

View file

@ -0,0 +1,201 @@
import type { RequestHandler } from 'express';
import express from 'express';
import type { Options as RateLimitOptions } from 'express-rate-limit';
import { rateLimit } from 'express-rate-limit';
import * as z from 'zod/v4';
import { InvalidClientError, InvalidRequestError, OAuthError, ServerError, TooManyRequestsError } from '../errors.js';
import { allowedMethods } from '../middleware/allowedMethods.js';
import type { OAuthServerProvider } from '../provider.js';
export type AuthorizationHandlerOptions = {
provider: OAuthServerProvider;
/**
* Rate limiting configuration for the authorization endpoint.
* Set to false to disable rate limiting for this endpoint.
*/
rateLimit?: Partial<RateLimitOptions> | false;
};
const LOOPBACK_HOSTS = new Set(['localhost', '127.0.0.1', '[::1]']);
/**
* Validates a requested redirect_uri against a registered one.
*
* Per RFC 8252 §7.3 (OAuth 2.0 for Native Apps), authorization servers MUST
* allow any port for loopback redirect URIs (localhost, 127.0.0.1, [::1]) to
* accommodate native clients that obtain an ephemeral port from the OS. For
* non-loopback URIs, exact match is required.
*
* @see https://datatracker.ietf.org/doc/html/rfc8252#section-7.3
*/
export function redirectUriMatches(requested: string, registered: string): boolean {
if (requested === registered) {
return true;
}
let req: URL, reg: URL;
try {
req = new URL(requested);
reg = new URL(registered);
} catch {
return false;
}
// Port relaxation only applies when both URIs target a loopback host.
if (!LOOPBACK_HOSTS.has(req.hostname) || !LOOPBACK_HOSTS.has(reg.hostname)) {
return false;
}
// RFC 8252 relaxes the port only — scheme, host, path, and query must
// still match exactly. Note: hostname must match exactly too (the RFC
// does not allow localhost↔127.0.0.1 cross-matching).
return req.protocol === reg.protocol && req.hostname === reg.hostname && req.pathname === reg.pathname && req.search === reg.search;
}
// Parameters that must be validated in order to issue redirects.
const ClientAuthorizationParamsSchema = z.object({
client_id: z.string(),
redirect_uri: z
.string()
.optional()
.refine(value => value === undefined || URL.canParse(value), { message: 'redirect_uri must be a valid URL' })
});
// Parameters that must be validated for a successful authorization request. Failure can be reported to the redirect URI.
const RequestAuthorizationParamsSchema = z.object({
response_type: z.literal('code'),
code_challenge: z.string(),
code_challenge_method: z.literal('S256'),
scope: z.string().optional(),
state: z.string().optional(),
resource: z.string().url().optional()
});
export function authorizationHandler({ provider, rateLimit: rateLimitConfig }: AuthorizationHandlerOptions): RequestHandler {
// Create a router to apply middleware
const router = express.Router();
router.use(allowedMethods(['GET', 'POST']));
router.use(express.urlencoded({ extended: false }));
// Apply rate limiting unless explicitly disabled
if (rateLimitConfig !== false) {
router.use(
rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // 100 requests per windowMs
standardHeaders: true,
legacyHeaders: false,
message: new TooManyRequestsError('You have exceeded the rate limit for authorization requests').toResponseObject(),
...rateLimitConfig
})
);
}
router.all('/', async (req, res) => {
res.setHeader('Cache-Control', 'no-store');
// In the authorization flow, errors are split into two categories:
// 1. Pre-redirect errors (direct response with 400)
// 2. Post-redirect errors (redirect with error parameters)
// Phase 1: Validate client_id and redirect_uri. Any errors here must be direct responses.
let client_id, redirect_uri, client;
try {
const result = ClientAuthorizationParamsSchema.safeParse(req.method === 'POST' ? req.body : req.query);
if (!result.success) {
throw new InvalidRequestError(result.error.message);
}
client_id = result.data.client_id;
redirect_uri = result.data.redirect_uri;
client = await provider.clientsStore.getClient(client_id);
if (!client) {
throw new InvalidClientError('Invalid client_id');
}
if (redirect_uri !== undefined) {
const requested = redirect_uri;
if (!client.redirect_uris.some(registered => redirectUriMatches(requested, registered))) {
throw new InvalidRequestError('Unregistered redirect_uri');
}
} else if (client.redirect_uris.length === 1) {
redirect_uri = client.redirect_uris[0];
} else {
throw new InvalidRequestError('redirect_uri must be specified when client has multiple registered URIs');
}
} catch (error) {
// Pre-redirect errors - return direct response
//
// These don't need to be JSON encoded, as they'll be displayed in a user
// agent, but OTOH they all represent exceptional situations (arguably,
// "programmer error"), so presenting a nice HTML page doesn't help the
// user anyway.
if (error instanceof OAuthError) {
const status = error instanceof ServerError ? 500 : 400;
res.status(status).json(error.toResponseObject());
} else {
const serverError = new ServerError('Internal Server Error');
res.status(500).json(serverError.toResponseObject());
}
return;
}
// Phase 2: Validate other parameters. Any errors here should go into redirect responses.
let state;
try {
// Parse and validate authorization parameters
const parseResult = RequestAuthorizationParamsSchema.safeParse(req.method === 'POST' ? req.body : req.query);
if (!parseResult.success) {
throw new InvalidRequestError(parseResult.error.message);
}
const { scope, code_challenge, resource } = parseResult.data;
state = parseResult.data.state;
// Validate scopes
let requestedScopes: string[] = [];
if (scope !== undefined) {
requestedScopes = scope.split(' ');
}
// All validation passed, proceed with authorization
await provider.authorize(
client,
{
state,
scopes: requestedScopes,
redirectUri: redirect_uri!,
codeChallenge: code_challenge,
resource: resource ? new URL(resource) : undefined
},
res
);
} catch (error) {
// Post-redirect errors - redirect with error parameters
if (error instanceof OAuthError) {
res.redirect(302, createErrorRedirect(redirect_uri!, error, state));
} else {
const serverError = new ServerError('Internal Server Error');
res.redirect(302, createErrorRedirect(redirect_uri!, serverError, state));
}
}
});
return router;
}
/**
* Helper function to create redirect URL with error parameters
*/
function createErrorRedirect(redirectUri: string, error: OAuthError, state?: string): string {
const errorUrl = new URL(redirectUri);
errorUrl.searchParams.set('error', error.errorCode);
errorUrl.searchParams.set('error_description', error.message);
if (error.errorUri) {
errorUrl.searchParams.set('error_uri', error.errorUri);
}
if (state) {
errorUrl.searchParams.set('state', state);
}
return errorUrl.href;
}

View file

@ -0,0 +1,21 @@
import type { OAuthMetadata, OAuthProtectedResourceMetadata } from '@modelcontextprotocol/core';
import cors from 'cors';
import type { RequestHandler } from 'express';
import express from 'express';
import { allowedMethods } from '../middleware/allowedMethods.js';
export function metadataHandler(metadata: OAuthMetadata | OAuthProtectedResourceMetadata): RequestHandler {
// Nested router so we can configure middleware and restrict HTTP method
const router = express.Router();
// Configure CORS to allow any origin, to make accessible to web-based MCP clients
router.use(cors());
router.use(allowedMethods(['GET', 'OPTIONS']));
router.get('/', (req, res) => {
res.status(200).json(metadata);
});
return router;
}

View file

@ -0,0 +1,124 @@
import crypto from 'node:crypto';
import type { OAuthClientInformationFull } from '@modelcontextprotocol/core';
import { OAuthClientMetadataSchema } from '@modelcontextprotocol/core';
import cors from 'cors';
import type { RequestHandler } from 'express';
import express from 'express';
import type { Options as RateLimitOptions } from 'express-rate-limit';
import { rateLimit } from 'express-rate-limit';
import type { OAuthRegisteredClientsStore } from '../clients.js';
import { InvalidClientMetadataError, OAuthError, ServerError, TooManyRequestsError } from '../errors.js';
import { allowedMethods } from '../middleware/allowedMethods.js';
export type ClientRegistrationHandlerOptions = {
/**
* A store used to save information about dynamically registered OAuth clients.
*/
clientsStore: OAuthRegisteredClientsStore;
/**
* The number of seconds after which to expire issued client secrets, or 0 to prevent expiration of client secrets (not recommended).
*
* If not set, defaults to 30 days.
*/
clientSecretExpirySeconds?: number;
/**
* Rate limiting configuration for the client registration endpoint.
* Set to false to disable rate limiting for this endpoint.
* Registration endpoints are particularly sensitive to abuse and should be rate limited.
*/
rateLimit?: Partial<RateLimitOptions> | false;
/**
* Whether to generate a client ID before calling the client registration endpoint.
*
* If not set, defaults to true.
*/
clientIdGeneration?: boolean;
};
const DEFAULT_CLIENT_SECRET_EXPIRY_SECONDS = 30 * 24 * 60 * 60; // 30 days
export function clientRegistrationHandler({
clientsStore,
clientSecretExpirySeconds = DEFAULT_CLIENT_SECRET_EXPIRY_SECONDS,
rateLimit: rateLimitConfig,
clientIdGeneration = true
}: ClientRegistrationHandlerOptions): RequestHandler {
if (!clientsStore.registerClient) {
throw new Error('Client registration store does not support registering clients');
}
// Nested router so we can configure middleware and restrict HTTP method
const router = express.Router();
// Configure CORS to allow any origin, to make accessible to web-based MCP clients
router.use(cors());
router.use(allowedMethods(['POST']));
router.use(express.json());
// Apply rate limiting unless explicitly disabled - stricter limits for registration
if (rateLimitConfig !== false) {
router.use(
rateLimit({
windowMs: 60 * 60 * 1000, // 1 hour
max: 20, // 20 requests per hour - stricter as registration is sensitive
standardHeaders: true,
legacyHeaders: false,
message: new TooManyRequestsError('You have exceeded the rate limit for client registration requests').toResponseObject(),
...rateLimitConfig
})
);
}
router.post('/', async (req, res) => {
res.setHeader('Cache-Control', 'no-store');
try {
const parseResult = OAuthClientMetadataSchema.safeParse(req.body);
if (!parseResult.success) {
throw new InvalidClientMetadataError(parseResult.error.message);
}
const clientMetadata = parseResult.data;
const isPublicClient = clientMetadata.token_endpoint_auth_method === 'none';
// Generate client credentials
const clientSecret = isPublicClient ? undefined : crypto.randomBytes(32).toString('hex');
const clientIdIssuedAt = Math.floor(Date.now() / 1000);
// Calculate client secret expiry time
const clientsDoExpire = clientSecretExpirySeconds > 0;
const secretExpiryTime = clientsDoExpire ? clientIdIssuedAt + clientSecretExpirySeconds : 0;
const clientSecretExpiresAt = isPublicClient ? undefined : secretExpiryTime;
let clientInfo: Omit<OAuthClientInformationFull, 'client_id'> & { client_id?: string } = {
...clientMetadata,
client_secret: clientSecret,
client_secret_expires_at: clientSecretExpiresAt
};
if (clientIdGeneration) {
clientInfo.client_id = crypto.randomUUID();
clientInfo.client_id_issued_at = clientIdIssuedAt;
}
clientInfo = await clientsStore.registerClient!(clientInfo);
res.status(201).json(clientInfo);
} catch (error) {
if (error instanceof OAuthError) {
const status = error instanceof ServerError ? 500 : 400;
res.status(status).json(error.toResponseObject());
} else {
const serverError = new ServerError('Internal Server Error');
res.status(500).json(serverError.toResponseObject());
}
}
});
return router;
}

View file

@ -0,0 +1,82 @@
import { OAuthTokenRevocationRequestSchema } from '@modelcontextprotocol/core';
import cors from 'cors';
import type { RequestHandler } from 'express';
import express from 'express';
import type { Options as RateLimitOptions } from 'express-rate-limit';
import { rateLimit } from 'express-rate-limit';
import { InvalidRequestError, OAuthError, ServerError, TooManyRequestsError } from '../errors.js';
import { allowedMethods } from '../middleware/allowedMethods.js';
import { authenticateClient } from '../middleware/clientAuth.js';
import type { OAuthServerProvider } from '../provider.js';
export type RevocationHandlerOptions = {
provider: OAuthServerProvider;
/**
* Rate limiting configuration for the token revocation endpoint.
* Set to false to disable rate limiting for this endpoint.
*/
rateLimit?: Partial<RateLimitOptions> | false;
};
export function revocationHandler({ provider, rateLimit: rateLimitConfig }: RevocationHandlerOptions): RequestHandler {
if (!provider.revokeToken) {
throw new Error('Auth provider does not support revoking tokens');
}
// Nested router so we can configure middleware and restrict HTTP method
const router = express.Router();
// Configure CORS to allow any origin, to make accessible to web-based MCP clients
router.use(cors());
router.use(allowedMethods(['POST']));
router.use(express.urlencoded({ extended: false }));
// Apply rate limiting unless explicitly disabled
if (rateLimitConfig !== false) {
router.use(
rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 50, // 50 requests per windowMs
standardHeaders: true,
legacyHeaders: false,
message: new TooManyRequestsError('You have exceeded the rate limit for token revocation requests').toResponseObject(),
...rateLimitConfig
})
);
}
// Authenticate and extract client details
router.use(authenticateClient({ clientsStore: provider.clientsStore }));
router.post('/', async (req, res) => {
res.setHeader('Cache-Control', 'no-store');
try {
const parseResult = OAuthTokenRevocationRequestSchema.safeParse(req.body);
if (!parseResult.success) {
throw new InvalidRequestError(parseResult.error.message);
}
const client = req.client;
if (!client) {
// This should never happen
throw new ServerError('Internal Server Error');
}
await provider.revokeToken!(client, parseResult.data);
res.status(200).json({});
} catch (error) {
if (error instanceof OAuthError) {
const status = error instanceof ServerError ? 500 : 400;
res.status(status).json(error.toResponseObject());
} else {
const serverError = new ServerError('Internal Server Error');
res.status(500).json(serverError.toResponseObject());
}
}
});
return router;
}

View file

@ -0,0 +1,157 @@
import cors from 'cors';
import type { RequestHandler } from 'express';
import express from 'express';
import type { Options as RateLimitOptions } from 'express-rate-limit';
import { rateLimit } from 'express-rate-limit';
import { verifyChallenge } from 'pkce-challenge';
import * as z from 'zod/v4';
import {
InvalidGrantError,
InvalidRequestError,
OAuthError,
ServerError,
TooManyRequestsError,
UnsupportedGrantTypeError
} from '../errors.js';
import { allowedMethods } from '../middleware/allowedMethods.js';
import { authenticateClient } from '../middleware/clientAuth.js';
import type { OAuthServerProvider } from '../provider.js';
export type TokenHandlerOptions = {
provider: OAuthServerProvider;
/**
* Rate limiting configuration for the token endpoint.
* Set to false to disable rate limiting for this endpoint.
*/
rateLimit?: Partial<RateLimitOptions> | false;
};
const TokenRequestSchema = z.object({
grant_type: z.string()
});
const AuthorizationCodeGrantSchema = z.object({
code: z.string(),
code_verifier: z.string(),
redirect_uri: z.string().optional(),
resource: z.string().url().optional()
});
const RefreshTokenGrantSchema = z.object({
refresh_token: z.string(),
scope: z.string().optional(),
resource: z.string().url().optional()
});
export function tokenHandler({ provider, rateLimit: rateLimitConfig }: TokenHandlerOptions): RequestHandler {
// Nested router so we can configure middleware and restrict HTTP method
const router = express.Router();
// Configure CORS to allow any origin, to make accessible to web-based MCP clients
router.use(cors());
router.use(allowedMethods(['POST']));
router.use(express.urlencoded({ extended: false }));
// Apply rate limiting unless explicitly disabled
if (rateLimitConfig !== false) {
router.use(
rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 50, // 50 requests per windowMs
standardHeaders: true,
legacyHeaders: false,
message: new TooManyRequestsError('You have exceeded the rate limit for token requests').toResponseObject(),
...rateLimitConfig
})
);
}
// Authenticate and extract client details
router.use(authenticateClient({ clientsStore: provider.clientsStore }));
router.post('/', async (req, res) => {
res.setHeader('Cache-Control', 'no-store');
try {
const parseResult = TokenRequestSchema.safeParse(req.body);
if (!parseResult.success) {
throw new InvalidRequestError(parseResult.error.message);
}
const { grant_type } = parseResult.data;
const client = req.client;
if (!client) {
// This should never happen
throw new ServerError('Internal Server Error');
}
switch (grant_type) {
case 'authorization_code': {
const parseResult = AuthorizationCodeGrantSchema.safeParse(req.body);
if (!parseResult.success) {
throw new InvalidRequestError(parseResult.error.message);
}
const { code, code_verifier, redirect_uri, resource } = parseResult.data;
const skipLocalPkceValidation = provider.skipLocalPkceValidation;
// Perform local PKCE validation unless explicitly skipped
// (e.g. to validate code_verifier in upstream server)
if (!skipLocalPkceValidation) {
const codeChallenge = await provider.challengeForAuthorizationCode(client, code);
if (!(await verifyChallenge(code_verifier, codeChallenge))) {
throw new InvalidGrantError('code_verifier does not match the challenge');
}
}
// Passes the code_verifier to the provider if PKCE validation didn't occur locally
const tokens = await provider.exchangeAuthorizationCode(
client,
code,
skipLocalPkceValidation ? code_verifier : undefined,
redirect_uri,
resource ? new URL(resource) : undefined
);
res.status(200).json(tokens);
break;
}
case 'refresh_token': {
const parseResult = RefreshTokenGrantSchema.safeParse(req.body);
if (!parseResult.success) {
throw new InvalidRequestError(parseResult.error.message);
}
const { refresh_token, scope, resource } = parseResult.data;
const scopes = scope?.split(' ');
const tokens = await provider.exchangeRefreshToken(
client,
refresh_token,
scopes,
resource ? new URL(resource) : undefined
);
res.status(200).json(tokens);
break;
}
default: {
throw new UnsupportedGrantTypeError('The grant type is not supported by this authorization server.');
}
}
} catch (error) {
if (error instanceof OAuthError) {
const status = error instanceof ServerError ? 500 : 400;
res.status(status).json(error.toResponseObject());
} else {
const serverError = new ServerError('Internal Server Error');
res.status(500).json(serverError.toResponseObject());
}
}
});
return router;
}

View file

@ -0,0 +1,37 @@
// Core router
export type { AuthMetadataOptions, AuthRouterOptions } from './router.js';
export { createOAuthMetadata, getOAuthProtectedResourceMetadataUrl, mcpAuthMetadataRouter, mcpAuthRouter } from './router.js';
// Provider interfaces
export type { AuthorizationParams, OAuthServerProvider, OAuthTokenVerifier } from './provider.js';
// Proxy provider
export type { ProxyEndpoints, ProxyOptions } from './providers/proxyProvider.js';
export { ProxyOAuthServerProvider } from './providers/proxyProvider.js';
// Client store
export type { OAuthRegisteredClientsStore } from './clients.js';
// Handlers
export type { AuthorizationHandlerOptions } from './handlers/authorize.js';
export { authorizationHandler, redirectUriMatches } from './handlers/authorize.js';
export { metadataHandler } from './handlers/metadata.js';
export type { ClientRegistrationHandlerOptions } from './handlers/register.js';
export { clientRegistrationHandler } from './handlers/register.js';
export type { RevocationHandlerOptions } from './handlers/revoke.js';
export { revocationHandler } from './handlers/revoke.js';
export type { TokenHandlerOptions } from './handlers/token.js';
export { tokenHandler } from './handlers/token.js';
// Middleware
export { allowedMethods } from './middleware/allowedMethods.js';
export type { BearerAuthMiddlewareOptions } from './middleware/bearerAuth.js';
export { requireBearerAuth } from './middleware/bearerAuth.js';
export type { ClientAuthenticationMiddlewareOptions } from './middleware/clientAuth.js';
export { authenticateClient } from './middleware/clientAuth.js';
// Error classes
export * from './errors.js';
// Types
export type { AuthInfo } from './types.js';

Some files were not shown because too many files have changed in this diff Show more