failecho-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -0
- package/bin/failecho-mcp.js +124 -0
- package/package.json +14 -0
package/README.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# failecho-mcp
|
|
2
|
+
|
|
3
|
+
Stdio MCP server that relays to [FailEcho](https://failecho.com): before your
|
|
4
|
+
agent retries a failed tool, check what other agents already tried and whether
|
|
5
|
+
it worked.
|
|
6
|
+
|
|
7
|
+
```json
|
|
8
|
+
{
|
|
9
|
+
"mcpServers": {
|
|
10
|
+
"failecho": {
|
|
11
|
+
"command": "npx",
|
|
12
|
+
"args": ["-y", "failecho-mcp"]
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
For hosts that can only start a local process. If your client speaks
|
|
19
|
+
Streamable HTTP, point it at `https://failecho.com/mcp` directly instead —
|
|
20
|
+
one less moving part.
|
|
21
|
+
|
|
22
|
+
## What it is
|
|
23
|
+
|
|
24
|
+
A relay, not a second FailEcho. It has no database and stores nothing: every
|
|
25
|
+
message is forwarded to the shared network and the reply handed back, so you
|
|
26
|
+
get the same four tools, with the same descriptions and the same evidence, as
|
|
27
|
+
using the URL directly.
|
|
28
|
+
|
|
29
|
+
- `check_tool_failure` — before a retry
|
|
30
|
+
- `report_tool_failure`, `report_tool_success` — failure rates need a denominator
|
|
31
|
+
- `report_recovery_outcome` — what actually fixed it
|
|
32
|
+
|
|
33
|
+
No account, no API key. Zero dependencies, Node 18+.
|
|
34
|
+
|
|
35
|
+
## Environment
|
|
36
|
+
|
|
37
|
+
| Variable | Default | Purpose |
|
|
38
|
+
|---|---|---|
|
|
39
|
+
| `FAILECHO_URL` | `https://failecho.com/mcp` | Network to relay to. Point it at your own server if you self-host. |
|
|
40
|
+
| `FAILECHO_REPORTER_ID` | unset | Optional. Salted and hashed on arrival; lets FailEcho tell your evidence from someone else's. |
|
|
41
|
+
|
|
42
|
+
## Privacy
|
|
43
|
+
|
|
44
|
+
Metadata only: the service, the operation, an error class and code, how long
|
|
45
|
+
the call took. Never prompts, tool arguments, tool results, headers or keys.
|
|
46
|
+
Error text is normalised server-side and the raw string discarded.
|
|
47
|
+
|
|
48
|
+
MIT. Source: https://github.com/FailEcho/failecho
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* FailEcho over stdio, for MCP hosts that can only start a local process.
|
|
4
|
+
*
|
|
5
|
+
* A relay, not a second FailEcho: it has no database and stores nothing. Every
|
|
6
|
+
* message is forwarded to the shared network and the reply handed straight
|
|
7
|
+
* back, so this serves the same four tools, with the same descriptions and the
|
|
8
|
+
* same evidence, as pointing a client at the URL directly.
|
|
9
|
+
*
|
|
10
|
+
* No dependencies on purpose. `npx` should fetch one small thing and start,
|
|
11
|
+
* because a person deciding whether to try this will not wait.
|
|
12
|
+
*/
|
|
13
|
+
"use strict";
|
|
14
|
+
|
|
15
|
+
const DEFAULT_URL = "https://failecho.com/mcp";
|
|
16
|
+
const VERSION = require("../package.json").version;
|
|
17
|
+
const TIMEOUT_MS = 15000;
|
|
18
|
+
|
|
19
|
+
const url = process.env.FAILECHO_URL || DEFAULT_URL;
|
|
20
|
+
|
|
21
|
+
const headers = {
|
|
22
|
+
"Content-Type": "application/json",
|
|
23
|
+
Accept: "application/json, text/event-stream",
|
|
24
|
+
"User-Agent": `failecho-mcp-node/${VERSION}`,
|
|
25
|
+
};
|
|
26
|
+
if (process.env.FAILECHO_REPORTER_ID) {
|
|
27
|
+
headers["X-Reporter-ID"] = process.env.FAILECHO_REPORTER_ID;
|
|
28
|
+
}
|
|
29
|
+
if (process.env.FAILECHO_OPERATOR_TOKEN) {
|
|
30
|
+
headers["X-FailEcho-Operator"] = process.env.FAILECHO_OPERATOR_TOKEN;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const log = (m) => process.stderr.write(`failecho-mcp: ${m}\n`);
|
|
34
|
+
|
|
35
|
+
function write(message) {
|
|
36
|
+
process.stdout.write(JSON.stringify(message) + "\n");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Server-sent events carry the JSON-RPC message in `data:` lines. */
|
|
40
|
+
function fromEventStream(text) {
|
|
41
|
+
const payloads = [];
|
|
42
|
+
for (const line of text.split(/\r?\n/)) {
|
|
43
|
+
if (line.startsWith("data:")) {
|
|
44
|
+
const body = line.slice(5).trim();
|
|
45
|
+
if (body) payloads.push(body);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return payloads;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function forward(message) {
|
|
52
|
+
const controller = new AbortController();
|
|
53
|
+
const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
|
54
|
+
try {
|
|
55
|
+
const response = await fetch(url, {
|
|
56
|
+
method: "POST",
|
|
57
|
+
headers,
|
|
58
|
+
body: JSON.stringify(message),
|
|
59
|
+
signal: controller.signal,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Notifications are accepted with no body; there is nothing to hand back.
|
|
63
|
+
if (response.status === 202 || response.status === 204) return;
|
|
64
|
+
|
|
65
|
+
const text = await response.text();
|
|
66
|
+
if (!text.trim()) return;
|
|
67
|
+
|
|
68
|
+
const type = response.headers.get("content-type") || "";
|
|
69
|
+
const chunks = type.includes("text/event-stream")
|
|
70
|
+
? fromEventStream(text)
|
|
71
|
+
: [text];
|
|
72
|
+
|
|
73
|
+
for (const chunk of chunks) {
|
|
74
|
+
try {
|
|
75
|
+
write(JSON.parse(chunk));
|
|
76
|
+
} catch {
|
|
77
|
+
log(`ignored an unparseable reply: ${chunk.slice(0, 120)}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
} catch (error) {
|
|
81
|
+
// A relay that dies on a network blip takes the host's session with it.
|
|
82
|
+
// Answer the request instead, so the agent sees a failure it can handle.
|
|
83
|
+
const reason = error && error.name === "AbortError"
|
|
84
|
+
? `no response from ${url} within ${TIMEOUT_MS} ms`
|
|
85
|
+
: `${(error && error.message) || error}`;
|
|
86
|
+
log(reason);
|
|
87
|
+
if (message && message.id !== undefined && message.id !== null) {
|
|
88
|
+
write({
|
|
89
|
+
jsonrpc: "2.0",
|
|
90
|
+
id: message.id,
|
|
91
|
+
error: { code: -32000, message: `FailEcho unreachable: ${reason}` },
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
} finally {
|
|
95
|
+
clearTimeout(timer);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// One message per line, and requests are answered in the order they arrive so
|
|
100
|
+
// a slow call cannot reorder the stream underneath the host.
|
|
101
|
+
let buffer = "";
|
|
102
|
+
let queue = Promise.resolve();
|
|
103
|
+
|
|
104
|
+
process.stdin.setEncoding("utf8");
|
|
105
|
+
process.stdin.on("data", (chunk) => {
|
|
106
|
+
buffer += chunk;
|
|
107
|
+
let index;
|
|
108
|
+
while ((index = buffer.indexOf("\n")) !== -1) {
|
|
109
|
+
const line = buffer.slice(0, index).trim();
|
|
110
|
+
buffer = buffer.slice(index + 1);
|
|
111
|
+
if (!line) continue;
|
|
112
|
+
let message;
|
|
113
|
+
try {
|
|
114
|
+
message = JSON.parse(line);
|
|
115
|
+
} catch {
|
|
116
|
+
log(`ignored an unparseable line from the host: ${line.slice(0, 120)}`);
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
queue = queue.then(() => forward(message));
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
process.stdin.on("end", () => queue.then(() => process.exit(0)));
|
|
124
|
+
log(`relaying to ${url}`);
|
package/package.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "failecho-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Stdio MCP server that relays to the FailEcho network: check what other agents hit before retrying a failed tool.",
|
|
5
|
+
"bin": { "failecho-mcp": "bin/failecho-mcp.js" },
|
|
6
|
+
"files": ["bin", "README.md"],
|
|
7
|
+
"engines": { "node": ">=18" },
|
|
8
|
+
"keywords": ["mcp", "model-context-protocol", "agents", "reliability", "retries", "failures"],
|
|
9
|
+
"homepage": "https://failecho.com",
|
|
10
|
+
"repository": { "type": "git", "url": "git+https://github.com/FailEcho/failecho.git", "directory": "npm-relay" },
|
|
11
|
+
"bugs": { "url": "https://github.com/FailEcho/failecho/issues" },
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"author": "FailEcho (https://failecho.com)"
|
|
14
|
+
}
|