badgr-mcp-doctor 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 +132 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +68 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +139 -0
- package/dist/index.js.map +1 -0
- package/package.json +16 -0
package/README.md
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# badgr-mcp-doctor
|
|
2
|
+
|
|
3
|
+
Diagnose MCP server problems — SSE connectivity, malformed events, tools/list failures, timeouts — in one command.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npx badgr-mcp-doctor --url http://localhost:3000/sse
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
**Free. No signup required.** Runs entirely on your machine.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## The problem it solves
|
|
14
|
+
|
|
15
|
+
MCP servers fail silently. Your coding agent stops calling tools but gives no useful error. Is the server down? Is SSE broken? Are events malformed? Did `tools/list` time out? `badgr-mcp-doctor` runs the full diagnostic sequence and tells you exactly what's wrong.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Quick start
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# Diagnose an MCP server
|
|
23
|
+
npx badgr-mcp-doctor --url http://localhost:3000/sse
|
|
24
|
+
|
|
25
|
+
# Machine-readable JSON (for CI or scripts)
|
|
26
|
+
npx badgr-mcp-doctor --url http://localhost:3000/sse --json
|
|
27
|
+
|
|
28
|
+
# Dry-run a specific tool
|
|
29
|
+
npx badgr-mcp-doctor --url http://localhost:3000/sse --tool my_tool_name
|
|
30
|
+
|
|
31
|
+
# Longer timeout for slow servers
|
|
32
|
+
npx badgr-mcp-doctor --url http://localhost:3000/sse --timeout-ms 20000
|
|
33
|
+
|
|
34
|
+
# Show optional hosted monitoring link
|
|
35
|
+
npx badgr-mcp-doctor connect
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## CLI flags
|
|
41
|
+
|
|
42
|
+
| Flag | Description |
|
|
43
|
+
|------|-------------|
|
|
44
|
+
| `--url <url>` | MCP server SSE endpoint, e.g. `http://localhost:3000/sse` |
|
|
45
|
+
| `--timeout-ms <n>` | Connection timeout in milliseconds (default: `10000`) |
|
|
46
|
+
| `--tool <name>` | Optional: dry-run a specific tool by name |
|
|
47
|
+
| `--json` | Output machine-readable JSON |
|
|
48
|
+
|
|
49
|
+
**Commands:**
|
|
50
|
+
|
|
51
|
+
| Command | Description |
|
|
52
|
+
|---------|-------------|
|
|
53
|
+
| `connect` | Show the optional AI Badgr hosted MCP monitoring link |
|
|
54
|
+
|
|
55
|
+
**Exit codes:** `0` = all checks passed or warnings only, `1` = one or more failures, `2` = bad usage
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## What it checks
|
|
60
|
+
|
|
61
|
+
Each diagnostic runs in sequence and stops early if a hard failure is found:
|
|
62
|
+
|
|
63
|
+
| Step | Check |
|
|
64
|
+
|------|-------|
|
|
65
|
+
| 1 | **HTTP connection** — server responds to the SSE endpoint |
|
|
66
|
+
| 2 | **SSE event parsing** — events arrive and are valid JSON |
|
|
67
|
+
| 3 | **tools/list** — JSON-RPC call returns a list of tool objects |
|
|
68
|
+
| 4 | **Dry-run tool call** — invokes the first tool (or `--tool`) with `{dryRun: true}` |
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Example output
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
badgr-mcp-doctor — http://localhost:3000/sse
|
|
76
|
+
|
|
77
|
+
✓ HTTP connection: 200 OK
|
|
78
|
+
✓ SSE events: valid JSON payloads received
|
|
79
|
+
✗ tools/list: timeout after 10000ms
|
|
80
|
+
|
|
81
|
+
The server connected but tools/list never responded.
|
|
82
|
+
Common causes: server still initializing, blocking startup code, wrong endpoint path.
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
badgr-mcp-doctor — http://localhost:3000/sse
|
|
87
|
+
|
|
88
|
+
✓ HTTP connection: 200 OK
|
|
89
|
+
✓ SSE events: valid JSON payloads received
|
|
90
|
+
✓ tools/list: 4 tools found (search, read_file, write_file, run_command)
|
|
91
|
+
✓ Dry-run tool call: search responded without error
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## TypeScript API
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
import { runMcpDoctor } from "badgr-mcp-doctor";
|
|
100
|
+
|
|
101
|
+
const result = await runMcpDoctor({
|
|
102
|
+
url: "http://localhost:3000/sse",
|
|
103
|
+
timeoutMs: 15_000,
|
|
104
|
+
toolName: "my_tool", // optional: dry-run a specific tool
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
for (const check of result.checks) {
|
|
108
|
+
console.log(check.status, check.label, check.detail);
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**Types:**
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
interface McpDoctorOptions {
|
|
116
|
+
url: string;
|
|
117
|
+
timeoutMs?: number; // default: 10000
|
|
118
|
+
toolName?: string;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
interface McpDoctorResult {
|
|
122
|
+
checks: DiagnosticCheck[];
|
|
123
|
+
report: JsonReport;
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Requirements
|
|
130
|
+
|
|
131
|
+
- Node.js 18+
|
|
132
|
+
- An MCP server running and accessible at the given URL
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createLogger, fireTelemetry, formatChecks, parseNumber } from "badgr-shared";
|
|
3
|
+
import { runMcpDoctor } from "./index.js";
|
|
4
|
+
fireTelemetry({ package: "badgr-mcp-doctor", command: process.argv[2] });
|
|
5
|
+
function readArg(name) {
|
|
6
|
+
const index = process.argv.indexOf(name);
|
|
7
|
+
return index >= 0 ? process.argv[index + 1] : undefined;
|
|
8
|
+
}
|
|
9
|
+
const command = process.argv[2];
|
|
10
|
+
if (!command || command === "--help" || command === "-h") {
|
|
11
|
+
console.log(`badgr-mcp-doctor — local MCP server diagnostics
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
npx badgr-mcp-doctor --url <sse-url>
|
|
15
|
+
npx badgr-mcp-doctor --url <sse-url> --json
|
|
16
|
+
npx badgr-mcp-doctor connect
|
|
17
|
+
|
|
18
|
+
Flags:
|
|
19
|
+
--url <url> MCP server SSE endpoint (e.g. http://localhost:3000/sse)
|
|
20
|
+
--timeout-ms <n> Connection timeout in milliseconds (default: 10000)
|
|
21
|
+
--tool <name> Optional tool name for a dry-run call
|
|
22
|
+
--json Output machine-readable JSON
|
|
23
|
+
|
|
24
|
+
Commands:
|
|
25
|
+
connect Show the optional AI Badgr hosted monitoring link
|
|
26
|
+
|
|
27
|
+
Exit codes:
|
|
28
|
+
0 All checks passed or warnings only
|
|
29
|
+
1 One or more checks failed
|
|
30
|
+
2 Bad usage (missing required flag)
|
|
31
|
+
|
|
32
|
+
No signup required. Hosted monitoring is always optional.
|
|
33
|
+
|
|
34
|
+
Environment:
|
|
35
|
+
BADGR_TELEMETRY=0 Disable anonymous usage telemetry`);
|
|
36
|
+
process.exit(0);
|
|
37
|
+
}
|
|
38
|
+
if (command === "connect") {
|
|
39
|
+
console.log("Monitor this MCP server continuously with AI Badgr:");
|
|
40
|
+
console.log("https://aibadgr.com/mcp/connect");
|
|
41
|
+
console.log("");
|
|
42
|
+
console.log("Signup is only required if you enable recurring hosted monitoring.");
|
|
43
|
+
process.exit(0);
|
|
44
|
+
}
|
|
45
|
+
const url = readArg("--url");
|
|
46
|
+
if (!url) {
|
|
47
|
+
console.error("Error: --url is required");
|
|
48
|
+
console.error("Usage: npx badgr-mcp-doctor --url http://localhost:3000/sse [--json]");
|
|
49
|
+
process.exit(2);
|
|
50
|
+
}
|
|
51
|
+
const json = process.argv.includes("--json");
|
|
52
|
+
const result = await runMcpDoctor({ url, timeoutMs: parseNumber(readArg("--timeout-ms")), toolName: readArg("--tool") });
|
|
53
|
+
const logger = createLogger(json);
|
|
54
|
+
if (json) {
|
|
55
|
+
logger.report(result.report);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
logger.line("badgr-mcp-doctor report");
|
|
59
|
+
logger.line("");
|
|
60
|
+
logger.line(formatChecks(result.checks));
|
|
61
|
+
logger.line("");
|
|
62
|
+
logger.line(`Status: ${result.report.status}`);
|
|
63
|
+
logger.line("");
|
|
64
|
+
logger.line("Optional: monitor this MCP server continuously with AI Badgr:");
|
|
65
|
+
logger.line(" npx badgr-mcp-doctor connect");
|
|
66
|
+
}
|
|
67
|
+
process.exitCode = result.report.status === "failed" ? 1 : 0;
|
|
68
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AACtF,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C,aAAa,CAAC,EAAE,OAAO,EAAE,kBAAkB,EAAE,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;AAEzE,SAAS,OAAO,CAAC,IAAY;IAC3B,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AAC1D,CAAC;AAED,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAEhC,IAAI,CAAC,OAAO,IAAI,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;IACzD,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;;;;;;;;;2DAwB6C,CAAC,CAAC;IAC3D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;IAC1B,OAAO,CAAC,GAAG,CAAC,qDAAqD,CAAC,CAAC;IACnE,OAAO,CAAC,GAAG,CAAC,iCAAiC,CAAC,CAAC;IAC/C,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,oEAAoE,CAAC,CAAC;IAClF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAC7B,IAAI,CAAC,GAAG,EAAE,CAAC;IACT,OAAO,CAAC,KAAK,CAAC,0BAA0B,CAAC,CAAC;IAC1C,OAAO,CAAC,KAAK,CAAC,sEAAsE,CAAC,CAAC;IACtF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;AAC7C,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,EAAE,GAAG,EAAE,SAAS,EAAE,WAAW,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;AACzH,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;AAClC,IAAI,IAAI,EAAE,CAAC;IACT,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;AAC/B,CAAC;KAAM,CAAC;IACN,MAAM,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;IACvC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAChB,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;IACzC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAChB,MAAM,CAAC,IAAI,CAAC,WAAW,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IAC/C,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAChB,MAAM,CAAC,IAAI,CAAC,+DAA+D,CAAC,CAAC;IAC7E,MAAM,CAAC,IAAI,CAAC,gCAAgC,CAAC,CAAC;AAChD,CAAC;AACD,OAAO,CAAC,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type DiagnosticCheck, type JsonReport } from "badgr-shared";
|
|
2
|
+
export interface McpDoctorOptions {
|
|
3
|
+
url: string;
|
|
4
|
+
timeoutMs?: number;
|
|
5
|
+
toolName?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface McpDoctorResult {
|
|
8
|
+
checks: DiagnosticCheck[];
|
|
9
|
+
report: JsonReport;
|
|
10
|
+
}
|
|
11
|
+
export declare function parseSseEvents(chunk: string): DiagnosticCheck[];
|
|
12
|
+
export declare function runMcpDoctor(options: McpDoctorOptions): Promise<McpDoctorResult>;
|
|
13
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAA0D,KAAK,eAAe,EAAE,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AAE7H,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,MAAM,EAAE,UAAU,CAAC;CACpB;AAOD,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,eAAe,EAAE,CAgB/D;AAED,wBAAsB,YAAY,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC,CAqCtF"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { checkToFinding, createReport, reportStatusFromFindings } from "badgr-shared";
|
|
2
|
+
export function parseSseEvents(chunk) {
|
|
3
|
+
const checks = [];
|
|
4
|
+
const events = chunk.split(/\r?\n\r?\n/).filter(Boolean);
|
|
5
|
+
for (const event of events) {
|
|
6
|
+
const dataLines = event.split(/\r?\n/).filter((line) => line.startsWith("data:"));
|
|
7
|
+
if (dataLines.length === 0)
|
|
8
|
+
continue;
|
|
9
|
+
const payload = dataLines.map((line) => line.slice(5).trimStart()).join("\n");
|
|
10
|
+
try {
|
|
11
|
+
JSON.parse(payload);
|
|
12
|
+
checks.push({ name: "sse-parse", status: "pass", message: "SSE event contained valid JSON payload" });
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
checks.push({ name: "sse-parse", status: "fail", message: "SSE event contained malformed multiline payload", details: { payloadPreview: payload.slice(0, 120) } });
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
if (checks.length === 0)
|
|
19
|
+
checks.push({ name: "sse-parse", status: "warn", message: "No SSE data events observed during parsing window" });
|
|
20
|
+
return checks;
|
|
21
|
+
}
|
|
22
|
+
export async function runMcpDoctor(options) {
|
|
23
|
+
const timeoutMs = options.timeoutMs ?? 10_000;
|
|
24
|
+
const checks = [];
|
|
25
|
+
const controller = new AbortController();
|
|
26
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
27
|
+
try {
|
|
28
|
+
const response = await fetch(options.url, { headers: { accept: "text/event-stream, application/json" }, signal: controller.signal });
|
|
29
|
+
checks.push({ name: "connection", status: response.ok ? "pass" : "fail", message: response.ok ? "connection established" : `connection failed with HTTP ${response.status}` });
|
|
30
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
31
|
+
if (contentType.includes("text/event-stream")) {
|
|
32
|
+
const text = await readSomeText(response, timeoutMs);
|
|
33
|
+
checks.push(...parseSseEvents(text));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
checks.push({ name: "connection", status: "fail", message: error instanceof Error && error.name === "AbortError" ? `connection timed out after ${timeoutMs / 1000}s` : `connection failed: ${String(error)}` });
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
clearTimeout(timer);
|
|
41
|
+
}
|
|
42
|
+
const rpcChecks = await runJsonRpcChecks(options.url, timeoutMs, options.toolName);
|
|
43
|
+
checks.push(...rpcChecks);
|
|
44
|
+
const findings = checks.map((check) => checkToFinding(check, "MCP"));
|
|
45
|
+
const status = reportStatusFromFindings(findings);
|
|
46
|
+
const nextCommand = "npx @aibadgr/mcp-doctor connect";
|
|
47
|
+
const report = createReport({
|
|
48
|
+
tool: "mcp-doctor",
|
|
49
|
+
status,
|
|
50
|
+
summary: status === "failed" ? "MCP server has failing diagnostics" : status === "warning" ? "MCP server has warnings" : "MCP server diagnostics passed",
|
|
51
|
+
findings,
|
|
52
|
+
recommendedActions: buildRecommendedActions(checks),
|
|
53
|
+
nextCommand,
|
|
54
|
+
actionUrl: "https://aibadgr.com/mcp/connect",
|
|
55
|
+
metadata: { url: options.url, timeoutMs },
|
|
56
|
+
});
|
|
57
|
+
return { checks, report };
|
|
58
|
+
}
|
|
59
|
+
async function readSomeText(response, timeoutMs) {
|
|
60
|
+
if (!response.body)
|
|
61
|
+
return await response.text();
|
|
62
|
+
const reader = response.body.getReader();
|
|
63
|
+
const timeout = new Promise((_, reject) => setTimeout(() => reject(new Error("read timeout")), Math.min(timeoutMs, 1500)));
|
|
64
|
+
try {
|
|
65
|
+
const { value } = await Promise.race([reader.read().then((result) => result), timeout.then((value) => ({ value, done: false }))]);
|
|
66
|
+
return Buffer.from(value ?? new Uint8Array()).toString("utf8");
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return "";
|
|
70
|
+
}
|
|
71
|
+
finally {
|
|
72
|
+
reader.releaseLock();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async function runJsonRpcChecks(url, timeoutMs, toolName) {
|
|
76
|
+
const checks = [];
|
|
77
|
+
const endpoint = url.replace(/\/sse\/?$/, "");
|
|
78
|
+
const tools = await callJsonRpc(endpoint, "tools/list", {}, timeoutMs);
|
|
79
|
+
if (tools.timedOut) {
|
|
80
|
+
checks.push({ name: "tools-list", status: "fail", message: `tools/list timed out after ${timeoutMs / 1000}s` });
|
|
81
|
+
return checks;
|
|
82
|
+
}
|
|
83
|
+
if (tools.error) {
|
|
84
|
+
checks.push({ name: "tools-list", status: "warn", message: `tools/list could not be checked over JSON-RPC: ${tools.error}` });
|
|
85
|
+
return checks;
|
|
86
|
+
}
|
|
87
|
+
const result = tools.response?.result;
|
|
88
|
+
const count = Array.isArray(result?.tools) ? result.tools.length : 0;
|
|
89
|
+
checks.push({ name: "tools-list", status: Array.isArray(result?.tools) ? "pass" : "fail", message: Array.isArray(result?.tools) ? `tools/list returned ${count} tools` : "tools/list returned malformed result" });
|
|
90
|
+
const dryRunName = toolName ?? (Array.isArray(result?.tools) && typeof result.tools[0]?.name === "string" ? String(result.tools[0].name) : undefined);
|
|
91
|
+
if (!dryRunName) {
|
|
92
|
+
checks.push({ name: "dry-run-tool", status: "warn", message: "No tool available for dry-run call" });
|
|
93
|
+
return checks;
|
|
94
|
+
}
|
|
95
|
+
const dryRun = await callJsonRpc(endpoint, "tools/call", { name: dryRunName, arguments: { dryRun: true } }, timeoutMs);
|
|
96
|
+
if (dryRun.timedOut)
|
|
97
|
+
checks.push({ name: "dry-run-tool", status: "fail", message: `tool call timed out after ${timeoutMs / 1000}s` });
|
|
98
|
+
else if (dryRun.error)
|
|
99
|
+
checks.push({ name: "dry-run-tool", status: "warn", message: `dry-run tool call returned an error: ${dryRun.error}` });
|
|
100
|
+
else
|
|
101
|
+
checks.push({ name: "dry-run-tool", status: "pass", message: "dry-run tool call returned a result" });
|
|
102
|
+
return checks;
|
|
103
|
+
}
|
|
104
|
+
async function callJsonRpc(url, method, params, timeoutMs) {
|
|
105
|
+
const controller = new AbortController();
|
|
106
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
107
|
+
try {
|
|
108
|
+
const response = await fetch(url, {
|
|
109
|
+
method: "POST",
|
|
110
|
+
headers: { "content-type": "application/json", accept: "application/json" },
|
|
111
|
+
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params }),
|
|
112
|
+
signal: controller.signal,
|
|
113
|
+
});
|
|
114
|
+
const text = await response.text();
|
|
115
|
+
if (!response.ok)
|
|
116
|
+
return { error: `HTTP ${response.status}` };
|
|
117
|
+
return { response: JSON.parse(text) };
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
if (error instanceof Error && error.name === "AbortError")
|
|
121
|
+
return { timedOut: true };
|
|
122
|
+
return { error: String(error) };
|
|
123
|
+
}
|
|
124
|
+
finally {
|
|
125
|
+
clearTimeout(timer);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function buildRecommendedActions(checks) {
|
|
129
|
+
const actions = [];
|
|
130
|
+
if (checks.some((check) => check.name === "sse-parse" && check.status === "fail"))
|
|
131
|
+
actions.push("Fix malformed multiline SSE event payloads");
|
|
132
|
+
if (checks.some((check) => check.message.includes("timed out")))
|
|
133
|
+
actions.push("Add tool-level timeout handling or reduce long-running tool work");
|
|
134
|
+
if (checks.some((check) => check.name === "tools-list" && check.status !== "pass"))
|
|
135
|
+
actions.push("Verify the MCP tools/list JSON-RPC response shape");
|
|
136
|
+
actions.push("Monitor this MCP server continuously with AI Badgr");
|
|
137
|
+
return actions;
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "badgr-mcp-doctor",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI diagnostics for MCP connection, tools/list, timeouts, SSE parsing, and dry-run tool calls.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"bin": { "badgr-mcp-doctor": "dist/cli.js" },
|
|
9
|
+
"exports": { ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" } },
|
|
10
|
+
"files": ["dist", "README.md"],
|
|
11
|
+
"scripts": { "build": "tsc -b", "typecheck": "tsc -b --pretty false", "test": "vitest run" },
|
|
12
|
+
"dependencies": { "badgr-shared": "0.1.1" },
|
|
13
|
+
"engines": { "node": ">=18.0.0" },
|
|
14
|
+
"keywords": ["mcp", "sse", "diagnostics", "tools", "timeout"],
|
|
15
|
+
"license": "MIT"
|
|
16
|
+
}
|