How to Handle Errors and Edge Cases Gracefully in Claude Skill Instructions

Centralize error handling through utility functions, wrap all external calls in try-catch blocks, and return sanitized error messages inside the JSON-RPC result object rather than as protocol-level failures to maintain LLM-friendly workflows.

Claude Skills from the ComposioHQ/awesome-claude-skills repository are instruction bundles that guide LLMs through complex workflows involving external tools and APIs. When these skills invoke MCP servers or internal utilities, robust error handling ensures the model receives contextual feedback rather than raw stack traces. This guide demonstrates architectural patterns for implementing graceful error recovery based on the reference implementations in mcp-builder/reference/mcp_best_practices.md and language-specific server templates.

Centralize Error Handling with Utility Functions

All MCP-based skills should funnel exceptions through a single utility function to ensure consistent formatting across your codebase. This approach allows you to adjust error presentation in one location while keeping individual tool implementations clean.

In mcp-builder/reference/python_mcp_server.md, the Python reference implements a _handle_api_error helper that formats exceptions into friendly strings (lines 235-250). Similarly, the TypeScript example in mcp-builder/reference/node_mcp_server.md provides a handleApiError utility specifically designed for AxiosError objects (lines 408-425).

Wrap External Calls in Try-Catch Blocks

Every network request, database query, or file operation must be wrapped in exception handling to prevent unexpected failures from leaking as raw stack traces. In Python, use try … except structures; in Node.js, use try { … } catch (error) { … } blocks.

This guarantee ensures that even unanticipated exceptions are transformed by your central error handler before reaching the model. The pattern appears consistently throughout the reference implementations, where tool implementations catch errors at the outermost layer of the function.

Return Errors Inside the Result Object

Claude expects tool results to be JSON-RPC compliant. According to mcp-builder/reference/mcp_best_practices.md (lines 686-694), tools should always return a successful RPC envelope, placing any error details inside the content array rather than as protocol-level failures.

This architectural decision allows the LLM to see the error context and decide whether to retry the operation, ask the user for clarification, or abort the workflow gracefully. Protocol-level errors terminate the entire session, while result-object errors enable conversational recovery.

Format Clear, Actionable Error Messages

Error messages must be concise, human-readable, and suggest a next step. The Error Handling Standards section in mcp-builder/reference/mcp_best_practices.md (lines 48-53) specifies that messages should identify the failure type without overwhelming the model with technical noise.

Examples from the reference implementations include:

  • Python pattern: Error: Unexpected error occurred: ConnectionTimeout
  • Node.js pattern: Error: API request failed with status 429

Both formats provide the LLM with enough context to understand the failure category while leaving room for the model to request additional details if needed.

Sanitize Output to Protect Sensitive Data

Never include raw API keys, internal IDs, or full stack traces in the error text returned to the model. The best practices document (lines 263-270) recommends logging sensitive details server-side while returning a sanitized message to the LLM.

This security boundary prevents credential leakage through the conversational interface while preserving debugging capabilities through server logs. Implement redaction logic in your centralized error formatter to strip tokens and personally identifiable information before constructing the response payload.

Handle Pagination and Character Limits

When tools return large collections, enforce the limit parameter and include pagination metadata such as has_more and next_offset. According to mcp-builder/reference/mcp_best_practices.md (lines 30-34 and 53-64), if output exceeds the global CHARACTER_LIMIT (approximately 25,000 characters), truncate gracefully and add a truncation_message explaining how to retrieve additional data.

This prevents context window overflow while maintaining the conversation flow. The LLM can request subsequent pages using the provided metadata rather than receiving a truncated payload without explanation.

Log Errors Locally for Debugging

While the LLM only sees the sanitized message, the server should record the full exception with context for later debugging. The pattern appears throughout the MCP documentation (lines 549-552), where implementations use ctx.log_error() or equivalent methods to capture complete stack traces and request parameters.

This dual-track approach satisfies both observability requirements and security constraints, allowing developers to diagnose issues without exposing internal system details to the conversational interface.

Document Error Strategies in SKILL.md Files

Each skill repository should contain explicit error handling documentation. The mcp-builder/SKILL.md file includes a high-level checklist that explicitly calls out error handling steps, while individual skill templates like composio-skills/zoho_mail-automation/SKILL.md (lines 85-86) provide placeholder sections for customization.

Include the following in your Error Handling section:

  • Expected error codes: Such as ConnectionTimeout, RateLimitExceeded, or InvalidCredentials
  • Sample messages: The exact text the LLM will receive, e.g., Error: ConnectionTimeout – please try again later
  • Recovery suggestions: Prompts the model can offer users, such as "Would you like me to retry the request?"

Practical Implementation Examples

Python MCP Server Pattern


# utils.py

def format_error(e: Exception) -> str:
    """Return a user-friendly error string without leaking internals."""
    return f"Error: {type(e).__name__}: {str(e)}"

# tool implementation

def list_projects(params: dict) -> dict:
    try:
        # external API call

        resp = client.get("/projects", params=params)
        resp.raise_for_status()
        return {"content": resp.json()}
    except Exception as exc:
        # Centralized handling

        return {"content": [{"text": format_error(exc)}]}

See the full reference in mcp-builder/reference/python_mcp_server.md for additional context.

TypeScript MCP Server Pattern

// utils.ts
export function handleApiError(error: unknown): string {
  if (axios.isAxiosError(error) && error.response) {
    return `Error: API request failed with status ${error.response.status}`;
  }
  if (error instanceof Error) {
    return `Error: ${error.name}: ${error.message}`;
  }
  return "Error: Unexpected error occurred";
}

// tool implementation
export async function listProjects(params: Record<string, any>) {
  try {
    const { data } = await axios.get('/projects', { params });
    return { content: data };
  } catch (e) {
    return { content: [{ text: handleApiError(e) }] };
  }
}

Full implementation details are available in mcp-builder/reference/node_mcp_server.md.

Skill Markdown Documentation Template


### Error Handling

- **Possible errors:** `ConnectionTimeout`, `RateLimitExceeded`, `InvalidCredentials`
- **Message shown to Claude:** `Error: ConnectionTimeout – please try again later`
- **Recovery suggestion:** "Would you like me to retry the request?"

Summary

  • Centralize error formatting through dedicated utility functions like format_error or handleApiError to maintain consistency across your skill.
  • Wrap all external calls in try-catch blocks to prevent raw exceptions from reaching the LLM.
  • Return errors within the JSON-RPC result object rather than as protocol-level failures, enabling the model to decide on retry or recovery strategies.
  • Sanitize error messages to remove API keys and internal IDs while keeping them actionable and human-readable.
  • Respect the 25,000-character limit by implementing pagination with has_more metadata and truncation messages.
  • Log full error details server-side using ctx.log_error() for debugging while returning minimal safe messages to the model.
  • Document expected errors in your SKILL.md files with specific error codes and recovery suggestions.

Frequently Asked Questions

How should I structure error messages for Claude to understand them best?

Keep error messages concise, specific, and actionable. According to the ComposioHQ/awesome-claude-skills best practices, use the format Error: {ErrorType}: {Description} without including stack traces or sensitive data. Include a suggestion for the next step, such as retrying or checking credentials, so the LLM can offer meaningful options to the user.

What is the difference between protocol-level errors and result-object errors in MCP?

Protocol-level errors terminate the entire MCP session and prevent the LLM from seeing any response. Result-object errors, which place error details inside the content array of a successful JSON-RPC response, allow the model to see the failure context and choose how to proceed. The repository guidelines explicitly recommend returning errors inside the result object to maintain conversational flow.

How do I prevent sensitive information from leaking through Claude Skill error messages?

Implement a centralized error formatter that redacts API keys, tokens, and internal IDs before constructing the response. Log the full error details with sensitive context server-side using ctx.log_error() or equivalent methods, but return only sanitized, generic messages to the LLM. This pattern is documented in mcp-builder/reference/mcp_best_practices.md as a security requirement for production skills.

What should I do when a tool response exceeds Claude's context window?

Enforce the global CHARACTER_LIMIT of approximately 25,000 characters by truncating large payloads and adding pagination metadata. Include has_more and next_offset fields in your response so the LLM can request additional data in subsequent calls. Always add a truncation_message explaining that results were limited and how to retrieve the complete dataset, as specified in the MCP best practices documentation.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →