mcp-triage 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/LICENSE +21 -0
- package/README.md +111 -0
- package/dist/checks.d.ts +12 -0
- package/dist/checks.js +211 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +83 -0
- package/dist/clients.d.ts +13 -0
- package/dist/clients.js +116 -0
- package/dist/discover.d.ts +5 -0
- package/dist/discover.js +70 -0
- package/dist/fix.d.ts +24 -0
- package/dist/fix.js +131 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +11 -0
- package/dist/parse.d.ts +31 -0
- package/dist/parse.js +643 -0
- package/dist/report.d.ts +9 -0
- package/dist/report.js +99 -0
- package/dist/types.d.ts +86 -0
- package/dist/types.js +2 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +1 -0
- package/package.json +61 -0
package/dist/report.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// Report rendering: human-readable (default) and --json.
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import { CLIENTS } from "./clients.js";
|
|
4
|
+
const ORDER = ['error', 'warning', 'info'];
|
|
5
|
+
const LABEL = { error: 'ERROR', warning: 'WARN ', info: 'INFO ' };
|
|
6
|
+
const FIX_LABEL = {
|
|
7
|
+
fixed: 'FIXED',
|
|
8
|
+
'would-fix': 'DRY ',
|
|
9
|
+
'not-fixable': 'NOFIX',
|
|
10
|
+
skipped: 'SKIP ',
|
|
11
|
+
};
|
|
12
|
+
export function tilde(p) {
|
|
13
|
+
const home = os.homedir();
|
|
14
|
+
return p.startsWith(home) ? '~' + p.slice(home.length) : p;
|
|
15
|
+
}
|
|
16
|
+
function clientName(id) {
|
|
17
|
+
return CLIENTS.find((c) => c.id === id)?.name ?? id;
|
|
18
|
+
}
|
|
19
|
+
export function renderHuman(input, version, fixes) {
|
|
20
|
+
const lines = [];
|
|
21
|
+
lines.push(`MCP Triage v${version} — scanned ${input.files.length} config file(s)`);
|
|
22
|
+
lines.push('');
|
|
23
|
+
if (input.files.length === 0) {
|
|
24
|
+
lines.push('No MCP config files found in the standard locations for: ' + CLIENTS.map((c) => c.name).join(', '));
|
|
25
|
+
lines.push('If you expected one, pass a path explicitly: mcp-triage scan --file <path>');
|
|
26
|
+
return lines.join('\n');
|
|
27
|
+
}
|
|
28
|
+
for (const p of input.parsed) {
|
|
29
|
+
const flag = p.ok ? '✓' : '✗';
|
|
30
|
+
const n = p.servers.length;
|
|
31
|
+
const caveat = p.caveat ? ` (${p.caveat})` : '';
|
|
32
|
+
lines.push(` ${flag} ${clientName(p.clientId)} — ${tilde(p.file)} — ${n} server(s)${caveat}`);
|
|
33
|
+
}
|
|
34
|
+
lines.push('');
|
|
35
|
+
const sorted = [...input.diagnostics].sort((a, b) => ORDER.indexOf(a.severity) - ORDER.indexOf(b.severity));
|
|
36
|
+
if (sorted.length === 0) {
|
|
37
|
+
lines.push('All clear — no findings.');
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
lines.push(`Findings (${sorted.length}):`);
|
|
41
|
+
for (const d of sorted) {
|
|
42
|
+
const where = [clientName(d.clientId ?? ''), d.serverName ? `"${d.serverName}"` : ''].filter(Boolean).join(' · ');
|
|
43
|
+
lines.push(` [${LABEL[d.severity]}] ${d.checkId} — ${where ? where + ': ' : ''}${d.title}`);
|
|
44
|
+
if (d.detail)
|
|
45
|
+
for (const l of d.detail.split('\n'))
|
|
46
|
+
lines.push(` ${l}`);
|
|
47
|
+
if (d.hint)
|
|
48
|
+
lines.push(` → ${d.hint}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (fixes !== undefined) {
|
|
52
|
+
lines.push('');
|
|
53
|
+
if (fixes.length === 0) {
|
|
54
|
+
lines.push('Fix results: no repair candidates — no scanned file failed to parse.');
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
const dry = fixes.some((f) => f.status === 'would-fix');
|
|
58
|
+
lines.push(dry ? 'Fix results (dry run — nothing written):' : 'Fix results:');
|
|
59
|
+
for (const f of fixes) {
|
|
60
|
+
const what = f.changes.length > 0 ? `: ${f.changes.join(', ')}` : '';
|
|
61
|
+
lines.push(` [${FIX_LABEL[f.status]}] ${clientName(f.clientId)} — ${tilde(f.file)}${what}`);
|
|
62
|
+
if (f.reason)
|
|
63
|
+
lines.push(` → ${f.reason}`);
|
|
64
|
+
if (f.backupPath) {
|
|
65
|
+
lines.push(` → backup: ${tilde(f.backupPath)}${f.backupKept ? ' (existing backup kept)' : ''}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const counts = { error: 0, warning: 0, info: 0 };
|
|
71
|
+
for (const d of input.diagnostics)
|
|
72
|
+
counts[d.severity]++;
|
|
73
|
+
const servers = input.parsed.reduce((a, p) => a + p.servers.length, 0);
|
|
74
|
+
lines.push('');
|
|
75
|
+
lines.push(`Summary: ${counts.error} error(s), ${counts.warning} warning(s), ${counts.info} info — ${servers} server(s) across ${input.files.length} file(s).`);
|
|
76
|
+
return lines.join('\n');
|
|
77
|
+
}
|
|
78
|
+
export function renderJson(input, version, fixes) {
|
|
79
|
+
const counts = { error: 0, warning: 0, info: 0 };
|
|
80
|
+
for (const d of input.diagnostics)
|
|
81
|
+
counts[d.severity]++;
|
|
82
|
+
return JSON.stringify({
|
|
83
|
+
tool: 'mcp-triage',
|
|
84
|
+
version,
|
|
85
|
+
generatedAt: new Date().toISOString(),
|
|
86
|
+
files: input.parsed.map((p) => ({
|
|
87
|
+
clientId: p.clientId,
|
|
88
|
+
clientName: clientName(p.clientId),
|
|
89
|
+
file: p.file,
|
|
90
|
+
format: p.format,
|
|
91
|
+
ok: p.ok,
|
|
92
|
+
caveat: p.caveat,
|
|
93
|
+
servers: p.servers.map((s) => ({ name: s.name, command: s.command, url: s.url, transport: s.transport })),
|
|
94
|
+
})),
|
|
95
|
+
diagnostics: input.diagnostics,
|
|
96
|
+
...(fixes !== undefined ? { fixes } : {}),
|
|
97
|
+
summary: { counts, servers: input.parsed.reduce((a, p) => a + p.servers.length, 0), files: input.files.length },
|
|
98
|
+
}, null, 2);
|
|
99
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
export type Severity = 'error' | 'warning' | 'info';
|
|
2
|
+
export type Format = 'json' | 'toml' | 'yaml';
|
|
3
|
+
export interface Diagnostic {
|
|
4
|
+
/** Stable id, e.g. 'config.syntax', 'server.command-resolvable' */
|
|
5
|
+
checkId: string;
|
|
6
|
+
severity: Severity;
|
|
7
|
+
/** One-line, plain English */
|
|
8
|
+
title: string;
|
|
9
|
+
detail?: string;
|
|
10
|
+
hint?: string;
|
|
11
|
+
clientId?: string;
|
|
12
|
+
file?: string;
|
|
13
|
+
serverName?: string;
|
|
14
|
+
/** Whether `--fix` can address this in a future/current version */
|
|
15
|
+
fixable?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export interface ServerEntry {
|
|
18
|
+
name: string;
|
|
19
|
+
command?: string;
|
|
20
|
+
args?: string[];
|
|
21
|
+
env?: Record<string, string | undefined>;
|
|
22
|
+
url?: string;
|
|
23
|
+
transport?: string;
|
|
24
|
+
cwd?: string;
|
|
25
|
+
/** Explicitly disabled entries (OpenClaw `enabled: false`) — kept but not connected; runtime checks are skipped. */
|
|
26
|
+
enabled?: boolean;
|
|
27
|
+
}
|
|
28
|
+
export interface ParsedConfig {
|
|
29
|
+
clientId: string;
|
|
30
|
+
file: string;
|
|
31
|
+
format: Format;
|
|
32
|
+
ok: boolean;
|
|
33
|
+
/** Set when parsing is intentionally partial, e.g. 'toml-minimal', 'yaml-light' */
|
|
34
|
+
caveat?: string;
|
|
35
|
+
servers: ServerEntry[];
|
|
36
|
+
diagnostics: Diagnostic[];
|
|
37
|
+
}
|
|
38
|
+
export interface ClientSpec {
|
|
39
|
+
id: string;
|
|
40
|
+
name: string;
|
|
41
|
+
format: Format;
|
|
42
|
+
/**
|
|
43
|
+
* Config paths per platform. Placeholders: <home> <appdata> <config>.
|
|
44
|
+
* A single '*' segment is allowed for profile-style dirs (e.g. dsh profiles).
|
|
45
|
+
*/
|
|
46
|
+
paths: Partial<Record<'win32' | 'darwin' | 'linux' | 'any', string[]>>;
|
|
47
|
+
/** Project-level (relative to cwd) config paths */
|
|
48
|
+
projectPaths?: string[];
|
|
49
|
+
/** Where server entries live in the file (informational) */
|
|
50
|
+
serversHint: string;
|
|
51
|
+
/** File format is JSON5 (comments + trailing commas are legal) — parse leniently. */
|
|
52
|
+
json5?: boolean;
|
|
53
|
+
/** Env var that overrides the config path (e.g. OPENCLAW_CONFIG_PATH); scanned in addition when set. */
|
|
54
|
+
envOverride?: string;
|
|
55
|
+
/** True when the path/format is not yet verified against official docs (pre-release). */
|
|
56
|
+
verify?: boolean;
|
|
57
|
+
}
|
|
58
|
+
export interface DiscoveredFile {
|
|
59
|
+
clientId: string;
|
|
60
|
+
file: string;
|
|
61
|
+
format: Format;
|
|
62
|
+
scope: 'global' | 'project';
|
|
63
|
+
/** Inherited from the client spec (JSON5-tolerant parsing). */
|
|
64
|
+
json5?: boolean;
|
|
65
|
+
/** Unknown client (e.g. explicit --file): try a JSON5 fallback and report it as info when it applies. */
|
|
66
|
+
json5Fallback?: boolean;
|
|
67
|
+
}
|
|
68
|
+
export interface ScanResult {
|
|
69
|
+
files: DiscoveredFile[];
|
|
70
|
+
parsed: ParsedConfig[];
|
|
71
|
+
diagnostics: Diagnostic[];
|
|
72
|
+
}
|
|
73
|
+
export type FixStatus = 'fixed' | 'would-fix' | 'not-fixable' | 'skipped';
|
|
74
|
+
export interface FixOutcome {
|
|
75
|
+
file: string;
|
|
76
|
+
clientId: string;
|
|
77
|
+
status: FixStatus;
|
|
78
|
+
/** Human-readable list of repairs applied (or that would be applied). */
|
|
79
|
+
changes: string[];
|
|
80
|
+
/** Path of the backup written before the fix (absent for dry runs and non-writes). */
|
|
81
|
+
backupPath?: string;
|
|
82
|
+
/** True when an earlier backup existed and was kept (the file had been backed up before). */
|
|
83
|
+
backupKept?: boolean;
|
|
84
|
+
/** Why nothing was written (for not-fixable / skipped). */
|
|
85
|
+
reason?: string;
|
|
86
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const VERSION = "0.1.0";
|
package/dist/version.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const VERSION = '0.1.0';
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mcp-triage",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Triage broken MCP setups across 8 agent clients — find what is wrong, fix what is fixable, escalate the rest. Zero runtime dependencies.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"mcp-triage": "dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "dist/index.js",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./package.json": "./package.json"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"README.md",
|
|
20
|
+
"LICENSE"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsc -p tsconfig.json",
|
|
24
|
+
"test": "node --test tests/*.spec.ts",
|
|
25
|
+
"dev": "node src/cli.ts scan",
|
|
26
|
+
"prepare": "npm run build",
|
|
27
|
+
"prepack": "npm run build",
|
|
28
|
+
"prepublishOnly": "npm test"
|
|
29
|
+
},
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=20"
|
|
32
|
+
},
|
|
33
|
+
"author": "NeufAgents (neufagents.com)",
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "https://github.com/neufagents/mcp-triage.git"
|
|
38
|
+
},
|
|
39
|
+
"homepage": "https://github.com/neufagents/mcp-triage#readme",
|
|
40
|
+
"bugs": {
|
|
41
|
+
"url": "https://github.com/neufagents/mcp-triage/issues"
|
|
42
|
+
},
|
|
43
|
+
"keywords": [
|
|
44
|
+
"mcp",
|
|
45
|
+
"model-context-protocol",
|
|
46
|
+
"triage",
|
|
47
|
+
"doctor",
|
|
48
|
+
"diagnostics",
|
|
49
|
+
"cli",
|
|
50
|
+
"agent",
|
|
51
|
+
"claude",
|
|
52
|
+
"cursor",
|
|
53
|
+
"codex",
|
|
54
|
+
"windsurf",
|
|
55
|
+
"openclaw"
|
|
56
|
+
],
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"@types/node": "^26.6.1",
|
|
59
|
+
"typescript": "^5.7.0"
|
|
60
|
+
}
|
|
61
|
+
}
|