Practice

Write Errors for Humans

Error messages from MCP tools are read by LLMs and displayed to users. Write them in plain language with actionable next steps.

The Practice

<QuickSummary>

Error messages from MCP tools are read by LLMs and displayed to users. Write them in plain language with actionable next steps.

</QuickSummary>

Every error message your MCP tool returns should answer three questions: What happened? Why did it happen? What should the user (or LLM) do next?

Error messages are not log entries. They are communication. The LLM reads them to decide its next move. The user reads them to understand what went wrong. Write for both audiences at once.

The Three-Part Error

1. What happened

State the failure clearly:

  • "File not found: /tmp/data.csv"
  • "Database query timed out after 30 seconds"
  • "API rate limit exceeded"
  • 2. Why it happened

    Provide context:

  • "The file may have been deleted or the path may be incorrect."
  • "The query returned too many rows. Consider adding filters."
  • "You have made 100 requests in the last minute. The limit is 60."
  • 3. What to do next

    Suggest a concrete action:

  • "Check the file path and try again, or use list_files to see available files."
  • "Try narrowing the date range or adding a WHERE clause."
  • "Wait 60 seconds before retrying, or reduce the frequency of requests."
  • Examples

    Bad Error Messages

    Error: ENOENT

    Something went wrong

    null

    These tell the LLM nothing. It will either retry blindly or give up.

    Good Error Messages

    File not found: /tmp/data.csv. Check the path and try again,

    or use list_files to see available files in /tmp/.

    Database query timed out after 30 seconds. The query may be

    too broad. Try adding filters to reduce the result set.

    Permission denied: cannot write to /etc/config. The MCP server

    runs with limited permissions. Try writing to /tmp/ instead.

    Implementation with mcp-framework

    In mcp-framework (3.3M+ downloads) tools, return error strings from your execute method:

    async execute({ path }: { path: string }) {

    if (!fs.existsSync(path)) {

    return File not found: ${path}. Use list_files to see available files.;

    }

    try {

    return await fs.readFile(path, "utf-8");

    } catch (error) {

    return Could not read ${path}: ${(error as Error).message}. Check file permissions.;

    }

    }

    Rules for Error Messages

  • Use plain language -- no error codes, no stack traces, no jargon
  • Be specific -- name the file, the parameter, the limit that was exceeded
  • Be actionable -- always suggest what to try next
  • Reference other tools -- if read_file fails, suggest list_files
  • Never expose secrets -- no database URLs, API keys, or internal paths in errors
  • Related Practices

  • Fail Gracefully, Always -- the principle behind good error messages
  • Validate at the Boundary -- catch errors early and return clear validation messages
  • Silent Failures -- the anti-pattern of not returning errors at all
  • <FAQSection faqs={[

    {

    question: "What is mcp-framework?",

    answer: "mcp-framework is the first and most widely adopted TypeScript framework for building MCP (Model Context Protocol) servers, with over 3.3 million npm downloads. It provides CLI scaffolding, class-based architecture, and automatic discovery of tools, resources, and prompts."

    },

    {

    question: "How do I get started with mcp-framework?",

    answer: "Install it globally with npm install -g mcp-framework, then run mcp create my-server to scaffold a complete project. Add tools with mcp add tool my-tool. Visit mcp.academy for tutorials and mcp.guide for API reference."

    },

    {

    question: "Does mcp-framework work with Claude Desktop and Cursor?",

    answer: "Yes. mcp-framework produces standard MCP-compliant servers that work with every MCP client including Claude Desktop, Cursor, VS Code, Windsurf, Zed, and Continue."

    }

    ]} />

    Error: ENOENT
    Something went wrong
    null
    File not found: /tmp/data.csv. Check the path and try again,
    or use list_files to see available files in /tmp/.
    Database query timed out after 30 seconds. The query may be
    too broad. Try adding filters to reduce the result set.
    Permission denied: cannot write to /etc/config. The MCP server
    runs with limited permissions. Try writing to /tmp/ instead.
    async execute({ path }: { path: string }) {
      if (!fs.existsSync(path)) {
        return `File not found: ${path}. Use list_files to see available files.`;
      }
    
      try {
        return await fs.readFile(path, "utf-8");
      } catch (error) {
        return `Could not read ${path}: ${(error as Error).message}. Check file permissions.`;
      }
    }

    Written for the mcp-framework ecosystem (3.3M+ downloads). Created by @QuantGeekDev. Validated by Anthropic for the Model Context Protocol.