v2: Errors refactor (ProtocolError, SdkError, OAuthError) (#1454)
This commit is contained in:
parent
160902eb2e
commit
0f0a4eb7c8
7 changed files with 1086 additions and 705 deletions
|
|
@ -11,21 +11,16 @@ import type {
|
|||
} from '@modelcontextprotocol/core';
|
||||
import {
|
||||
checkResourceAllowed,
|
||||
InvalidClientError,
|
||||
InvalidClientMetadataError,
|
||||
InvalidGrantError,
|
||||
LATEST_PROTOCOL_VERSION,
|
||||
OAUTH_ERRORS,
|
||||
OAuthClientInformationFullSchema,
|
||||
OAuthError,
|
||||
OAuthErrorCode,
|
||||
OAuthErrorResponseSchema,
|
||||
OAuthMetadataSchema,
|
||||
OAuthProtectedResourceMetadataSchema,
|
||||
OAuthTokensSchema,
|
||||
OpenIdProviderDiscoveryMetadataSchema,
|
||||
resourceUrlFromServerUrl,
|
||||
ServerError,
|
||||
UnauthorizedClientError
|
||||
resourceUrlFromServerUrl
|
||||
} from '@modelcontextprotocol/core';
|
||||
import pkceChallenge from 'pkce-challenge';
|
||||
|
||||
|
|
@ -328,7 +323,7 @@ function applyPublicAuth(clientId: string, params: URLSearchParams): void {
|
|||
* Parses an OAuth error response from a string or Response object.
|
||||
*
|
||||
* If the input is a standard OAuth2.0 error response, it will be parsed according to the spec
|
||||
* and an instance of the appropriate OAuthError subclass will be returned.
|
||||
* and an OAuthError will be returned with the appropriate error code.
|
||||
* If parsing fails, it falls back to a generic ServerError that includes
|
||||
* the response status (if available) and original content.
|
||||
*
|
||||
|
|
@ -341,13 +336,11 @@ export async function parseErrorResponse(input: Response | string): Promise<OAut
|
|||
|
||||
try {
|
||||
const result = OAuthErrorResponseSchema.parse(JSON.parse(body));
|
||||
const { error, error_description, error_uri } = result;
|
||||
const errorClass = OAUTH_ERRORS[error] || ServerError;
|
||||
return new errorClass(error_description || '', error_uri);
|
||||
return OAuthError.fromResponse(result);
|
||||
} catch (error) {
|
||||
// Not a valid OAuth error response, but try to inform the user of the raw data anyway
|
||||
const errorMessage = `${statusCode ? `HTTP ${statusCode}: ` : ''}Invalid OAuth error response: ${error}. Raw body: ${body}`;
|
||||
return new ServerError(errorMessage);
|
||||
return new OAuthError(OAuthErrorCode.ServerError, errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -371,12 +364,14 @@ export async function auth(
|
|||
return await authInternal(provider, options);
|
||||
} catch (error) {
|
||||
// Handle recoverable error types by invalidating credentials and retrying
|
||||
if (error instanceof InvalidClientError || error instanceof UnauthorizedClientError) {
|
||||
await provider.invalidateCredentials?.('all');
|
||||
return await authInternal(provider, options);
|
||||
} else if (error instanceof InvalidGrantError) {
|
||||
await provider.invalidateCredentials?.('tokens');
|
||||
return await authInternal(provider, options);
|
||||
if (error instanceof OAuthError) {
|
||||
if (error.code === OAuthErrorCode.InvalidClient || error.code === OAuthErrorCode.UnauthorizedClient) {
|
||||
await provider.invalidateCredentials?.('all');
|
||||
return await authInternal(provider, options);
|
||||
} else if (error.code === OAuthErrorCode.InvalidGrant) {
|
||||
await provider.invalidateCredentials?.('tokens');
|
||||
return await authInternal(provider, options);
|
||||
}
|
||||
}
|
||||
|
||||
// Throw otherwise
|
||||
|
|
@ -437,7 +432,8 @@ async function authInternal(
|
|||
const clientMetadataUrl = provider.clientMetadataUrl;
|
||||
|
||||
if (clientMetadataUrl && !isHttpsUrl(clientMetadataUrl)) {
|
||||
throw new InvalidClientMetadataError(
|
||||
throw new OAuthError(
|
||||
OAuthErrorCode.InvalidClientMetadata,
|
||||
`clientMetadataUrl must be a valid HTTPS URL with a non-root pathname, got: ${clientMetadataUrl}`
|
||||
);
|
||||
}
|
||||
|
|
@ -502,7 +498,7 @@ async function authInternal(
|
|||
return 'AUTHORIZED';
|
||||
} catch (error) {
|
||||
// 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.
|
||||
if (!(error instanceof OAuthError) || error instanceof ServerError) {
|
||||
if (!(error instanceof OAuthError) || error.code === OAuthErrorCode.ServerError) {
|
||||
// Could not refresh OAuth tokens
|
||||
} else {
|
||||
// Refresh failed for another reason, re-throw
|
||||
|
|
|
|||
|
|
@ -47,7 +47,6 @@ import {
|
|||
ElicitRequestSchema,
|
||||
ElicitResultSchema,
|
||||
EmptyResultSchema,
|
||||
ErrorCode,
|
||||
GetPromptResultSchema,
|
||||
InitializeResultSchema,
|
||||
LATEST_PROTOCOL_VERSION,
|
||||
|
|
@ -56,11 +55,14 @@ import {
|
|||
ListResourcesResultSchema,
|
||||
ListResourceTemplatesResultSchema,
|
||||
ListToolsResultSchema,
|
||||
McpError,
|
||||
mergeCapabilities,
|
||||
Protocol,
|
||||
ProtocolError,
|
||||
ProtocolErrorCode,
|
||||
ReadResourceResultSchema,
|
||||
safeParse
|
||||
safeParse,
|
||||
SdkError,
|
||||
SdkErrorCode
|
||||
} from '@modelcontextprotocol/core';
|
||||
|
||||
import { ExperimentalClientTasks } from '../experimental/tasks/client.js';
|
||||
|
|
@ -344,7 +346,7 @@ export class Client<
|
|||
// Type guard: if success is false, error is guaranteed to exist
|
||||
const errorMessage =
|
||||
validatedRequest.error instanceof Error ? validatedRequest.error.message : String(validatedRequest.error);
|
||||
throw new McpError(ErrorCode.InvalidParams, `Invalid elicitation request: ${errorMessage}`);
|
||||
throw new ProtocolError(ProtocolErrorCode.InvalidParams, `Invalid elicitation request: ${errorMessage}`);
|
||||
}
|
||||
|
||||
const { params } = validatedRequest.data;
|
||||
|
|
@ -352,11 +354,11 @@ export class Client<
|
|||
const { supportsFormMode, supportsUrlMode } = getSupportedElicitationModes(this._capabilities.elicitation);
|
||||
|
||||
if (params.mode === 'form' && !supportsFormMode) {
|
||||
throw new McpError(ErrorCode.InvalidParams, 'Client does not support form-mode elicitation requests');
|
||||
throw new ProtocolError(ProtocolErrorCode.InvalidParams, 'Client does not support form-mode elicitation requests');
|
||||
}
|
||||
|
||||
if (params.mode === 'url' && !supportsUrlMode) {
|
||||
throw new McpError(ErrorCode.InvalidParams, 'Client does not support URL-mode elicitation requests');
|
||||
throw new ProtocolError(ProtocolErrorCode.InvalidParams, 'Client does not support URL-mode elicitation requests');
|
||||
}
|
||||
|
||||
const result = await Promise.resolve(handler(request, extra));
|
||||
|
|
@ -369,7 +371,7 @@ export class Client<
|
|||
taskValidationResult.error instanceof Error
|
||||
? taskValidationResult.error.message
|
||||
: String(taskValidationResult.error);
|
||||
throw new McpError(ErrorCode.InvalidParams, `Invalid task creation result: ${errorMessage}`);
|
||||
throw new ProtocolError(ProtocolErrorCode.InvalidParams, `Invalid task creation result: ${errorMessage}`);
|
||||
}
|
||||
return taskValidationResult.data;
|
||||
}
|
||||
|
|
@ -380,7 +382,7 @@ export class Client<
|
|||
// Type guard: if success is false, error is guaranteed to exist
|
||||
const errorMessage =
|
||||
validationResult.error instanceof Error ? validationResult.error.message : String(validationResult.error);
|
||||
throw new McpError(ErrorCode.InvalidParams, `Invalid elicitation result: ${errorMessage}`);
|
||||
throw new ProtocolError(ProtocolErrorCode.InvalidParams, `Invalid elicitation result: ${errorMessage}`);
|
||||
}
|
||||
|
||||
const validatedResult = validationResult.data;
|
||||
|
|
@ -416,7 +418,7 @@ export class Client<
|
|||
if (!validatedRequest.success) {
|
||||
const errorMessage =
|
||||
validatedRequest.error instanceof Error ? validatedRequest.error.message : String(validatedRequest.error);
|
||||
throw new McpError(ErrorCode.InvalidParams, `Invalid sampling request: ${errorMessage}`);
|
||||
throw new ProtocolError(ProtocolErrorCode.InvalidParams, `Invalid sampling request: ${errorMessage}`);
|
||||
}
|
||||
|
||||
const { params } = validatedRequest.data;
|
||||
|
|
@ -431,7 +433,7 @@ export class Client<
|
|||
taskValidationResult.error instanceof Error
|
||||
? taskValidationResult.error.message
|
||||
: String(taskValidationResult.error);
|
||||
throw new McpError(ErrorCode.InvalidParams, `Invalid task creation result: ${errorMessage}`);
|
||||
throw new ProtocolError(ProtocolErrorCode.InvalidParams, `Invalid task creation result: ${errorMessage}`);
|
||||
}
|
||||
return taskValidationResult.data;
|
||||
}
|
||||
|
|
@ -443,7 +445,7 @@ export class Client<
|
|||
if (!validationResult.success) {
|
||||
const errorMessage =
|
||||
validationResult.error instanceof Error ? validationResult.error.message : String(validationResult.error);
|
||||
throw new McpError(ErrorCode.InvalidParams, `Invalid sampling result: ${errorMessage}`);
|
||||
throw new ProtocolError(ProtocolErrorCode.InvalidParams, `Invalid sampling result: ${errorMessage}`);
|
||||
}
|
||||
|
||||
return validationResult.data;
|
||||
|
|
@ -542,7 +544,7 @@ 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 SdkError(SdkErrorCode.CapabilityNotSupported, `Server does not support logging (required for ${method})`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
@ -550,7 +552,7 @@ export class Client<
|
|||
case 'prompts/get':
|
||||
case 'prompts/list': {
|
||||
if (!this._serverCapabilities?.prompts) {
|
||||
throw new Error(`Server does not support prompts (required for ${method})`);
|
||||
throw new SdkError(SdkErrorCode.CapabilityNotSupported, `Server does not support prompts (required for ${method})`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
@ -561,11 +563,14 @@ 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 SdkError(SdkErrorCode.CapabilityNotSupported, `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 SdkError(
|
||||
SdkErrorCode.CapabilityNotSupported,
|
||||
`Server does not support resource subscriptions (required for ${method})`
|
||||
);
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
@ -574,14 +579,14 @@ 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 SdkError(SdkErrorCode.CapabilityNotSupported, `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 SdkError(SdkErrorCode.CapabilityNotSupported, `Server does not support completions (required for ${method})`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
@ -602,7 +607,10 @@ 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 SdkError(
|
||||
SdkErrorCode.CapabilityNotSupported,
|
||||
`Client does not support roots list changed notifications (required for ${method})`
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
@ -634,21 +642,30 @@ 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 SdkError(
|
||||
SdkErrorCode.CapabilityNotSupported,
|
||||
`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 SdkError(
|
||||
SdkErrorCode.CapabilityNotSupported,
|
||||
`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 SdkError(
|
||||
SdkErrorCode.CapabilityNotSupported,
|
||||
`Client does not support roots capability (required for ${method})`
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
@ -658,7 +675,10 @@ 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 SdkError(
|
||||
SdkErrorCode.CapabilityNotSupported,
|
||||
`Client does not support tasks capability (required for ${method})`
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
@ -753,8 +773,8 @@ export class Client<
|
|||
) {
|
||||
// Guard: required-task tools need experimental API
|
||||
if (this.isToolTaskRequired(params.name)) {
|
||||
throw new McpError(
|
||||
ErrorCode.InvalidRequest,
|
||||
throw new ProtocolError(
|
||||
ProtocolErrorCode.InvalidRequest,
|
||||
`Tool "${params.name}" requires task-based execution. Use client.experimental.tasks.callToolStream() instead.`
|
||||
);
|
||||
}
|
||||
|
|
@ -766,8 +786,8 @@ export class Client<
|
|||
if (validator) {
|
||||
// If tool has outputSchema, it MUST return structuredContent (unless it's an error)
|
||||
if (!result.structuredContent && !result.isError) {
|
||||
throw new McpError(
|
||||
ErrorCode.InvalidRequest,
|
||||
throw new ProtocolError(
|
||||
ProtocolErrorCode.InvalidRequest,
|
||||
`Tool ${params.name} has an output schema but did not return structured content`
|
||||
);
|
||||
}
|
||||
|
|
@ -779,17 +799,17 @@ export class Client<
|
|||
const validationResult = validator(result.structuredContent);
|
||||
|
||||
if (!validationResult.valid) {
|
||||
throw new McpError(
|
||||
ErrorCode.InvalidParams,
|
||||
throw new ProtocolError(
|
||||
ProtocolErrorCode.InvalidParams,
|
||||
`Structured content does not match the tool's output schema: ${validationResult.errorMessage}`
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof McpError) {
|
||||
if (error instanceof ProtocolError) {
|
||||
throw error;
|
||||
}
|
||||
throw new McpError(
|
||||
ErrorCode.InvalidParams,
|
||||
throw new ProtocolError(
|
||||
ProtocolErrorCode.InvalidParams,
|
||||
`Failed to validate structured content: ${error instanceof Error ? error.message : String(error)}`
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ export type Middleware = (next: FetchLike) => FetchLike;
|
|||
* - Add Authorization headers with access tokens
|
||||
* - Handle 401 responses by attempting re-authentication
|
||||
* - Retry the original request after successful auth
|
||||
* - Handle OAuth errors appropriately (InvalidClientError, etc.)
|
||||
* - Handle OAuth errors appropriately (OAuthErrorCode.InvalidClient, etc.)
|
||||
*
|
||||
* The baseUrl parameter is optional and defaults to using the domain from the request URL.
|
||||
* However, you should explicitly provide baseUrl when:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { FetchLike, JSONRPCMessage, Transport } from '@modelcontextprotocol/core';
|
||||
import { createFetchWithInit, JSONRPCMessageSchema, normalizeHeaders } from '@modelcontextprotocol/core';
|
||||
import { createFetchWithInit, JSONRPCMessageSchema, normalizeHeaders, SdkError, SdkErrorCode } from '@modelcontextprotocol/core';
|
||||
import type { ErrorEvent, EventSourceInit } from 'eventsource';
|
||||
import { EventSource } from 'eventsource';
|
||||
|
||||
|
|
@ -245,7 +245,7 @@ export class SSEClientTransport implements Transport {
|
|||
|
||||
async send(message: JSONRPCMessage): Promise<void> {
|
||||
if (!this._endpoint) {
|
||||
throw new Error('Not connected');
|
||||
throw new SdkError(SdkErrorCode.NotConnected, 'Not connected');
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import type { Stream } from 'node:stream';
|
|||
import { PassThrough } from 'node:stream';
|
||||
|
||||
import type { JSONRPCMessage, Transport } from '@modelcontextprotocol/core';
|
||||
import { ReadBuffer, serializeMessage } from '@modelcontextprotocol/core';
|
||||
import { ReadBuffer, SdkError, SdkErrorCode, serializeMessage } from '@modelcontextprotocol/core';
|
||||
import spawn from 'cross-spawn';
|
||||
|
||||
export type StdioServerParameters = {
|
||||
|
|
@ -246,7 +246,7 @@ export class StdioClientTransport implements Transport {
|
|||
send(message: JSONRPCMessage): Promise<void> {
|
||||
return new Promise(resolve => {
|
||||
if (!this._process?.stdin) {
|
||||
throw new Error('Not connected');
|
||||
throw new SdkError(SdkErrorCode.NotConnected, 'Not connected');
|
||||
}
|
||||
|
||||
const json = serializeMessage(message);
|
||||
|
|
|
|||
|
|
@ -7,7 +7,9 @@ import {
|
|||
isJSONRPCRequest,
|
||||
isJSONRPCResultResponse,
|
||||
JSONRPCMessageSchema,
|
||||
normalizeHeaders
|
||||
normalizeHeaders,
|
||||
SdkError,
|
||||
SdkErrorCode
|
||||
} from '@modelcontextprotocol/core';
|
||||
import { EventSourceParserStream } from 'eventsource-parser/stream';
|
||||
|
||||
|
|
@ -22,15 +24,6 @@ const DEFAULT_STREAMABLE_HTTP_RECONNECTION_OPTIONS: StreamableHTTPReconnectionOp
|
|||
maxRetries: 2
|
||||
};
|
||||
|
||||
export class StreamableHTTPError extends Error {
|
||||
constructor(
|
||||
public readonly code: number | undefined,
|
||||
message: string | undefined
|
||||
) {
|
||||
super(`Streamable HTTP error: ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for starting or authenticating an SSE connection
|
||||
*/
|
||||
|
|
@ -248,7 +241,10 @@ export class StreamableHTTPClientTransport implements Transport {
|
|||
return;
|
||||
}
|
||||
|
||||
throw new StreamableHTTPError(response.status, `Failed to open SSE stream: ${response.statusText}`);
|
||||
throw new SdkError(SdkErrorCode.ClientHttpFailedToOpenStream, `Failed to open SSE stream: ${response.statusText}`, {
|
||||
status: response.status,
|
||||
statusText: response.statusText
|
||||
});
|
||||
}
|
||||
|
||||
this._handleSseStream(response.body, options, true);
|
||||
|
|
@ -500,7 +496,10 @@ export class StreamableHTTPClientTransport implements Transport {
|
|||
if (response.status === 401 && this._authProvider) {
|
||||
// Prevent infinite recursion when server returns 401 after successful auth
|
||||
if (this._hasCompletedAuthFlow) {
|
||||
throw new StreamableHTTPError(401, 'Server returned 401 after successful authentication');
|
||||
throw new SdkError(SdkErrorCode.ClientHttpAuthentication, 'Server returned 401 after successful authentication', {
|
||||
status: 401,
|
||||
text
|
||||
});
|
||||
}
|
||||
|
||||
const { resourceMetadataUrl, scope } = extractWWWAuthenticateParams(response);
|
||||
|
|
@ -531,7 +530,10 @@ export class StreamableHTTPClientTransport implements Transport {
|
|||
|
||||
// Check if we've already tried upscoping with this header to prevent infinite loops.
|
||||
if (this._lastUpscopingHeader === wwwAuthHeader) {
|
||||
throw new StreamableHTTPError(403, 'Server returned 403 after trying upscoping');
|
||||
throw new SdkError(SdkErrorCode.ClientHttpForbidden, 'Server returned 403 after trying upscoping', {
|
||||
status: 403,
|
||||
text
|
||||
});
|
||||
}
|
||||
|
||||
if (scope) {
|
||||
|
|
@ -559,7 +561,10 @@ export class StreamableHTTPClientTransport implements Transport {
|
|||
}
|
||||
}
|
||||
|
||||
throw new StreamableHTTPError(response.status, `Error POSTing to endpoint: ${text}`);
|
||||
throw new SdkError(SdkErrorCode.ClientHttpNotImplemented, `Error POSTing to endpoint: ${text}`, {
|
||||
status: response.status,
|
||||
text
|
||||
});
|
||||
}
|
||||
|
||||
// Reset auth loop flag on successful response
|
||||
|
|
@ -604,7 +609,9 @@ export class StreamableHTTPClientTransport implements Transport {
|
|||
}
|
||||
} else {
|
||||
await response.text?.().catch(() => {});
|
||||
throw new StreamableHTTPError(-1, `Unexpected content type: ${contentType}`);
|
||||
throw new SdkError(SdkErrorCode.ClientHttpUnexpectedContent, `Unexpected content type: ${contentType}`, {
|
||||
contentType
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// No requests in message but got 200 OK - still need to release connection
|
||||
|
|
@ -652,7 +659,10 @@ export class StreamableHTTPClientTransport implements Transport {
|
|||
// We specifically handle 405 as a valid response according to the spec,
|
||||
// meaning the server does not support explicit session termination
|
||||
if (!response.ok && response.status !== 405) {
|
||||
throw new StreamableHTTPError(response.status, `Failed to terminate session: ${response.statusText}`);
|
||||
throw new SdkError(SdkErrorCode.ClientHttpFailedToTerminateSession, `Failed to terminate session: ${response.statusText}`, {
|
||||
status: response.status,
|
||||
statusText: response.statusText
|
||||
});
|
||||
}
|
||||
|
||||
this._sessionId = undefined;
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import type {
|
|||
Result,
|
||||
SchemaOutput
|
||||
} from '@modelcontextprotocol/core';
|
||||
import { CallToolResultSchema, ErrorCode, McpError } from '@modelcontextprotocol/core';
|
||||
import { CallToolResultSchema, ProtocolError, ProtocolErrorCode } from '@modelcontextprotocol/core';
|
||||
|
||||
import type { Client } from '../../client/client.js';
|
||||
|
||||
|
|
@ -123,8 +123,8 @@ export class ExperimentalClientTasks<
|
|||
if (!result.structuredContent && !result.isError) {
|
||||
yield {
|
||||
type: 'error',
|
||||
error: new McpError(
|
||||
ErrorCode.InvalidRequest,
|
||||
error: new ProtocolError(
|
||||
ProtocolErrorCode.InvalidRequest,
|
||||
`Tool ${params.name} has an output schema but did not return structured content`
|
||||
)
|
||||
};
|
||||
|
|
@ -140,22 +140,22 @@ export class ExperimentalClientTasks<
|
|||
if (!validationResult.valid) {
|
||||
yield {
|
||||
type: 'error',
|
||||
error: new McpError(
|
||||
ErrorCode.InvalidParams,
|
||||
error: new ProtocolError(
|
||||
ProtocolErrorCode.InvalidParams,
|
||||
`Structured content does not match the tool's output schema: ${validationResult.errorMessage}`
|
||||
)
|
||||
};
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof McpError) {
|
||||
if (error instanceof ProtocolError) {
|
||||
yield { type: 'error', error };
|
||||
return;
|
||||
}
|
||||
yield {
|
||||
type: 'error',
|
||||
error: new McpError(
|
||||
ErrorCode.InvalidParams,
|
||||
error: new ProtocolError(
|
||||
ProtocolErrorCode.InvalidParams,
|
||||
`Failed to validate structured content: ${error instanceof Error ? error.message : String(error)}`
|
||||
)
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue