DEV Community

Yigit Konur
Yigit Konur

Posted 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)