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

> Master Claude Skill error handling. Centralize with utility functions, use try-catch, return sanitized JSON-RPC errors for LLM-friendly workflows. Learn graceful error management.

- Repository: [Composio/awesome-claude-skills](https://github.com/composiohq/awesome-claude-skills)
- Tags: how-to-guide
- Published: 2026-07-26

---

**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`](https://github.com/ComposioHQ/awesome-claude-skills/blob/main/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`](https://github.com/ComposioHQ/awesome-claude-skills/blob/main/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`](https://github.com/ComposioHQ/awesome-claude-skills/blob/main/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`](https://github.com/ComposioHQ/awesome-claude-skills/blob/main/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`](https://github.com/ComposioHQ/awesome-claude-skills/blob/main/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`](https://github.com/ComposioHQ/awesome-claude-skills/blob/main/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`](https://github.com/ComposioHQ/awesome-claude-skills/blob/main/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`](https://github.com/ComposioHQ/awesome-claude-skills/blob/main/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

```python

# 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`](https://github.com/ComposioHQ/awesome-claude-skills/blob/main/mcp-builder/reference/python_mcp_server.md) for additional context.*

### TypeScript MCP Server Pattern

```typescript
// 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`](https://github.com/ComposioHQ/awesome-claude-skills/blob/main/mcp-builder/reference/node_mcp_server.md).*

### Skill Markdown Documentation Template

```markdown

### 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`](https://github.com/ComposioHQ/awesome-claude-skills/blob/main/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`](https://github.com/ComposioHQ/awesome-claude-skills/blob/main/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.