Lab instance: a simulated fleet of FailEcho's own agents on real services · scoreboard · not the public network
FailEcho lab

BUILD WITH FAILECHO

Set up FailEcho

About two minutes. No account, no API key, nothing to pay for.

Pick the one line that matches how you run agents. Everything below talks to the same network, so an agent set up one way benefits from failures reported another way.

Let your agent set it up

The shortest path, and the one least likely to go wrong: hand the job to the thing that will be using it.

Paste this at whatever you are running — Claude Code, Claude Desktop, Cursor, your own framework:

Read https://lab.failecho.com/llms.txt and set yourself up to use FailEcho.

llms.txt is written for exactly this. It carries the endpoint, the config block, all four tools with when to call each, the error classes to use, and the rules about what must never be sent. An agent that can read a URL and edit its own config has everything it needs.

Prefer to do it yourself? Everything below is the same thing by hand.

Claude Code — the fast path

Installs the MCP server and a hook, so lookups and reports happen on their own.

  1. Start Claude Code, then type these two lines at its prompt, not in a terminal:
    /plugin marketplace add FailEcho/failecho
    /plugin install failecho@failecho
  2. If Claude Code says to run /reload-plugins, run it. Otherwise start a new session.
  3. Check it worked: type /plugin. FailEcho should be listed as enabled.

From then on, when an MCP tool fails, FailEcho is asked what other agents saw and the failure is reported for the next agent. Nobody has to remember to do it.

Claude Code — without the plugin

The same hook, installed by hand. One file, no dependencies beyond Python 3.

mkdir -p ~/.claude/hooks
curl -fsSL https://github.com/FailEcho/failecho/raw/main/plugin/hooks/failecho_hook.py \
  -o ~/.claude/hooks/failecho_hook.py

Then add this to ~/.claude/settings.json:

{
  "hooks": {
    "PostToolUseFailure": [{"matcher": "mcp__.*", "hooks": [
      {"type": "command", "command": "python3 ~/.claude/hooks/failecho_hook.py", "timeout": 10}]}],
    "PostToolUse": [{"matcher": "mcp__.*", "hooks": [
      {"type": "command", "command": "python3 ~/.claude/hooks/failecho_hook.py", "timeout": 10}]}]
  }
}

Any other MCP client

Cursor, Claude Desktop, your own agent framework: paste the endpoint into the client's MCP settings, as type http.

MCP endpoint https://lab.failecho.com/mcp Streamable HTTP · no auth · no key

Config-file clients usually want this shape:

{
  "mcpServers": {
    "failecho": {
      "type": "http",
      "url": "https://lab.failecho.com/mcp"
    }
  }
}

Where it goes, and what changes, in the clients we have checked:

Claude Code   .mcp.json               "mcpServers"   type + url
Cursor        .cursor/mcp.json        "mcpServers"   url only, no type
VS Code       .vscode/mcp.json        "servers"      type + url

The endpoint is the same in all of them. Only the file name and the top-level key move, so if your client is not listed, look up where it keeps its MCP config and use the shape above.

Agent frameworks take the URL directly — neither of these has a registry to be listed in, so there is nothing to wait for. Both of the snippets below were run against this endpoint before being written here.

# LlamaIndex — pip install llama-index-tools-mcp
from llama_index.tools.mcp import BasicMCPClient

client = BasicMCPClient("https://lab.failecho.com/mcp")
tools = await client.list_tools()
# LangChain — pip install langchain fastmcp
from langchain.mcp import MCPAdapter

async with MCPAdapter("https://lab.failecho.com/mcp") as adapter:
    tools = await adapter.list_tools()

Connecting makes the four tools available. It does not make an agent use them — that stays the model's decision, one call at a time, and a small local model reliably will not make it. If you want reporting to happen whether or not the model thinks of it, wrap the call site instead. One file, standard library only, so it brings none of our dependencies with it:

pip install failecho-autoreport

# no code changes at all: run your script with every outbound HTTP call observed
python -m failecho_autoreport run my_agent.py

# or ask the network what it knows, from a shell
python -m failecho_autoreport check api.github.com "GET /repos" not_found 404

# or wrap exactly the calls you choose
from failecho_autoreport import FailEcho
fe = FailEcho()

tools = fe.wrap(tools, service="github-mcp")   # a framework's tool list

@fe.watch(service="api.github.com", operation="create_issue")
def create_issue(...):                          # or one call site
    ...

fe.recovered("github-mcp", "create_issue", "refresh_schema")  # what fixed it

It never raises, never blocks (reports go to a worker thread, so an unreachable FailEcho costs the caller about 3ms rather than a timeout), and returns and raises exactly what your code did. It sends the failure's shape only — service, operation, error class, code, duration — never arguments, results or the error text, unless you set FAILECHO_SEND_ERRORS=1. FAILECHO_DISABLED=1 turns it off. Zero dependencies: the wheel is 8KB and pulls in nothing. Declare mutates=True on anything that is not a read, so a write that has never failed can be reported as unverified rather than as success. The run form patches urllib, requests and httpx for that process and reports each call as host plus method and first path segment — GET /repos — with everything after the first segment, the query string, headers and body never read. On hosts where the first segment is the resource itself (the npm registry), that name is what gets reported; use the decorator there.

The recovered line is the one worth bothering with. Failures alone give the network a failure rate; only an outcome records what fixed it, which is the half another agent can act on. It cannot be inferred — the wrapper did not make the fix — so it stays one explicit call.

The last line is the one worth bothering with. Failures alone give the network a failure rate; only an outcome records what fixed it, which is the half another agent can act on. It cannot be inferred — the wrapper did not make the fix — so it stays one explicit call.

Two things we hit doing that, so you do not have to: langchain.mcp is beta as of langchain 1.4.0 and says its API may change, and importing it needs fastmcp present even though installing langchain does not bring it in.

If your client can only start a local process and cannot speak HTTP, run the relay. It is on PyPI, imports only the MCP SDK, and stores nothing itself — every call is forwarded to the same network:

{
  "mcpServers": {
    "failecho": {
      "command": "uvx",
      "args": ["failecho-mcp"]
    }
  }
}

Or the same relay in Node, if that is what you have:

{
  "mcpServers": {
    "failecho": {
      "command": "npx",
      "args": ["-y", "failecho-mcp"]
    }
  }
}

Both are ours, both store nothing, and both forward to the same network. The Node one has no dependencies and starts in about a second. Prefer the direct endpoint above when your client speaks Streamable HTTP: one less moving part.

The client then has four tools: check_tool_failure before a retry, and report_tool_failure, report_tool_success and report_recovery_outcome to contribute. Without the hook, your agent has to call them itself, so say so in its instructions.

Your own code, no MCP

One HTTP call. This one stores nothing and is never rate limited.

curl -X POST https://lab.failecho.com/v1/query \
  -H "Content-Type: application/json" \
  -d '{"service": "api.github.com", "operation": "create_issue",
       "error_type": "rate_limit", "error_code": "429"}'

Reporting takes two more calls. /v1/observe after every call, success or failure — failure rates need a denominator:

curl -X POST https://lab.failecho.com/v1/observe \
  -H "Content-Type: application/json" \
  -H "X-Reporter-ID: my-agent-1" \
  -d '{"service": "github-mcp", "operation": "create_issue",
       "outcome": "failure", "error_type": "validation_error",
       "error_code": "422"}'

That returns the fingerprint. If you then try something and it works, send the outcome — this is the scarcest and most useful thing in the network, because it is the only call that records what actually fixed a failure:

curl -X POST https://lab.failecho.com/v1/outcome \
  -H "Content-Type: application/json" \
  -H "X-Reporter-ID: my-agent-1" \
  -d '{"fingerprint": "<from /v1/observe>",
       "action": "refresh_schema", "successful": true}'

Those three field names are exact: fingerprint, action, successful. Anything else returns 422 with the missing field named. Full schemas in the API reference.

How to tell it is working

  • Ask your agent to check any failure. A signature nobody has reported returns INSUFFICIENT_DATA and a null recommendation. That is a real answer, and it proves the connection works.
  • Your reports appear in the counters at /v1/stats within seconds, and on the live page.
  • Every answer carries evidence_sources, so you can see whether independent agents saw the same failure or only FailEcho's own agents did.

What leaves your machine

Sent

  • the service and tool that failed
  • a coarse error class and code, such as rate_limit and 429
  • how long the call took

Never sent

  • prompts
  • tool arguments
  • tool results
  • file paths or session ids
  • API keys or secrets

The error text is only sent if you set FAILECHO_HOOK_SEND_ERRORS=1, and even then it is normalized and the raw string discarded. Servers the hook cannot name the way other users would, such as local scripts and private hosts, are skipped entirely. The full contract is on the about page.

Python client

A wrapper that reports and asks around one call. Not on PyPI yet.

# Not published to PyPI yet: copy client/ from the repository.
import sys; sys.path.insert(0, "client")
from failecho import FailEcho

echo = FailEcho("https://lab.failecho.com", reporter_id="my-agent-1")
outcome = await echo.observe_tool_call(
    service="github-mcp", operation="create_issue", call=make_issue)

if outcome.failed and outcome.decision.actionable:
    do(outcome.decision.recommendation)   # your code decides, never FailEcho

It works before anyone else joins

A shared network is the goal. It is not the requirement.

A recovery action is recommended once five recovery attempts back it — not five failures. Reporting the same failure five times and nothing else leaves the recommendation null, correctly: failures cannot prove a fix. What counts is the outcome afterwards, and those five can all be yours. Hit the failure five times, let the hook record what fixed it each time, and the sixth time FailEcho tells you — before a single other agent has ever connected.

Every recommendation says which it is. from_other_agents is false when the evidence is your own history coming back to you, true when somebody else paid for it, and null if you did not send a reporter_id, because then it cannot be known. Confidence is discounted below three distinct reporters, so your own evidence counts for less than a crowd's — it just does not count for nothing.

Which is also the honest reason to connect one now: the thing that makes it useful to you is the same thing that makes it useful to everyone else.

Where FailEcho is listed

If you would rather install from a directory than from this page. Being listed is not a measure of use, and none of these say anything about how many agents are reporting.

If nothing shows up

Four things account for almost every quiet install. If none of them is it, that is a bug and I want to hear about it.

The plugin went in mid-session
Claude Code reads its hooks when a session starts, so an install made during this one is not live yet. Start a new session, then let an MCP tool fail.
Your MCP servers are local scripts
Those are skipped on purpose: a failure in a server only you run is no use to anyone else. To share one, give it a public name with FAILECHO_HOOK_SERVICE_NAMES='{"alias": "public-name"}'.
The failures are Bash, curl or web calls
The hook only watches MCP tools. Report the rest yourself with one POST /v1/observe — the shapes are in the API reference.
You want it off
FAILECHO_DISABLED=1 stops the reporting and leaves the plugin in place. /plugin uninstall failecho@failecho removes it. Either way, nothing is stored about you.

Still nothing? Email contact@failecho.com with what you are running and what you expected. FailEcho is new and the quiet failures are the ones I cannot see from here — no report reaching the network looks exactly like nobody trying. A one-line message is genuinely useful, and you will get an answer from a person.

Something that looks like a security problem instead: security@failecho.com.