Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/add-server-auth-legacy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@modelcontextprotocol/server-auth-legacy': patch
---

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`.
1 change: 1 addition & 0 deletions .changeset/pre.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"@modelcontextprotocol/hono": "2.0.0-alpha.0",
"@modelcontextprotocol/node": "2.0.0-alpha.0",
"@modelcontextprotocol/server": "2.0.0-alpha.0",
"@modelcontextprotocol/server-auth-legacy": "2.0.0-alpha.2",
"@modelcontextprotocol/test-conformance": "2.0.0-alpha.0",
"@modelcontextprotocol/test-helpers": "2.0.0-alpha.0",
"@modelcontextprotocol/test-integration": "2.0.0-alpha.0"
Expand Down
22 changes: 22 additions & 0 deletions packages/server-auth-legacy/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# @modelcontextprotocol/server-auth-legacy

<!-- prettier-ignore -->
> [!WARNING]
> **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.

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.

## Migration

- **Resource Server glue** (`requireBearerAuth`, `mcpAuthMetadataRouter`, Protected Resource Metadata): use the first-class helpers in `@modelcontextprotocol/express`.
- **Authorization Server**: use a dedicated IdP (Auth0, Keycloak, Okta, etc.) or a purpose-built OAuth library.

## Usage (legacy)

```ts
import express from 'express';
import { mcpAuthRouter, ProxyOAuthServerProvider } from '@modelcontextprotocol/server-auth-legacy';

const app = express();
app.use(mcpAuthRouter({ provider, issuerUrl: new URL('https://example.com') }));
```
12 changes: 12 additions & 0 deletions packages/server-auth-legacy/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// @ts-check

import baseConfig from '@modelcontextprotocol/eslint-config';

export default [
...baseConfig,
{
settings: {
'import/internal-regex': '^@modelcontextprotocol/core'
}
}
];
77 changes: 77 additions & 0 deletions packages/server-auth-legacy/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
{
"name": "@modelcontextprotocol/server-auth-legacy",
"private": false,
"version": "2.0.0-alpha.2",
"description": "Frozen v1 OAuth Authorization Server helpers (mcpAuthRouter, ProxyOAuthServerProvider) for the Model Context Protocol TypeScript SDK. Deprecated; use a dedicated OAuth server in production.",
"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.",
"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",
"oauth",
"express",
"legacy"
],
"exports": {
".": {
"types": "./dist/index.d.mts",
"import": "./dist/index.mjs"
}
},
"files": [
"dist"
],
"scripts": {
"typecheck": "tsgo -p tsconfig.json --noEmit",
"build": "tsdown",
"build:watch": "tsdown --watch",
"prepack": "npm 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": {
"cors": "catalog:runtimeServerOnly",
"express-rate-limit": "^8.2.1",
"pkce-challenge": "catalog:runtimeShared",
"zod": "catalog:runtimeShared"
},
"peerDependencies": {
"express": "catalog:runtimeServerOnly"
},
"devDependencies": {
"@modelcontextprotocol/core": "workspace:^",
"@modelcontextprotocol/tsconfig": "workspace:^",
"@modelcontextprotocol/vitest-config": "workspace:^",
"@modelcontextprotocol/eslint-config": "workspace:^",
"@eslint/js": "catalog:devTools",
"@types/cors": "catalog:devTools",
"@types/express": "catalog:devTools",
"@types/express-serve-static-core": "catalog:devTools",
"@types/supertest": "catalog:devTools",
"@typescript/native-preview": "catalog:devTools",
"eslint": "catalog:devTools",
"eslint-config-prettier": "catalog:devTools",
"eslint-plugin-n": "catalog:devTools",
"express": "catalog:runtimeServerOnly",
"prettier": "catalog:devTools",
"supertest": "catalog:devTools",
"tsdown": "catalog:devTools",
"typescript": "catalog:devTools",
"typescript-eslint": "catalog:devTools",
"vitest": "catalog:devTools"
}
}
22 changes: 22 additions & 0 deletions packages/server-auth-legacy/src/clients.ts
Original file line number Diff line number Diff line change
@@ -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>;
}
212 changes: 212 additions & 0 deletions packages/server-auth-legacy/src/errors.ts
Original file line number Diff line number Diff line change
@@ -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;
Loading
Loading