Skip to content
29 changes: 16 additions & 13 deletions packages/client/src/client/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ import {
ResourceListChangedNotificationSchema,
safeParse,
SUPPORTED_PROTOCOL_VERSIONS,
ToolListChangedNotificationSchema
ToolListChangedNotificationSchema,
UnsupportedCapabilityError
} from '@modelcontextprotocol/core';

import { ExperimentalClientTasks } from '../experimental/tasks/client.js';
Expand Down Expand Up @@ -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})`);
}
}

Expand Down Expand Up @@ -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;

Expand All @@ -582,25 +583,25 @@ 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;

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;

Expand All @@ -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;

Expand Down Expand Up @@ -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;

Expand All @@ -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;

Expand Down
22 changes: 15 additions & 7 deletions packages/core/src/experimental/tasks/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
*/
Expand All @@ -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;

Expand All @@ -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
*/
Expand All @@ -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;

Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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> = T extends Primitive
? T
Expand Down
2 changes: 1 addition & 1 deletion test/integration/test/client/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});

/***
Expand Down
Loading