When an LLM-powered tool call blows up, the model usually sees an opaque stack trace. It can’t tell whether to retry, give up, or suggest a fix, so users get “Something went wrong” or infinite retry loops.
Cursor’s editor solved this by wrapping errors in a small, structured JSON envelope:
``` Request ID: c90e… {"error":"ERROR_USER_ABORTED_REQUEST", "details":{"title":"User aborted request.", "isRetryable":false}, "isExpected":true} ```
That single structure lets the agent reason correctly (“don’t retry; the user cancelled”).
I wanted the same behavior in standalone MCP servers and LangChain tools, so I extracted it into a tiny package:
``` npm i @bjoaquinc/mcp-error-formatter ```
```ts import { formatMCPError } from "@bjoaquinc/mcp-error-formatter";
export async function githubTool(args) { try { const data = await github.repos.get(args.repo); return { content: [{ type: "text", text: JSON.stringify(data) }] }; } catch (err) { return formatMCPError(err, { title: "GitHub API failed", isRetryable: true, // optional flags }); } } ```
* Supports both structured and unstructured content * Zero deps (aside from `uuid`), \~3 kB min+gzip * Adds `isRetryable`, `isExpected`, `errorType`, `requestId`, and free-form `additionalInfo` * Returns a standard `CallToolResult`, so it slots into marimo, LangChain, FastMCP, or plain MCP SDK * Apache-2.0 (OSS)
Repo: [https://github.com/bjoaquinc/mcp-error-formatter](https://github.com/bjoaquinc/mcp-error-formatter)
I’d love feedback on the format, naming, or edge-cases I’ve missed. PRs and issues welcome—happy to iterate.