v2: Errors refactor (ProtocolError, SdkError, OAuthError) (#1454)

This commit is contained in:
Konstantin Konstantinov 2026-02-03 18:41:22 +02:00 • committed by GitHub
commit 0f0a4eb7c8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 1086 additions and 705 deletions

View file

@ -4,11 +4,12 @@ import {
CallToolResultSchema,
CreateTaskResultSchema,
ElicitResultSchema,
ErrorCode,
InMemoryTransport,
LATEST_PROTOCOL_VERSION,
ListToolsResultSchema,
McpError,
ProtocolErrorCode,
SdkError,
SdkErrorCode,
SUPPORTED_PROTOCOL_VERSIONS
} from '@modelcontextprotocol/core';
import { InMemoryTaskStore, McpServer, Server } from '@modelcontextprotocol/server';
@ -815,7 +816,7 @@ test('should reject form-mode elicitation when client only supports URL mode', a
const response = (await responsePromise) as { id: number; error: { code: number; message: string } };
expect(response.id).toBe(requestId);
expect(response.error.code).toBe(ErrorCode.InvalidParams);
expect(response.error.code).toBe(ProtocolErrorCode.InvalidParams);
expect(response.error.message).toContain('Client does not support form-mode elicitation requests');
expect(handler).not.toHaveBeenCalled();
@ -954,7 +955,7 @@ test('should reject URL-mode elicitation when client only supports form mode', a
const response = (await responsePromise) as { id: number; error: { code: number; message: string } };
expect(response.id).toBe(requestId);
expect(response.error.code).toBe(ErrorCode.InvalidParams);
expect(response.error.code).toBe(ProtocolErrorCode.InvalidParams);
expect(response.error.message).toContain('Client does not support URL-mode elicitation requests');
expect(handler).not.toHaveBeenCalled();
@ -1074,8 +1075,8 @@ test('should handle client cancelling a request', async () => {
});
controller.abort('Cancelled by test');
// Request should be rejected with an McpError
await expect(listResourcesPromise).rejects.toThrow(McpError);
// Request should be rejected with an SdkError (local timeout/cancellation)
await expect(listResourcesPromise).rejects.toThrow(SdkError);
});
/***
@ -1123,7 +1124,7 @@ test('should handle request timeout', async () => {
// Request with 0 msec timeout should fail immediately
await expect(client.listResources(undefined, { timeout: 0 })).rejects.toMatchObject({
code: ErrorCode.RequestTimeout
code: SdkErrorCode.RequestTimeout
});
});

View file

@ -1,4 +1,4 @@
import { ErrorCode, McpError } from '@modelcontextprotocol/core';
import { ProtocolError, ProtocolErrorCode } from '@modelcontextprotocol/core';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { createInMemoryTaskEnvironment } from '../../helpers/mcp.js';
@ -88,9 +88,9 @@ describe('Task Listing with Pagination', () => {
});
// Try to use an invalid cursor - should return -32602 (Invalid params) per MCP spec
await expect(client.experimental.tasks.listTasks('invalid-cursor')).rejects.toSatisfy((error: McpError) => {
expect(error).toBeInstanceOf(McpError);
expect(error.code).toBe(ErrorCode.InvalidParams);
await expect(client.experimental.tasks.listTasks('invalid-cursor')).rejects.toSatisfy((error: ProtocolError) => {
expect(error).toBeInstanceOf(ProtocolError);
expect(error.code).toBe(ProtocolErrorCode.InvalidParams);
expect(error.message).toContain('Invalid cursor');
return true;
});

View file

@ -13,10 +13,12 @@ import {
CreateMessageResultSchema,
CreateTaskResultSchema,
ElicitResultSchema,
ErrorCode,
InMemoryTransport,
LATEST_PROTOCOL_VERSION,
McpError,
ProtocolError,
ProtocolErrorCode,
SdkError,
SdkErrorCode,
SUPPORTED_PROTOCOL_VERSIONS
} from '@modelcontextprotocol/core';
import { createMcpExpressApp } from '@modelcontextprotocol/express';
@ -1356,8 +1358,8 @@ test('should handle server cancelling a request', async () => {
);
controller.abort('Cancelled by test');
// Request should be rejected with an McpError
await expect(createMessagePromise).rejects.toThrow(McpError);
// Request should be rejected with an SdkError (local timeout/cancellation)
await expect(createMessagePromise).rejects.toThrow(SdkError);
});
test('should handle request timeout', async () => {
@ -1417,7 +1419,7 @@ test('should handle request timeout', async () => {
{ timeout: 0 }
)
).rejects.toMatchObject({
code: ErrorCode.RequestTimeout
code: SdkErrorCode.RequestTimeout
});
});

View file

@ -3,7 +3,6 @@ import type { CallToolResult, Notification, TextContent } from '@modelcontextpro
import {
CallToolResultSchema,
CompleteResultSchema,
ErrorCode,
getDisplayName,
GetPromptResultSchema,
InMemoryTaskStore,
@ -12,6 +11,7 @@ import {
ListResourcesResultSchema,
ListResourceTemplatesResultSchema,
ListToolsResultSchema,
ProtocolErrorCode,
ReadResourceResultSchema,
UriTemplate,
UrlElicitationRequiredError
@ -1571,9 +1571,9 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
});
/***
* Test: McpError for Invalid Tool Name
* Test: ProtocolError for Invalid Tool Name
*/
test('should throw McpError for invalid tool name', async () => {
test('should throw ProtocolError for invalid tool name', async () => {
const mcpServer = new McpServer({
name: 'test server',
version: '1.0'
@ -1666,7 +1666,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
.catch(error => {
expect(error).toBeInstanceOf(UrlElicitationRequiredError);
if (error instanceof UrlElicitationRequiredError) {
expect(error.code).toBe(ErrorCode.UrlElicitationRequired);
expect(error.code).toBe(ProtocolErrorCode.UrlElicitationRequired);
expect(error.elicitations).toEqual([elicitationParams]);
}
});
@ -2612,9 +2612,9 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
});
/***
* Test: McpError for Invalid Resource URI
* Test: ProtocolError for Invalid Resource URI
*/
test('should throw McpError for invalid resource URI', async () => {
test('should throw ProtocolError for invalid resource URI', async () => {
const mcpServer = new McpServer({
name: 'test server',
version: '1.0'
@ -3537,9 +3537,9 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
});
/***
* Test: McpError for Invalid Prompt Name
* Test: ProtocolError for Invalid Prompt Name
*/
test('should throw McpError for invalid prompt name', async () => {
test('should throw ProtocolError for invalid prompt name', async () => {
const mcpServer = new McpServer({
name: 'test server',
version: '1.0'

View file

@ -9,11 +9,11 @@ import {
CallToolResultSchema,
CreateTaskResultSchema,
ElicitResultSchema,
ErrorCode,
InMemoryTaskMessageQueue,
InMemoryTaskStore,
McpError,
McpServer,
ProtocolError,
ProtocolErrorCode,
RELATED_TASK_META_KEY,
TaskSchema
} from '@modelcontextprotocol/server';
@ -394,9 +394,9 @@ describe('Task Lifecycle Integration Tests', () => {
expect(task.status).toBe('completed');
// Try to cancel via tasks/cancel request (should fail with -32602)
await expect(client.experimental.tasks.cancelTask(taskId)).rejects.toSatisfy((error: McpError) => {
expect(error).toBeInstanceOf(McpError);
expect(error.code).toBe(ErrorCode.InvalidParams);
await expect(client.experimental.tasks.cancelTask(taskId)).rejects.toSatisfy((error: ProtocolError) => {
expect(error).toBeInstanceOf(ProtocolError);
expect(error.code).toBe(ProtocolErrorCode.InvalidParams);
expect(error.message).toContain('Cannot cancel task in terminal status');
return true;
});
@ -788,9 +788,9 @@ describe('Task Lifecycle Integration Tests', () => {
await client.connect(transport);
// Try to get non-existent task via tasks/get request
await expect(client.experimental.tasks.getTask('non-existent-task-id')).rejects.toSatisfy((error: McpError) => {
expect(error).toBeInstanceOf(McpError);
expect(error.code).toBe(ErrorCode.InvalidParams);
await expect(client.experimental.tasks.getTask('non-existent-task-id')).rejects.toSatisfy((error: ProtocolError) => {
expect(error).toBeInstanceOf(ProtocolError);
expect(error.code).toBe(ProtocolErrorCode.InvalidParams);
expect(error.message).toContain('Task not found');
return true;
});
@ -808,9 +808,9 @@ describe('Task Lifecycle Integration Tests', () => {
await client.connect(transport);
// Try to cancel non-existent task via tasks/cancel request
await expect(client.experimental.tasks.cancelTask('non-existent-task-id')).rejects.toSatisfy((error: McpError) => {
expect(error).toBeInstanceOf(McpError);
expect(error.code).toBe(ErrorCode.InvalidParams);
await expect(client.experimental.tasks.cancelTask('non-existent-task-id')).rejects.toSatisfy((error: ProtocolError) => {
expect(error).toBeInstanceOf(ProtocolError);
expect(error.code).toBe(ProtocolErrorCode.InvalidParams);
expect(error.message).toContain('Task not found');
return true;
});
@ -836,9 +836,9 @@ describe('Task Lifecycle Integration Tests', () => {
},
CallToolResultSchema
)
).rejects.toSatisfy((error: McpError) => {
expect(error).toBeInstanceOf(McpError);
expect(error.code).toBe(ErrorCode.InvalidParams);
).rejects.toSatisfy((error: ProtocolError) => {
expect(error).toBeInstanceOf(ProtocolError);
expect(error.code).toBe(ProtocolErrorCode.InvalidParams);
expect(error.message).toContain('Task not found');
return true;
});