DEV Community

Yigit Konur
Yigit Konur

Posted on • Edited on

Error Handling in MCP TypeScript SDK

This document provides comprehensive documentation of error handling features and patterns in the Model Context Protocol (MCP) TypeScript SDK. The SDK implements a robust error handling system that covers protocol-level errors, OAuth authentication errors, transport errors, and application-level exceptions.

Table of Contents

Core Error Classes

McpError

The McpError class is the primary error type for MCP protocol-level errors. It extends the standard JavaScript Error class with MCP-specific information.

Location: src/types.ts:1461-1470

export class McpError extends Error {
  constructor(
    public readonly code: number,
    message: string,
    public readonly data?: unknown,
  ) {
    super(`MCP error ${code}: ${message}`);
    this.name = "McpError";
  }
}
Enter fullscreen mode Exit fullscreen mode

Properties:

  • code: Numeric error code (from ErrorCode enum)
  • message: Human-readable error description
  • data: Optional additional error information
  • name: Always set to "McpError"

Usage:

import { McpError, ErrorCode } from "@modelcontextprotocol/sdk";

throw new McpError(
  ErrorCode.InvalidRequest,
  "Missing required parameter",
  { parameter: "name" }
);
Enter fullscreen mode Exit fullscreen mode

OAuthError Hierarchy

A comprehensive set of OAuth-specific error classes that implement RFC 6749 OAuth 2.0 error responses.

Location: src/server/auth/errors.ts

Base Class: OAuthError

export class OAuthError extends Error {
  static errorCode: string;

  constructor(
    message: string,
    public readonly errorUri?: string
  ) {
    super(message);
    this.name = this.constructor.name;
  }

  toResponseObject(): OAuthErrorResponse {
    const response: OAuthErrorResponse = {
      error: this.errorCode,
      error_description: this.message
    };

    if (this.errorUri) {
      response.error_uri = this.errorUri;
    }

    return response;
  }

  get errorCode(): string {
    return (this.constructor as typeof OAuthError).errorCode
  }
}
Enter fullscreen mode Exit fullscreen mode

Standard OAuth Error Types

  1. InvalidRequestError (invalid_request)

    • Malformed request, missing parameters, or invalid parameter values
  2. InvalidClientError (invalid_client)

    • Client authentication failed
  3. InvalidGrantError (invalid_grant)

    • Authorization grant is invalid, expired, or revoked
  4. UnauthorizedClientError (unauthorized_client)

    • Client not authorized for this grant type
  5. UnsupportedGrantTypeError (unsupported_grant_type)

    • Grant type not supported by authorization server
  6. InvalidScopeError (invalid_scope)

    • Requested scope is invalid or exceeds granted scope
  7. AccessDeniedError (access_denied)

    • Resource owner or authorization server denied the request
  8. ServerError (server_error)

    • Unexpected server condition
  9. TemporarilyUnavailableError (temporarily_unavailable)

    • Server temporarily overloaded or under maintenance
  10. UnsupportedResponseTypeError (unsupported_response_type)

    • Authorization server doesn't support this response type
  11. UnsupportedTokenTypeError (unsupported_token_type)

    • Token type not supported
  12. InvalidTokenError (invalid_token)

    • Access token expired, revoked, or malformed
  13. MethodNotAllowedError (method_not_allowed)

    • HTTP method not allowed (custom extension)
  14. TooManyRequestsError (too_many_requests)

    • Rate limit exceeded (RFC 6585)
  15. InvalidClientMetadataError (invalid_client_metadata)

    • Invalid client metadata (RFC 7591)
  16. InsufficientScopeError (insufficient_scope)

    • Request requires higher privileges

CustomOAuthError

For defining custom error codes:

export class CustomOAuthError extends OAuthError {
  constructor(
    private readonly customErrorCode: string, 
    message: string, 
    errorUri?: string
  ) {
    super(message, errorUri);
  }

  get errorCode(): string {
    return this.customErrorCode;
  }
}
Enter fullscreen mode Exit fullscreen mode

Error Registry

All OAuth errors are registered in the OAUTH_ERRORS constant for runtime error parsing:

export const OAUTH_ERRORS = {
  [InvalidRequestError.errorCode]: InvalidRequestError,
  [InvalidClientError.errorCode]: InvalidClientError,
  // ... all other error types
} as const;
Enter fullscreen mode Exit fullscreen mode

JSON-RPC Error Codes

The SDK defines standard JSON-RPC error codes in the ErrorCode enum:

Location: src/types.ts:122-133

export enum ErrorCode {
  // SDK error codes
  ConnectionClosed = -32000,
  RequestTimeout = -32001,

  // Standard JSON-RPC error codes
  ParseError = -32700,
  InvalidRequest = -32600,
  MethodNotFound = -32601,
  InvalidParams = -32602,
  InternalError = -32603,
}
Enter fullscreen mode Exit fullscreen mode

Error Code Categories

  1. SDK-Specific Errors (-32000 to -32099)

    • ConnectionClosed (-32000): Transport connection was closed
    • RequestTimeout (-32001): Request exceeded timeout duration
  2. Standard JSON-RPC Errors (-32600 to -32699)

    • ParseError (-32700): Invalid JSON received
    • InvalidRequest (-32600): JSON-RPC request is invalid
    • MethodNotFound (-32601): Requested method doesn't exist
    • InvalidParams (-32602): Invalid method parameters
    • InternalError (-32603): Internal server error

OAuth Error Handling

Client-Side OAuth Errors

Location: src/client/auth.ts

The SDK provides utilities for handling OAuth errors in client applications:

import { 
  OAuthError, 
  InvalidClientError, 
  InvalidGrantError 
} from "@modelcontextprotocol/sdk";

// Error handling in OAuth flow
try {
  const token = await exchangeAuthorization(authCode, codeVerifier, metadata);
} catch (error) {
  if (error instanceof InvalidGrantError) {
    // Handle invalid authorization code
    console.error("Authorization code is invalid or expired");
  } else if (error instanceof InvalidClientError) {
    // Handle client authentication failure
    console.error("Client credentials are invalid");
  } else if (error instanceof OAuthError) {
    // Handle other OAuth errors
    console.error(`OAuth error: ${error.errorCode} - ${error.message}`);
  }
}
Enter fullscreen mode Exit fullscreen mode

Server-Side OAuth Error Responses

OAuth errors are automatically converted to proper HTTP responses:

try {
  // OAuth operation
} catch (error) {
  if (error instanceof OAuthError) {
    const errorResponse = error.toResponseObject();
    return new Response(JSON.stringify(errorResponse), {
      status: 400,
      headers: { 'Content-Type': 'application/json' }
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

UnauthorizedError

A special client-side error for handling unauthorized access:

export class UnauthorizedError extends Error {
  constructor(message: string = "Unauthorized") {
    super(message);
    this.name = "UnauthorizedError";
  }
}
Enter fullscreen mode Exit fullscreen mode

Protocol Error Patterns

Request Handler Error Handling

Location: src/shared/protocol.ts:411-449

The Protocol class implements comprehensive error handling for request processing:

Promise.resolve()
  .then(() => handler(request, fullExtra))
  .then(
    (result) => {
      if (abortController.signal.aborted) {
        return;
      }
      return capturedTransport?.send({
        result,
        jsonrpc: "2.0",
        id: request.id,
      });
    },
    (error) => {
      if (abortController.signal.aborted) {
        return;
      }
      return capturedTransport?.send({
        jsonrpc: "2.0",
        id: request.id,
        error: {
          code: Number.isSafeInteger(error["code"])
            ? error["code"]
            : ErrorCode.InternalError,
          message: error.message ?? "Internal error",
        },
      });
    },
  )
  .catch((error) =>
    this._onerror(new Error(`Failed to send response: ${error}`)),
  );
Enter fullscreen mode Exit fullscreen mode

Key Features:

  • Automatic error code detection and fallback
  • Abort signal handling
  • Transport error isolation
  • Structured error responses

Notification Handler Error Handling

Location: src/shared/protocol.ts:359-367

Promise.resolve()
  .then(() => handler(notification))
  .catch((error) =>
    this._onerror(
      new Error(`Uncaught error in notification handler: ${error}`),
    ),
  );
Enter fullscreen mode Exit fullscreen mode

Features:

  • Async error containment
  • Error reporting through onerror callback
  • Non-blocking error handling

Transport Error Handling

Connection Management

Location: src/shared/protocol.ts:331-343

private _onclose(): void {
  const responseHandlers = this._responseHandlers;
  this._responseHandlers = new Map();
  this._progressHandlers.clear();
  this._pendingDebouncedNotifications.clear();
  this._transport = undefined;
  this.onclose?.();

  const error = new McpError(ErrorCode.ConnectionClosed, "Connection closed");
  for (const handler of responseHandlers.values()) {
    handler(error);
  }
}
Enter fullscreen mode Exit fullscreen mode

Connection Closure Handling:

  • All pending requests receive ConnectionClosed error
  • Clean resource cleanup
  • Handler deregistration
  • Optional close callback invocation

WebSocket Error Handling

Location: src/client/websocket.ts

this._ws.onerror = (event) => {
  const error = event.error
    ? (event.error as Error)
    : new Error(`WebSocket error: ${JSON.stringify(event)}`);
  this.onerror?.(error);
};

this._ws.onmessage = (event) => {
  try {
    const message = JSON.parse(event.data);
    this.onmessage?.(message);
  } catch (error) {
    this.onerror?.(error as Error);
  }
};
Enter fullscreen mode Exit fullscreen mode

Features:

  • WebSocket event error transformation
  • JSON parsing error handling
  • Error callback propagation

Client Error Handling

Initialization Error Handling

Location: src/client/index.ts:135-183

try {
  const result = await this.request(
    {
      method: "initialize",
      params: {
        protocolVersion: LATEST_PROTOCOL_VERSION,
        capabilities: this._capabilities,
        clientInfo: this._clientInfo,
      },
    },
    InitializeResultSchema,
    options
  );

  if (result === undefined) {
    throw new Error(`Server sent invalid initialize result: ${result}`);
  }

  if (!SUPPORTED_PROTOCOL_VERSIONS.includes(result.protocolVersion)) {
    throw new Error(
      `Server's protocol version is not supported: ${result.protocolVersion}`,
    );
  }

  // ... initialization logic
} catch (error) {
  // Disconnect if initialization fails
  void this.close();
  throw error;
}
Enter fullscreen mode Exit fullscreen mode

Initialization Error Patterns:

  • Automatic disconnection on failure
  • Protocol version validation
  • Result validation
  • Error propagation with cleanup

Capability Validation

protected assertCapability(
  capability: keyof ServerCapabilities,
  method: string,
): void {
  if (!this._serverCapabilities?.[capability]) {
    throw new Error(
      `Server does not support ${capability} (required for ${method})`,
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Tool Call Validation

Location: src/client/index.ts:429-478

async callTool(params: CallToolRequest["params"]) {
  const result = await this.request(
    { method: "tools/call", params },
    resultSchema,
    options,
  );

  // Validate tool output schema
  const validator = this.getToolOutputValidator(params.name);
  if (validator) {
    if (!result.structuredContent && !result.isError) {
      throw new McpError(
        ErrorCode.InvalidRequest,
        `Tool ${params.name} has an output schema but did not return structured content`
      );
    }

    if (result.structuredContent) {
      try {
        const isValid = validator(result.structuredContent);
        if (!isValid) {
          throw new McpError(
            ErrorCode.InvalidParams,
            `Structured content does not match the tool's output schema: ${this._ajv.errorsText(validator.errors)}`
          );
        }
      } catch (error) {
        if (error instanceof McpError) {
          throw error;
        }
        throw new McpError(
          ErrorCode.InvalidParams,
          `Failed to validate structured content: ${error instanceof Error ? error.message : String(error)}`
        );
      }
    }
  }

  return result;
}
Enter fullscreen mode Exit fullscreen mode

Tool Validation Features:

  • Output schema validation
  • Structured content requirements
  • Error classification and re-throwing
  • AJV integration for JSON Schema validation

Server Error Handling

Elicitation Input Validation

Location: src/server/index.ts:313-349

async elicitInput(
  params: ElicitRequest["params"],
  options?: RequestOptions,
): Promise<ElicitResult> {
  const result = await this.request(
    { method: "elicitation/create", params },
    ElicitResultSchema,
    options,
  );

  if (result.action === "accept" && result.content) {
    try {
      const ajv = new Ajv();
      const validate = ajv.compile(params.requestedSchema);
      const isValid = validate(result.content);

      if (!isValid) {
        throw new McpError(
          ErrorCode.InvalidParams,
          `Elicitation response content does not match requested schema: ${ajv.errorsText(validate.errors)}`,
        );
      }
    } catch (error) {
      if (error instanceof McpError) {
        throw error;
      }
      throw new McpError(
        ErrorCode.InternalError,
        `Error validating elicitation response: ${error}`,
      );
    }
  }

  return result;
}
Enter fullscreen mode Exit fullscreen mode

Validation Features:

  • Schema-based content validation
  • Error classification and wrapping
  • JSON Schema integration
  • Detailed error messages

Request Lifecycle Error Management

Request Timeout Management

Location: src/shared/protocol.ts:249-292

private _setupTimeout(
  messageId: number,
  timeout: number,
  maxTotalTimeout: number | undefined,
  onTimeout: () => void,
  resetTimeoutOnProgress: boolean = false
) {
  this._timeoutInfo.set(messageId, {
    timeoutId: setTimeout(onTimeout, timeout),
    startTime: Date.now(),
    timeout,
    maxTotalTimeout,
    resetTimeoutOnProgress,
    onTimeout
  });
}

private _resetTimeout(messageId: number): boolean {
  const info = this._timeoutInfo.get(messageId);
  if (!info) return false;

  const totalElapsed = Date.now() - info.startTime;
  if (info.maxTotalTimeout && totalElapsed >= info.maxTotalTimeout) {
    this._timeoutInfo.delete(messageId);
    throw new McpError(
      ErrorCode.RequestTimeout,
      "Maximum total timeout exceeded",
      { maxTotalTimeout: info.maxTotalTimeout, totalElapsed }
    );
  }

  clearTimeout(info.timeoutId);
  info.timeoutId = setTimeout(info.onTimeout, info.timeout);
  return true;
}
Enter fullscreen mode Exit fullscreen mode

Timeout Features:

  • Per-request timeout tracking
  • Progress-based timeout reset
  • Maximum total timeout enforcement
  • Automatic cleanup on completion/error

Request Cancellation

Location: src/shared/protocol.ts:582-601

const cancel = (reason: unknown) => {
  this._responseHandlers.delete(messageId);
  this._progressHandlers.delete(messageId);
  this._cleanupTimeout(messageId);

  this._transport
    ?.send({
      jsonrpc: "2.0",
      method: "notifications/cancelled",
      params: {
        requestId: messageId,
        reason: String(reason),
      },
    })
    .catch((error) =>
      this._onerror(new Error(`Failed to send cancellation: ${error}`)),
    );

  reject(reason);
};
Enter fullscreen mode Exit fullscreen mode

Cancellation Features:

  • Clean handler removal
  • Timeout cleanup
  • Cancellation notification to remote peer
  • Promise rejection with reason

AbortSignal Integration

options?.signal?.addEventListener("abort", () => {
  cancel(options?.signal?.reason);
});
Enter fullscreen mode Exit fullscreen mode

Timeout and Cancellation

RequestOptions Timeout Configuration

export type RequestOptions = {
  timeout?: number;                    // Individual request timeout
  maxTotalTimeout?: number;           // Maximum total time regardless of progress
  resetTimeoutOnProgress?: boolean;   // Reset timeout on progress notifications
  signal?: AbortSignal;              // External cancellation signal
  onprogress?: ProgressCallback;     // Progress notification handler
};
Enter fullscreen mode Exit fullscreen mode

Progress-Based Timeout Reset

Location: src/shared/protocol.ts:451-474

private _onprogress(notification: ProgressNotification): void {
  const { progressToken, ...params } = notification.params;
  const messageId = Number(progressToken);

  const handler = this._progressHandlers.get(messageId);
  if (!handler) {
    this._onerror(new Error(`Received a progress notification for an unknown token: ${JSON.stringify(notification)}`));
    return;
  }

  const responseHandler = this._responseHandlers.get(messageId);
  const timeoutInfo = this._timeoutInfo.get(messageId);

  if (timeoutInfo && responseHandler && timeoutInfo.resetTimeoutOnProgress) {
    try {
      this._resetTimeout(messageId);
    } catch (error) {
      responseHandler(error as Error);
      return;
    }
  }

  handler(params);
}
Enter fullscreen mode Exit fullscreen mode

Error Propagation and Recovery

Response Handler Error Processing

Location: src/shared/protocol.ts:476-502

private _onresponse(response: JSONRPCResponse | JSONRPCError): void {
  const messageId = Number(response.id);
  const handler = this._responseHandlers.get(messageId);
  if (handler === undefined) {
    this._onerror(
      new Error(
        `Received a response for an unknown message ID: ${JSON.stringify(response)}`,
      ),
    );
    return;
  }

  this._responseHandlers.delete(messageId);
  this._progressHandlers.delete(messageId);
  this._cleanupTimeout(messageId);

  if (isJSONRPCResponse(response)) {
    handler(response);
  } else {
    const error = new McpError(
      response.error.code,
      response.error.message,
      response.error.data,
    );
    handler(error);
  }
}
Enter fullscreen mode Exit fullscreen mode

Error Processing Features:

  • Unknown message ID handling
  • Automatic resource cleanup
  • McpError construction from JSON-RPC errors
  • Handler deregistration

Fallback Error Handlers

/**
 * A handler to invoke for any request types that do not have their own handler installed.
 */
fallbackRequestHandler?: (
  request: JSONRPCRequest,
  extra: RequestHandlerExtra<SendRequestT, SendNotificationT>
) => Promise<SendResultT>;

/**
 * A handler to invoke for any notification types that do not have their own handler installed.
 */
fallbackNotificationHandler?: (notification: Notification) => Promise<void>;
Enter fullscreen mode Exit fullscreen mode

Best Practices

1. Use Specific Error Types

// Good: Specific error types
throw new McpError(ErrorCode.InvalidParams, "Missing required field: name");
throw new InvalidClientError("Client authentication failed");

// Avoid: Generic errors
throw new Error("Something went wrong");
Enter fullscreen mode Exit fullscreen mode

2. Provide Meaningful Error Messages

// Good: Descriptive messages with context
throw new McpError(
  ErrorCode.InvalidRequest,
  `Tool ${toolName} requires parameter '${paramName}' of type ${expectedType}`,
  { toolName, paramName, expectedType, receivedValue }
);

// Avoid: Vague messages
throw new McpError(ErrorCode.InvalidRequest, "Bad request");
Enter fullscreen mode Exit fullscreen mode

3. Handle Errors at Appropriate Levels

// Application level - handle business logic errors
try {
  const result = await client.callTool({ name: "calculator", arguments: { a: 1, b: 2 } });
} catch (error) {
  if (error instanceof McpError && error.code === ErrorCode.MethodNotFound) {
    console.log("Calculator tool not available, using fallback");
    return fallbackCalculation(1, 2);
  }
  throw error; // Re-throw unexpected errors
}

// Transport level - handle connection errors
client.onerror = (error) => {
  console.error("Transport error:", error);
  // Implement reconnection logic
};

client.onclose = () => {
  console.log("Connection closed, attempting reconnect...");
  // Implement reconnection strategy
};
Enter fullscreen mode Exit fullscreen mode

4. Implement Proper Cleanup

class MyClient {
  private client: Client;

  async connect() {
    try {
      await this.client.connect(transport);
    } catch (error) {
      // Cleanup on connection failure
      await this.client.close();
      throw error;
    }
  }

  async shutdown() {
    // Always cleanup resources
    try {
      await this.client.close();
    } catch (error) {
      console.error("Error during shutdown:", error);
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

5. Use AbortSignal for Cancellation

const controller = new AbortController();

// Set timeout for user cancellation
setTimeout(() => controller.abort("User cancelled"), 30000);

try {
  const result = await client.callTool(
    { name: "longRunningTool", arguments: {} },
    CallToolResultSchema,
    { signal: controller.signal }
  );
} catch (error) {
  if (error.name === 'AbortError') {
    console.log("Operation was cancelled");
  } else {
    console.error("Operation failed:", error);
  }
}
Enter fullscreen mode Exit fullscreen mode

Examples

Complete Error Handling in Client Application

import { 
  Client, 
  McpError, 
  ErrorCode,
  InvalidClientError,
  UnauthorizedError 
} from "@modelcontextprotocol/sdk";

class MCPClientWrapper {
  private client: Client;
  private reconnectAttempts = 0;
  private maxReconnectAttempts = 3;

  constructor() {
    this.client = new Client({ name: "MyApp", version: "1.0.0" });
    this.setupErrorHandlers();
  }

  private setupErrorHandlers() {
    this.client.onerror = (error) => {
      console.error("MCP Client Error:", error);

      if (error instanceof UnauthorizedError) {
        this.handleAuthenticationError();
      } else if (error instanceof McpError) {
        this.handleMCPError(error);
      } else {
        this.handleGenericError(error);
      }
    };

    this.client.onclose = () => {
      console.log("Connection closed");
      this.attemptReconnect();
    };
  }

  private handleMCPError(error: McpError) {
    switch (error.code) {
      case ErrorCode.ConnectionClosed:
        console.log("Connection lost, will attempt to reconnect");
        break;
      case ErrorCode.RequestTimeout:
        console.log("Request timed out, consider retrying");
        break;
      case ErrorCode.MethodNotFound:
        console.log("Method not supported by server");
        break;
      default:
        console.error(`MCP Error ${error.code}: ${error.message}`);
    }
  }

  private async handleAuthenticationError() {
    try {
      await this.refreshAuthentication();
    } catch (error) {
      console.error("Failed to refresh authentication:", error);
      // Redirect to login or request new credentials
    }
  }

  private handleGenericError(error: Error) {
    console.error("Generic error:", error.message);
    // Log to error reporting service
  }

  private async attemptReconnect() {
    if (this.reconnectAttempts >= this.maxReconnectAttempts) {
      console.error("Max reconnection attempts reached");
      return;
    }

    this.reconnectAttempts++;
    const delay = Math.pow(2, this.reconnectAttempts) * 1000; // Exponential backoff

    console.log(`Attempting reconnection ${this.reconnectAttempts}/${this.maxReconnectAttempts} in ${delay}ms`);

    setTimeout(async () => {
      try {
        await this.connect();
        this.reconnectAttempts = 0; // Reset on successful connection
      } catch (error) {
        console.error("Reconnection failed:", error);
        this.attemptReconnect();
      }
    }, delay);
  }

  async callToolSafely(toolName: string, args: any) {
    const maxRetries = 3;
    let attempt = 0;

    while (attempt < maxRetries) {
      try {
        const result = await this.client.callTool(
          { name: toolName, arguments: args },
          CallToolResultSchema,
          { 
            timeout: 30000,
            resetTimeoutOnProgress: true 
          }
        );

        if (result.isError) {
          throw new Error(`Tool error: ${result.content?.[0]?.text || 'Unknown error'}`);
        }

        return result;
      } catch (error) {
        attempt++;

        if (error instanceof McpError) {
          if (error.code === ErrorCode.RequestTimeout && attempt < maxRetries) {
            console.log(`Request timed out, retrying (${attempt}/${maxRetries})`);
            continue;
          } else if (error.code === ErrorCode.InvalidParams) {
            // Don't retry parameter errors
            throw error;
          }
        }

        if (attempt >= maxRetries) {
          throw error;
        }

        // Wait before retry
        await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Server Error Handling with OAuth

import { 
  Server, 
  McpError, 
  ErrorCode,
  OAuthError,
  InvalidTokenError,
  InsufficientScopeError 
} from "@modelcontextprotocol/sdk";

class MCPServerWrapper {
  private server: Server;

  constructor() {
    this.server = new Server(
      { name: "MyServer", version: "1.0.0" },
      { 
        capabilities: { tools: {}, resources: {} },
        instructions: "A secure MCP server with OAuth"
      }
    );

    this.setupRequestHandlers();
  }

  private setupRequestHandlers() {
    // Tool call handler with authentication
    this.server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
      try {
        // Verify authentication
        if (!extra.authInfo?.valid) {
          throw new InvalidTokenError("Invalid or expired access token");
        }

        // Check authorization scope
        const requiredScope = this.getRequiredScopeForTool(request.params.name);
        if (!this.hasScope(extra.authInfo.scopes, requiredScope)) {
          throw new InsufficientScopeError(
            `Tool '${request.params.name}' requires scope '${requiredScope}'`
          );
        }

        // Execute tool
        const result = await this.executeTool(request.params.name, request.params.arguments);

        return {
          content: [{ type: "text", text: JSON.stringify(result) }],
          isError: false
        };
      } catch (error) {
        // Convert OAuth errors to tool results
        if (error instanceof OAuthError) {
          return {
            content: [{ 
              type: "text", 
              text: `Authentication error: ${error.message}` 
            }],
            isError: true
          };
        }

        // Handle other errors
        if (error instanceof McpError) {
          throw error; // Re-throw MCP errors
        }

        // Wrap unknown errors
        throw new McpError(
          ErrorCode.InternalError,
          `Tool execution failed: ${error.message}`,
          { toolName: request.params.name, originalError: error.message }
        );
      }
    });

    // Resource handler with error handling
    this.server.setRequestHandler(ReadResourceRequestSchema, async (request, extra) => {
      try {
        const resource = await this.loadResource(request.params.uri);

        if (!resource) {
          throw new McpError(
            ErrorCode.InvalidParams,
            `Resource not found: ${request.params.uri}`,
            { uri: request.params.uri }
          );
        }

        return {
          contents: [{
            uri: request.params.uri,
            mimeType: resource.mimeType,
            text: resource.content
          }]
        };
      } catch (error) {
        if (error instanceof McpError) {
          throw error;
        }

        throw new McpError(
          ErrorCode.InternalError,
          `Failed to read resource: ${error.message}`,
          { uri: request.params.uri }
        );
      }
    });
  }

  private getRequiredScopeForTool(toolName: string): string {
    const scopeMap = {
      'read_files': 'files:read',
      'write_files': 'files:write',
      'execute_command': 'system:execute'
    };
    return scopeMap[toolName] || 'basic';
  }

  private hasScope(userScopes: string[], requiredScope: string): boolean {
    return userScopes.includes(requiredScope) || userScopes.includes('admin');
  }

  private async executeTool(name: string, args: any) {
    // Tool implementation with proper error handling
    switch (name) {
      case 'calculator':
        if (typeof args.a !== 'number' || typeof args.b !== 'number') {
          throw new McpError(
            ErrorCode.InvalidParams,
            "Calculator requires numeric parameters 'a' and 'b'",
            { providedArgs: args }
          );
        }
        return { result: args.a + args.b };

      default:
        throw new McpError(
          ErrorCode.MethodNotFound,
          `Tool '${name}' not found`,
          { availableTools: ['calculator'] }
        );
    }
  }

  private async loadResource(uri: string) {
    // Resource loading with error handling
    try {
      // Implementation would load actual resource
      return {
        content: "Resource content",
        mimeType: "text/plain"
      };
    } catch (error) {
      // Re-throw as MCP error with context
      throw new McpError(
        ErrorCode.InternalError,
        `Failed to load resource from ${uri}: ${error.message}`,
        { uri, error: error.message }
      );
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

This comprehensive error handling documentation covers all major error scenarios in the MCP TypeScript SDK, from low-level transport errors to high-level application errors, providing developers with the knowledge and patterns needed to build robust MCP applications.

Top comments (0)