Skip to content

Commit 099bdf1

Browse files
feat(compat): add @modelcontextprotocol/server-auth-legacy package
Frozen, deprecated copy of the v1 SDK's src/server/auth/ Authorization Server helpers (mcpAuthRouter, ProxyOAuthServerProvider, OAuth handlers, middleware, and error subclasses) as a standalone package for v1 -> v2 migration. The package carries a package.json "deprecated" field directing users to a dedicated IdP plus the Resource Server helpers in @modelcontextprotocol/express. Imports of OAuth types/schemas are rewritten to @modelcontextprotocol/core; AuthInfo is re-exported from core for structural compatibility with v2 request-handler context. Minimal edits vs v1 source: override modifiers and noUncheckedIndexedAccess fixes to satisfy the v2 strict tsconfig; behaviour is unchanged.
1 parent 9ed62fe commit 099bdf1

26 files changed

+1933
-8
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@modelcontextprotocol/server-auth-legacy': patch
3+
---
4+
5+
Add `@modelcontextprotocol/server-auth-legacy`, a deprecated, frozen copy of the v1 SDK's `src/server/auth/` Authorization Server helpers (`mcpAuthRouter`, `ProxyOAuthServerProvider`, OAuth handlers/middleware/errors). Provided solely for v1 → v2 migration; new code should use a dedicated IdP plus the Resource Server helpers in `@modelcontextprotocol/express`.

.changeset/pre.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"@modelcontextprotocol/hono": "2.0.0-alpha.0",
1818
"@modelcontextprotocol/node": "2.0.0-alpha.0",
1919
"@modelcontextprotocol/server": "2.0.0-alpha.0",
20+
"@modelcontextprotocol/server-auth-legacy": "2.0.0-alpha.2",
2021
"@modelcontextprotocol/test-conformance": "2.0.0-alpha.0",
2122
"@modelcontextprotocol/test-helpers": "2.0.0-alpha.0",
2223
"@modelcontextprotocol/test-integration": "2.0.0-alpha.0"
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# @modelcontextprotocol/server-auth-legacy
2+
3+
<!-- prettier-ignore -->
4+
> [!WARNING]
5+
> **Deprecated.** This package is a frozen copy of the v1 SDK's `src/server/auth/` Authorization Server helpers (`mcpAuthRouter`, `ProxyOAuthServerProvider`, etc.). It exists solely to ease migration from `@modelcontextprotocol/sdk` v1 and will not receive new features or non-critical bug fixes.
6+
7+
The v2 SDK no longer ships an OAuth Authorization Server implementation. MCP servers are Resource Servers; running your own AS is an anti-pattern for most deployments.
8+
9+
## Migration
10+
11+
- **Resource Server glue** (`requireBearerAuth`, `mcpAuthMetadataRouter`, Protected Resource Metadata): use the first-class helpers in `@modelcontextprotocol/express`.
12+
- **Authorization Server**: use a dedicated IdP (Auth0, Keycloak, Okta, etc.) or a purpose-built OAuth library.
13+
14+
## Usage (legacy)
15+
16+
```ts
17+
import express from 'express';
18+
import { mcpAuthRouter, ProxyOAuthServerProvider } from '@modelcontextprotocol/server-auth-legacy';
19+
20+
const app = express();
21+
app.use(mcpAuthRouter({ provider, issuerUrl: new URL('https://example.com') }));
22+
```
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// @ts-check
2+
3+
import baseConfig from '@modelcontextprotocol/eslint-config';
4+
5+
export default [
6+
...baseConfig,
7+
{
8+
settings: {
9+
'import/internal-regex': '^@modelcontextprotocol/core'
10+
}
11+
}
12+
];
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
{
2+
"name": "@modelcontextprotocol/server-auth-legacy",
3+
"private": false,
4+
"version": "2.0.0-alpha.2",
5+
"description": "Frozen v1 OAuth Authorization Server helpers (mcpAuthRouter, ProxyOAuthServerProvider) for the Model Context Protocol TypeScript SDK. Deprecated; use a dedicated OAuth server in production.",
6+
"deprecated": "The MCP SDK no longer ships an Authorization Server implementation. This package is a frozen copy of the v1 src/server/auth helpers for migration purposes only and will not receive new features. Use a dedicated OAuth Authorization Server (e.g. an IdP) and the Resource Server helpers in @modelcontextprotocol/express instead.",
7+
"license": "MIT",
8+
"author": "Anthropic, PBC (https://anthropic.com)",
9+
"homepage": "https://modelcontextprotocol.io",
10+
"bugs": "https://github.com/modelcontextprotocol/typescript-sdk/issues",
11+
"type": "module",
12+
"repository": {
13+
"type": "git",
14+
"url": "git+https://github.com/modelcontextprotocol/typescript-sdk.git"
15+
},
16+
"engines": {
17+
"node": ">=20"
18+
},
19+
"keywords": [
20+
"modelcontextprotocol",
21+
"mcp",
22+
"oauth",
23+
"express",
24+
"legacy"
25+
],
26+
"exports": {
27+
".": {
28+
"types": "./dist/index.d.mts",
29+
"import": "./dist/index.mjs"
30+
}
31+
},
32+
"files": [
33+
"dist"
34+
],
35+
"scripts": {
36+
"typecheck": "tsgo -p tsconfig.json --noEmit",
37+
"build": "tsdown",
38+
"build:watch": "tsdown --watch",
39+
"prepack": "npm run build",
40+
"lint": "eslint src/ && prettier --ignore-path ../../.prettierignore --check .",
41+
"lint:fix": "eslint src/ --fix && prettier --ignore-path ../../.prettierignore --write .",
42+
"check": "pnpm run typecheck && pnpm run lint",
43+
"test": "vitest run",
44+
"test:watch": "vitest"
45+
},
46+
"dependencies": {
47+
"cors": "catalog:runtimeServerOnly",
48+
"express-rate-limit": "^8.2.1",
49+
"pkce-challenge": "catalog:runtimeShared",
50+
"zod": "catalog:runtimeShared"
51+
},
52+
"peerDependencies": {
53+
"express": "catalog:runtimeServerOnly"
54+
},
55+
"devDependencies": {
56+
"@modelcontextprotocol/core": "workspace:^",
57+
"@modelcontextprotocol/tsconfig": "workspace:^",
58+
"@modelcontextprotocol/vitest-config": "workspace:^",
59+
"@modelcontextprotocol/eslint-config": "workspace:^",
60+
"@eslint/js": "catalog:devTools",
61+
"@types/cors": "catalog:devTools",
62+
"@types/express": "catalog:devTools",
63+
"@types/express-serve-static-core": "catalog:devTools",
64+
"@types/supertest": "catalog:devTools",
65+
"@typescript/native-preview": "catalog:devTools",
66+
"eslint": "catalog:devTools",
67+
"eslint-config-prettier": "catalog:devTools",
68+
"eslint-plugin-n": "catalog:devTools",
69+
"express": "catalog:runtimeServerOnly",
70+
"prettier": "catalog:devTools",
71+
"supertest": "catalog:devTools",
72+
"tsdown": "catalog:devTools",
73+
"typescript": "catalog:devTools",
74+
"typescript-eslint": "catalog:devTools",
75+
"vitest": "catalog:devTools"
76+
}
77+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import type { OAuthClientInformationFull } from '@modelcontextprotocol/core';
2+
3+
/**
4+
* Stores information about registered OAuth clients for this server.
5+
*/
6+
export interface OAuthRegisteredClientsStore {
7+
/**
8+
* Returns information about a registered client, based on its ID.
9+
*/
10+
getClient(clientId: string): OAuthClientInformationFull | undefined | Promise<OAuthClientInformationFull | undefined>;
11+
12+
/**
13+
* 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.
14+
*
15+
* 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.
16+
*
17+
* If unimplemented, dynamic client registration is unsupported.
18+
*/
19+
registerClient?(
20+
client: Omit<OAuthClientInformationFull, 'client_id' | 'client_id_issued_at'>
21+
): OAuthClientInformationFull | Promise<OAuthClientInformationFull>;
22+
}
Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
import type { OAuthErrorResponse } from '@modelcontextprotocol/core';
2+
3+
/**
4+
* Base class for all OAuth errors
5+
*/
6+
export class OAuthError extends Error {
7+
static errorCode: string;
8+
9+
constructor(
10+
message: string,
11+
public readonly errorUri?: string
12+
) {
13+
super(message);
14+
this.name = this.constructor.name;
15+
}
16+
17+
/**
18+
* Converts the error to a standard OAuth error response object
19+
*/
20+
toResponseObject(): OAuthErrorResponse {
21+
const response: OAuthErrorResponse = {
22+
error: this.errorCode,
23+
error_description: this.message
24+
};
25+
26+
if (this.errorUri) {
27+
response.error_uri = this.errorUri;
28+
}
29+
30+
return response;
31+
}
32+
33+
get errorCode(): string {
34+
return (this.constructor as typeof OAuthError).errorCode;
35+
}
36+
}
37+
38+
/**
39+
* Invalid request error - The request is missing a required parameter,
40+
* includes an invalid parameter value, includes a parameter more than once,
41+
* or is otherwise malformed.
42+
*/
43+
export class InvalidRequestError extends OAuthError {
44+
static override errorCode = 'invalid_request';
45+
}
46+
47+
/**
48+
* Invalid client error - Client authentication failed (e.g., unknown client, no client
49+
* authentication included, or unsupported authentication method).
50+
*/
51+
export class InvalidClientError extends OAuthError {
52+
static override errorCode = 'invalid_client';
53+
}
54+
55+
/**
56+
* Invalid grant error - The provided authorization grant or refresh token is
57+
* invalid, expired, revoked, does not match the redirection URI used in the
58+
* authorization request, or was issued to another client.
59+
*/
60+
export class InvalidGrantError extends OAuthError {
61+
static override errorCode = 'invalid_grant';
62+
}
63+
64+
/**
65+
* Unauthorized client error - The authenticated client is not authorized to use
66+
* this authorization grant type.
67+
*/
68+
export class UnauthorizedClientError extends OAuthError {
69+
static override errorCode = 'unauthorized_client';
70+
}
71+
72+
/**
73+
* Unsupported grant type error - The authorization grant type is not supported
74+
* by the authorization server.
75+
*/
76+
export class UnsupportedGrantTypeError extends OAuthError {
77+
static override errorCode = 'unsupported_grant_type';
78+
}
79+
80+
/**
81+
* Invalid scope error - The requested scope is invalid, unknown, malformed, or
82+
* exceeds the scope granted by the resource owner.
83+
*/
84+
export class InvalidScopeError extends OAuthError {
85+
static override errorCode = 'invalid_scope';
86+
}
87+
88+
/**
89+
* Access denied error - The resource owner or authorization server denied the request.
90+
*/
91+
export class AccessDeniedError extends OAuthError {
92+
static override errorCode = 'access_denied';
93+
}
94+
95+
/**
96+
* Server error - The authorization server encountered an unexpected condition
97+
* that prevented it from fulfilling the request.
98+
*/
99+
export class ServerError extends OAuthError {
100+
static override errorCode = 'server_error';
101+
}
102+
103+
/**
104+
* Temporarily unavailable error - The authorization server is currently unable to
105+
* handle the request due to a temporary overloading or maintenance of the server.
106+
*/
107+
export class TemporarilyUnavailableError extends OAuthError {
108+
static override errorCode = 'temporarily_unavailable';
109+
}
110+
111+
/**
112+
* Unsupported response type error - The authorization server does not support
113+
* obtaining an authorization code using this method.
114+
*/
115+
export class UnsupportedResponseTypeError extends OAuthError {
116+
static override errorCode = 'unsupported_response_type';
117+
}
118+
119+
/**
120+
* Unsupported token type error - The authorization server does not support
121+
* the requested token type.
122+
*/
123+
export class UnsupportedTokenTypeError extends OAuthError {
124+
static override errorCode = 'unsupported_token_type';
125+
}
126+
127+
/**
128+
* Invalid token error - The access token provided is expired, revoked, malformed,
129+
* or invalid for other reasons.
130+
*/
131+
export class InvalidTokenError extends OAuthError {
132+
static override errorCode = 'invalid_token';
133+
}
134+
135+
/**
136+
* Method not allowed error - The HTTP method used is not allowed for this endpoint.
137+
* (Custom, non-standard error)
138+
*/
139+
export class MethodNotAllowedError extends OAuthError {
140+
static override errorCode = 'method_not_allowed';
141+
}
142+
143+
/**
144+
* Too many requests error - Rate limit exceeded.
145+
* (Custom, non-standard error based on RFC 6585)
146+
*/
147+
export class TooManyRequestsError extends OAuthError {
148+
static override errorCode = 'too_many_requests';
149+
}
150+
151+
/**
152+
* Invalid client metadata error - The client metadata is invalid.
153+
* (Custom error for dynamic client registration - RFC 7591)
154+
*/
155+
export class InvalidClientMetadataError extends OAuthError {
156+
static override errorCode = 'invalid_client_metadata';
157+
}
158+
159+
/**
160+
* Insufficient scope error - The request requires higher privileges than provided by the access token.
161+
*/
162+
export class InsufficientScopeError extends OAuthError {
163+
static override errorCode = 'insufficient_scope';
164+
}
165+
166+
/**
167+
* Invalid target error - The requested resource is invalid, missing, unknown, or malformed.
168+
* (Custom error for resource indicators - RFC 8707)
169+
*/
170+
export class InvalidTargetError extends OAuthError {
171+
static override errorCode = 'invalid_target';
172+
}
173+
174+
/**
175+
* A utility class for defining one-off error codes
176+
*/
177+
export class CustomOAuthError extends OAuthError {
178+
constructor(
179+
private readonly customErrorCode: string,
180+
message: string,
181+
errorUri?: string
182+
) {
183+
super(message, errorUri);
184+
}
185+
186+
override get errorCode(): string {
187+
return this.customErrorCode;
188+
}
189+
}
190+
191+
/**
192+
* A full list of all OAuthErrors, enabling parsing from error responses
193+
*/
194+
export const OAUTH_ERRORS = {
195+
[InvalidRequestError.errorCode]: InvalidRequestError,
196+
[InvalidClientError.errorCode]: InvalidClientError,
197+
[InvalidGrantError.errorCode]: InvalidGrantError,
198+
[UnauthorizedClientError.errorCode]: UnauthorizedClientError,
199+
[UnsupportedGrantTypeError.errorCode]: UnsupportedGrantTypeError,
200+
[InvalidScopeError.errorCode]: InvalidScopeError,
201+
[AccessDeniedError.errorCode]: AccessDeniedError,
202+
[ServerError.errorCode]: ServerError,
203+
[TemporarilyUnavailableError.errorCode]: TemporarilyUnavailableError,
204+
[UnsupportedResponseTypeError.errorCode]: UnsupportedResponseTypeError,
205+
[UnsupportedTokenTypeError.errorCode]: UnsupportedTokenTypeError,
206+
[InvalidTokenError.errorCode]: InvalidTokenError,
207+
[MethodNotAllowedError.errorCode]: MethodNotAllowedError,
208+
[TooManyRequestsError.errorCode]: TooManyRequestsError,
209+
[InvalidClientMetadataError.errorCode]: InvalidClientMetadataError,
210+
[InsufficientScopeError.errorCode]: InsufficientScopeError,
211+
[InvalidTargetError.errorCode]: InvalidTargetError
212+
} as const;

0 commit comments

Comments
 (0)