Skip to content

Commit df49b7a

Browse files
committed
merge commit
2 parents 3dbc7c0 + 1fb7b25 commit df49b7a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1348
-742
lines changed

.npmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
registry = "https://registry.npmjs.org/"
2+
manage-package-manager-versions=false

docs/migration-SKILL.md

Lines changed: 199 additions & 80 deletions
Large diffs are not rendered by default.

docs/migration.md

Lines changed: 276 additions & 67 deletions
Large diffs are not rendered by default.

examples/client/src/elicitationUrlExample.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ import type {
2121
import {
2222
CallToolResultSchema,
2323
Client,
24-
ErrorCode,
2524
getDisplayName,
2625
ListToolsResultSchema,
27-
McpError,
26+
ProtocolError,
27+
ProtocolErrorCode,
2828
StreamableHTTPClientTransport,
2929
UnauthorizedError,
3030
UrlElicitationRequiredError
@@ -337,7 +337,7 @@ async function handleElicitationRequest(request: ElicitRequest): Promise<ElicitR
337337
} else {
338338
// Should not happen because the client declares its capabilities to the server,
339339
// but being defensive is a good practice:
340-
throw new McpError(ErrorCode.InvalidParams, `Unsupported elicitation mode: ${mode}`);
340+
throw new ProtocolError(ProtocolErrorCode.InvalidParams, `Unsupported elicitation mode: ${mode}`);
341341
}
342342
}
343343

examples/client/src/simpleStreamableHttp.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@ import type {
1212
import {
1313
CallToolResultSchema,
1414
Client,
15-
ErrorCode,
1615
getDisplayName,
1716
GetPromptResultSchema,
1817
ListPromptsResultSchema,
1918
ListResourcesResultSchema,
2019
ListToolsResultSchema,
21-
McpError,
20+
ProtocolError,
21+
ProtocolErrorCode,
2222
ReadResourceResultSchema,
2323
RELATED_TASK_META_KEY,
2424
StreamableHTTPClientTransport
@@ -270,7 +270,7 @@ async function connect(url?: string): Promise<void> {
270270
// Set up elicitation request handler with proper validation
271271
client.setRequestHandler('elicitation/create', async request => {
272272
if (request.params.mode !== 'form') {
273-
throw new McpError(ErrorCode.InvalidParams, `Unsupported elicitation mode: ${request.params.mode}`);
273+
throw new ProtocolError(ProtocolErrorCode.InvalidParams, `Unsupported elicitation mode: ${request.params.mode}`);
274274
}
275275
console.log('\n🔔 Elicitation (form) Request Received:');
276276
console.log(`Message: ${request.params.message}`);

examples/client/src/simpleTaskInteractiveClient.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,13 @@
1010
import { createInterface } from 'node:readline';
1111

1212
import type { CreateMessageRequest, CreateMessageResult, TextContent } from '@modelcontextprotocol/client';
13-
import { CallToolResultSchema, Client, ErrorCode, McpError, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
13+
import {
14+
CallToolResultSchema,
15+
Client,
16+
ProtocolError,
17+
ProtocolErrorCode,
18+
StreamableHTTPClientTransport
19+
} from '@modelcontextprotocol/client';
1420

1521
// Create readline interface for user input
1622
const readline = createInterface({
@@ -96,7 +102,7 @@ async function run(url: string): Promise<void> {
96102
// Set up elicitation request handler
97103
client.setRequestHandler('elicitation/create', async request => {
98104
if (request.params.mode && request.params.mode !== 'form') {
99-
throw new McpError(ErrorCode.InvalidParams, `Unsupported elicitation mode: ${request.params.mode}`);
105+
throw new ProtocolError(ProtocolErrorCode.InvalidParams, `Unsupported elicitation mode: ${request.params.mode}`);
100106
}
101107
return elicitationCallback(request.params);
102108
});
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/**
2+
* Example: Custom Protocol Version Support
3+
*
4+
* This demonstrates how to support protocol versions not yet in the SDK.
5+
* First version in the list is used as fallback when client requests
6+
* an unsupported version.
7+
*
8+
* Run with: pnpm tsx src/customProtocolVersion.ts
9+
*/
10+
11+
import { randomUUID } from 'node:crypto';
12+
import { createServer } from 'node:http';
13+
14+
import { NodeStreamableHTTPServerTransport } from '@modelcontextprotocol/node';
15+
import type { CallToolResult } from '@modelcontextprotocol/server';
16+
import { McpServer, SUPPORTED_PROTOCOL_VERSIONS } from '@modelcontextprotocol/server';
17+
18+
// Add support for a newer protocol version (first in list is fallback)
19+
const CUSTOM_VERSIONS = ['2026-01-01', ...SUPPORTED_PROTOCOL_VERSIONS];
20+
21+
const server = new McpServer(
22+
{ name: 'custom-protocol-server', version: '1.0.0' },
23+
{
24+
supportedProtocolVersions: CUSTOM_VERSIONS,
25+
capabilities: { tools: {} }
26+
}
27+
);
28+
29+
// Register a tool that shows the protocol configuration
30+
server.registerTool(
31+
'get-protocol-info',
32+
{
33+
title: 'Protocol Info',
34+
description: 'Returns protocol version configuration',
35+
inputSchema: {}
36+
},
37+
async (): Promise<CallToolResult> => ({
38+
content: [
39+
{
40+
type: 'text',
41+
text: JSON.stringify({ supportedVersions: CUSTOM_VERSIONS }, null, 2)
42+
}
43+
]
44+
})
45+
);
46+
47+
// Create transport - server passes versions automatically during connect()
48+
const transport = new NodeStreamableHTTPServerTransport({
49+
sessionIdGenerator: () => randomUUID()
50+
});
51+
52+
await server.connect(transport);
53+
54+
// Simple HTTP server
55+
const PORT = process.env.MCP_PORT ? Number.parseInt(process.env.MCP_PORT, 10) : 3000;
56+
57+
createServer(async (req, res) => {
58+
if (req.url === '/mcp') {
59+
await transport.handleRequest(req, res);
60+
} else {
61+
res.writeHead(404).end('Not Found');
62+
}
63+
}).listen(PORT, () => {
64+
console.log(`MCP server with custom protocol versions on port ${PORT}`);
65+
console.log(`Supported versions: ${CUSTOM_VERSIONS.join(', ')}`);
66+
});

package.json

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,12 @@
3333
"lint:fix:all": "pnpm -r lint:fix",
3434
"check:all": "pnpm -r typecheck && pnpm -r lint",
3535
"test:all": "pnpm -r test",
36-
"test:conformance:client": "conformance client --command 'npx tsx src/conformance/everything-client.ts'",
37-
"test:conformance:client:all": "conformance client --command 'npx tsx src/conformance/everything-client.ts' --suite all",
38-
"test:conformance:client:run": "npx tsx src/conformance/everything-client.ts",
39-
"test:conformance:server": "scripts/run-server-conformance.sh",
40-
"test:conformance:server:all": "scripts/run-server-conformance.sh --suite all",
41-
"test:conformance:server:run": "npx tsx src/conformance/everything-server.ts",
36+
"test:conformance:client": "pnpm --filter @modelcontextprotocol/test-conformance run test:conformance:client",
37+
"test:conformance:client:all": "pnpm --filter @modelcontextprotocol/test-conformance run test:conformance:client:all",
38+
"test:conformance:client:run": "pnpm --filter @modelcontextprotocol/test-conformance run test:conformance:client:run",
39+
"test:conformance:server": "pnpm --filter @modelcontextprotocol/test-conformance run test:conformance:server",
40+
"test:conformance:server:all": "pnpm --filter @modelcontextprotocol/test-conformance run test:conformance:server:all",
41+
"test:conformance:server:run": "pnpm --filter @modelcontextprotocol/test-conformance run test:conformance:server:run",
4242
"test:conformance:all": "pnpm run test:conformance:client:all && pnpm run test:conformance:server:all"
4343
},
4444
"devDependencies": {
@@ -47,7 +47,6 @@
4747
"@changesets/cli": "^2.29.8",
4848
"@eslint/js": "catalog:devTools",
4949
"@modelcontextprotocol/client": "workspace:^",
50-
"@modelcontextprotocol/conformance": "0.1.10",
5150
"@modelcontextprotocol/server": "workspace:^",
5251
"@modelcontextprotocol/node": "workspace:^",
5352
"@types/content-type": "catalog:devTools",
@@ -60,12 +59,10 @@
6059
"@types/supertest": "catalog:devTools",
6160
"@types/ws": "catalog:devTools",
6261
"@typescript/native-preview": "catalog:devTools",
63-
"cors": "catalog:runtimeServerOnly",
6462
"eslint": "catalog:devTools",
6563
"eslint-config-prettier": "catalog:devTools",
6664
"eslint-plugin-n": "catalog:devTools",
6765
"fast-glob": "^3.3.3",
68-
"express": "catalog:runtimeServerOnly",
6966
"prettier": "catalog:devTools",
7067
"supertest": "catalog:devTools",
7168
"tsdown": "catalog:devTools",

packages/client/src/client/auth.ts

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,16 @@ import type {
1111
} from '@modelcontextprotocol/core';
1212
import {
1313
checkResourceAllowed,
14-
InvalidClientError,
15-
InvalidClientMetadataError,
16-
InvalidGrantError,
1714
LATEST_PROTOCOL_VERSION,
18-
OAUTH_ERRORS,
1915
OAuthClientInformationFullSchema,
2016
OAuthError,
17+
OAuthErrorCode,
2118
OAuthErrorResponseSchema,
2219
OAuthMetadataSchema,
2320
OAuthProtectedResourceMetadataSchema,
2421
OAuthTokensSchema,
2522
OpenIdProviderDiscoveryMetadataSchema,
26-
resourceUrlFromServerUrl,
27-
ServerError,
28-
UnauthorizedClientError
23+
resourceUrlFromServerUrl
2924
} from '@modelcontextprotocol/core';
3025
import pkceChallenge from 'pkce-challenge';
3126

@@ -328,7 +323,7 @@ function applyPublicAuth(clientId: string, params: URLSearchParams): void {
328323
* Parses an OAuth error response from a string or Response object.
329324
*
330325
* If the input is a standard OAuth2.0 error response, it will be parsed according to the spec
331-
* and an instance of the appropriate OAuthError subclass will be returned.
326+
* and an OAuthError will be returned with the appropriate error code.
332327
* If parsing fails, it falls back to a generic ServerError that includes
333328
* the response status (if available) and original content.
334329
*
@@ -341,13 +336,11 @@ export async function parseErrorResponse(input: Response | string): Promise<OAut
341336

342337
try {
343338
const result = OAuthErrorResponseSchema.parse(JSON.parse(body));
344-
const { error, error_description, error_uri } = result;
345-
const errorClass = OAUTH_ERRORS[error] || ServerError;
346-
return new errorClass(error_description || '', error_uri);
339+
return OAuthError.fromResponse(result);
347340
} catch (error) {
348341
// Not a valid OAuth error response, but try to inform the user of the raw data anyway
349342
const errorMessage = `${statusCode ? `HTTP ${statusCode}: ` : ''}Invalid OAuth error response: ${error}. Raw body: ${body}`;
350-
return new ServerError(errorMessage);
343+
return new OAuthError(OAuthErrorCode.ServerError, errorMessage);
351344
}
352345
}
353346

@@ -371,12 +364,14 @@ export async function auth(
371364
return await authInternal(provider, options);
372365
} catch (error) {
373366
// Handle recoverable error types by invalidating credentials and retrying
374-
if (error instanceof InvalidClientError || error instanceof UnauthorizedClientError) {
375-
await provider.invalidateCredentials?.('all');
376-
return await authInternal(provider, options);
377-
} else if (error instanceof InvalidGrantError) {
378-
await provider.invalidateCredentials?.('tokens');
379-
return await authInternal(provider, options);
367+
if (error instanceof OAuthError) {
368+
if (error.code === OAuthErrorCode.InvalidClient || error.code === OAuthErrorCode.UnauthorizedClient) {
369+
await provider.invalidateCredentials?.('all');
370+
return await authInternal(provider, options);
371+
} else if (error.code === OAuthErrorCode.InvalidGrant) {
372+
await provider.invalidateCredentials?.('tokens');
373+
return await authInternal(provider, options);
374+
}
380375
}
381376

382377
// Throw otherwise
@@ -437,7 +432,8 @@ async function authInternal(
437432
const clientMetadataUrl = provider.clientMetadataUrl;
438433

439434
if (clientMetadataUrl && !isHttpsUrl(clientMetadataUrl)) {
440-
throw new InvalidClientMetadataError(
435+
throw new OAuthError(
436+
OAuthErrorCode.InvalidClientMetadata,
441437
`clientMetadataUrl must be a valid HTTPS URL with a non-root pathname, got: ${clientMetadataUrl}`
442438
);
443439
}
@@ -502,7 +498,7 @@ async function authInternal(
502498
return 'AUTHORIZED';
503499
} catch (error) {
504500
// If this is a ServerError, or an unknown type, log it out and try to continue. Otherwise, escalate so we can fix things and retry.
505-
if (!(error instanceof OAuthError) || error instanceof ServerError) {
501+
if (!(error instanceof OAuthError) || error.code === OAuthErrorCode.ServerError) {
506502
// Could not refresh OAuth tokens
507503
} else {
508504
// Refresh failed for another reason, re-throw

0 commit comments

Comments
 (0)