diff --git a/packages/client/src/client/client.ts b/packages/client/src/client/client.ts index 0c6fc67e5..7d0e57433 100644 --- a/packages/client/src/client/client.ts +++ b/packages/client/src/client/client.ts @@ -67,7 +67,8 @@ import { ResourceListChangedNotificationSchema, safeParse, SUPPORTED_PROTOCOL_VERSIONS, - ToolListChangedNotificationSchema + ToolListChangedNotificationSchema, + UnsupportedCapabilityError } from '@modelcontextprotocol/core'; import { ExperimentalClientTasks } from '../experimental/tasks/client.js'; @@ -482,7 +483,7 @@ export class Client< protected assertCapability(capability: keyof ServerCapabilities, method: string): void { if (!this._serverCapabilities?.[capability]) { - throw new Error(`Server does not support ${capability} (required for ${method})`); + throw new UnsupportedCapabilityError(`Server does not support ${capability} (required for ${method})`); } } @@ -565,14 +566,14 @@ export class Client< switch (method as ClientRequest['method']) { case 'logging/setLevel': if (!this._serverCapabilities?.logging) { - throw new Error(`Server does not support logging (required for ${method})`); + throw new UnsupportedCapabilityError(`Server does not support logging (required for ${method})`); } break; case 'prompts/get': case 'prompts/list': if (!this._serverCapabilities?.prompts) { - throw new Error(`Server does not support prompts (required for ${method})`); + throw new UnsupportedCapabilityError(`Server does not support prompts (required for ${method})`); } break; @@ -582,11 +583,11 @@ export class Client< case 'resources/subscribe': case 'resources/unsubscribe': if (!this._serverCapabilities?.resources) { - throw new Error(`Server does not support resources (required for ${method})`); + throw new UnsupportedCapabilityError(`Server does not support resources (required for ${method})`); } if (method === 'resources/subscribe' && !this._serverCapabilities.resources.subscribe) { - throw new Error(`Server does not support resource subscriptions (required for ${method})`); + throw new UnsupportedCapabilityError(`Server does not support resource subscriptions (required for ${method})`); } break; @@ -594,13 +595,13 @@ export class Client< case 'tools/call': case 'tools/list': if (!this._serverCapabilities?.tools) { - throw new Error(`Server does not support tools (required for ${method})`); + throw new UnsupportedCapabilityError(`Server does not support tools (required for ${method})`); } break; case 'completion/complete': if (!this._serverCapabilities?.completions) { - throw new Error(`Server does not support completions (required for ${method})`); + throw new UnsupportedCapabilityError(`Server does not support completions (required for ${method})`); } break; @@ -618,7 +619,9 @@ export class Client< switch (method as ClientNotification['method']) { case 'notifications/roots/list_changed': if (!this._capabilities.roots?.listChanged) { - throw new Error(`Client does not support roots list changed notifications (required for ${method})`); + throw new UnsupportedCapabilityError( + `Client does not support roots list changed notifications (required for ${method})` + ); } break; @@ -646,19 +649,19 @@ export class Client< switch (method) { case 'sampling/createMessage': if (!this._capabilities.sampling) { - throw new Error(`Client does not support sampling capability (required for ${method})`); + throw new UnsupportedCapabilityError(`Client does not support sampling capability (required for ${method})`); } break; case 'elicitation/create': if (!this._capabilities.elicitation) { - throw new Error(`Client does not support elicitation capability (required for ${method})`); + throw new UnsupportedCapabilityError(`Client does not support elicitation capability (required for ${method})`); } break; case 'roots/list': if (!this._capabilities.roots) { - throw new Error(`Client does not support roots capability (required for ${method})`); + throw new UnsupportedCapabilityError(`Client does not support roots capability (required for ${method})`); } break; @@ -667,7 +670,7 @@ export class Client< case 'tasks/result': case 'tasks/cancel': if (!this._capabilities.tasks) { - throw new Error(`Client does not support tasks capability (required for ${method})`); + throw new UnsupportedCapabilityError(`Client does not support tasks capability (required for ${method})`); } break; diff --git a/packages/core/src/experimental/tasks/helpers.ts b/packages/core/src/experimental/tasks/helpers.ts index 34b15188f..eee5b04d1 100644 --- a/packages/core/src/experimental/tasks/helpers.ts +++ b/packages/core/src/experimental/tasks/helpers.ts @@ -5,6 +5,8 @@ * @experimental */ +import { UnsupportedCapabilityError } from '../../types/types.js'; + /** * Type representing the task requests capability structure. * This is derived from ClientTasksCapability.requests and ServerTasksCapability.requests. @@ -22,7 +24,7 @@ interface TaskRequestsCapability { * @param requests - The task requests capability object * @param method - The method being checked * @param entityName - 'Server' or 'Client' for error messages - * @throws Error if the capability is not supported + * @throws UnsupportedCapabilityError if the capability is not supported * * @experimental */ @@ -32,13 +34,15 @@ export function assertToolsCallTaskCapability( entityName: 'Server' | 'Client' ): void { if (!requests) { - throw new Error(`${entityName} does not support task creation (required for ${method})`); + throw new UnsupportedCapabilityError(`${entityName} does not support task creation (required for ${method})`); } switch (method) { case 'tools/call': if (!requests.tools?.call) { - throw new Error(`${entityName} does not support task creation for tools/call (required for ${method})`); + throw new UnsupportedCapabilityError( + `${entityName} does not support task creation for tools/call (required for ${method})` + ); } break; @@ -55,7 +59,7 @@ export function assertToolsCallTaskCapability( * @param requests - The task requests capability object * @param method - The method being checked * @param entityName - 'Server' or 'Client' for error messages - * @throws Error if the capability is not supported + * @throws UnsupportedCapabilityError if the capability is not supported * * @experimental */ @@ -65,19 +69,23 @@ export function assertClientRequestTaskCapability( entityName: 'Server' | 'Client' ): void { if (!requests) { - throw new Error(`${entityName} does not support task creation (required for ${method})`); + throw new UnsupportedCapabilityError(`${entityName} does not support task creation (required for ${method})`); } switch (method) { case 'sampling/createMessage': if (!requests.sampling?.createMessage) { - throw new Error(`${entityName} does not support task creation for sampling/createMessage (required for ${method})`); + throw new UnsupportedCapabilityError( + `${entityName} does not support task creation for sampling/createMessage (required for ${method})` + ); } break; case 'elicitation/create': if (!requests.elicitation?.create) { - throw new Error(`${entityName} does not support task creation for elicitation/create (required for ${method})`); + throw new UnsupportedCapabilityError( + `${entityName} does not support task creation for elicitation/create (required for ${method})` + ); } break; diff --git a/packages/core/src/types/types.ts b/packages/core/src/types/types.ts index f3e1b92a8..6d306639a 100644 --- a/packages/core/src/types/types.ts +++ b/packages/core/src/types/types.ts @@ -2369,6 +2369,12 @@ export class UrlElicitationRequiredError extends McpError { } } +export class UnsupportedCapabilityError extends Error { + constructor(message: string) { + super(message); + } +} + type Primitive = string | number | boolean | bigint | null | undefined; type Flatten = T extends Primitive ? T diff --git a/test/integration/test/client/client.test.ts b/test/integration/test/client/client.test.ts index 66f38e4cd..7330c6bbf 100644 --- a/test/integration/test/client/client.test.ts +++ b/test/integration/test/client/client.test.ts @@ -645,7 +645,7 @@ test('should respect client notification capabilities', async () => { await clientWithoutCapability.connect(clientTransport); // This should throw because the client doesn't have the roots.listChanged capability - await expect(clientWithoutCapability.sendRootsListChanged()).rejects.toThrow(/^Client does not support/); + await expect(clientWithoutCapability.sendRootsListChanged()).rejects.toThrow(); }); /***