mcp-medic 1.2.8 โ 1.2.9
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 +5 -3
- package/dist/cli.js +10 -2
- package/dist/fleet.js +4 -1
- package/dist/junit.js +35 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -32,7 +32,7 @@ This is the whole point (a real handshake, not a schema guess) โ but it means
|
|
|
32
32
|
- ๐ **Auto-Discovery**: Run `mcp-medic check` with no arguments to auto-discover Claude Desktop, `.mcp.json`, and VS Code/Cursor MCP configuration paths across macOS, Windows, and Linux.
|
|
33
33
|
- ๐ก **Auto-Fix Suggestions**: Diagnose issues with clear, actionable fix suggestions using `--show-fixes`.
|
|
34
34
|
- ๐ **Registry Validation**: Validate published registry entries directly using `mcp-medic check --registry <server-id>`.
|
|
35
|
-
-
|
|
35
|
+
- **Fleet Validation**: Scan and validate monorepos or multi-team configurations with deterministic `*`/`**` glob matching, bounded parallel checks, and CI-ready failure handling.
|
|
36
36
|
- ๐งช **Drift Detection** (experimental): Catch environment divergence between staging and production configs with `mcp-medic diff <configA> <configB>`.
|
|
37
37
|
- ๐ **Policy-as-Code**: Enforce organizational constraints (banned transports, domain allowlists, minimum description lengths, a minimum quality score, a max tool count, required tool descriptions) via `.mcp-medic-policy.json` / `--policy`.
|
|
38
38
|
- ๐ธ **Snapshot Baseline Mode**: Filter out legacy diagnostics with `--snapshot <baseline.json>` to gate only on newly introduced regressions.
|
|
@@ -151,13 +151,16 @@ npx mcp-medic watch path/to/config.json
|
|
|
151
151
|
| `--verbose`, `-v` | Output raw JSON-RPC traffic and debug messages |
|
|
152
152
|
| `--json` | Output full diagnostic report in JSON |
|
|
153
153
|
| `--timeout <ms>` | Per-server handshake timeout in milliseconds (default: `5000`) |
|
|
154
|
+
| `--jobs <n>` | Maximum configs processed concurrently by `check-all` (positive integer; default: `1`) |
|
|
154
155
|
| `--help`, `-h` | Show usage help |
|
|
155
156
|
| `--version`, `-V` | Print the installed version |
|
|
156
157
|
|
|
158
|
+
`check-all` interprets globs relative to the current working directory: `*` matches within one path segment, while `**` can cross directories. Results are sorted by path. It skips `node_modules`, `.git`, and `dist`, does not follow symlinked directories, and processes one config at a time unless `--jobs` is set. A zero-match pattern, invalid config, or failed server connection returns exit code `1`; with `--fail-on error`, warnings alone remain non-blocking. In `--json` mode the no-match guidance is written to stderr so stdout stays valid JSON. Like `check`, fleet validation launches the commands and makes the network requests declared by the configs, so only point it at configs you trust.
|
|
159
|
+
|
|
157
160
|
### Exit Codes
|
|
158
161
|
|
|
159
162
|
- **`0`**: All checks passed cleanly.
|
|
160
|
-
- **`1`**: Diagnostic
|
|
163
|
+
- **`1`**: Diagnostic, config, or connection failure; warnings also fail if `--fail-on warning` is set. `check-all` also returns `1` when no files match.
|
|
161
164
|
- **`2`**: Configuration or usage error (missing file, JSON parse error, invalid options).
|
|
162
165
|
|
|
163
166
|
---
|
|
@@ -303,7 +306,6 @@ The `security.*` checks are heuristic โ they pattern-match on what a server *d
|
|
|
303
306
|
- **Handshake timeout defaults to 5000ms** per server (`--timeout <ms>` to change it). A slow-starting stdio server or a server behind a slow network path can fail with `status: 'timeout'` even though it would eventually respond.
|
|
304
307
|
- **`schema.sample-call-simulation`** builds synthetic payloads from a tool's declared JSON Schema and checks the schema is internally consistent (e.g. catches an empty `enum`, or conflicting `minimum`/`maximum`) โ it does **not** actually invoke the tool, and it does not validate business logic, side effects, or whether the tool's real output matches its declared schema.
|
|
305
308
|
- **`security.*` checks are heuristic pattern-matching**, not a security audit โ see the note above. They can both miss real issues and flag benign configs (e.g. a legitimate local dev server on plain `http://`).
|
|
306
|
-
- **Fleet commands (`check-all`, `diff`) are newer and less battle-tested** than `check`/`watch` โ the core check pipeline they're built on is the same, but edge cases in glob matching or drift diffing are more likely.
|
|
307
309
|
- **The VS Code extension and community check packages are not shipped/published** โ see the sections above.
|
|
308
310
|
- **The Reliability quality dimension is currently binary**: 100 if the server connected, 0 if it didn't (plus any future `reliability.*` diagnostics โ none exist yet). Signals like latency trends, retry behavior, or flakiness across repeated runs aren't scored yet.
|
|
309
311
|
- **`resources/list`/`prompts/list` pagination (`nextCursor`) is not followed** โ mcp-medic inspects only the first page a server returns, matching the existing (also unpaginated) `tools/list` handling. A server with a very large resource/prompt catalog behind pagination will be under-inspected.
|
package/dist/cli.js
CHANGED
|
@@ -581,6 +581,10 @@ export async function main(argv = process.argv.slice(2), deps = {}) {
|
|
|
581
581
|
verbose: args.verbose,
|
|
582
582
|
protocolVersion: args.protocolVersion,
|
|
583
583
|
});
|
|
584
|
+
const noMatches = fleetReport.totalFiles === 0;
|
|
585
|
+
if (noMatches) {
|
|
586
|
+
console.error(pc.red(`No configuration files matched "${glob}". Check the glob pattern and run from the intended project root.`));
|
|
587
|
+
}
|
|
584
588
|
if (args.exportJunit) {
|
|
585
589
|
try {
|
|
586
590
|
writeFileSync(resolve(args.exportJunit), formatFleetReportJUnit(fleetReport));
|
|
@@ -602,13 +606,17 @@ export async function main(argv = process.argv.slice(2), deps = {}) {
|
|
|
602
606
|
console.log(pc.red(`[FAIL] ${res.filePath} โ ${res.error}`));
|
|
603
607
|
}
|
|
604
608
|
else if (res.report) {
|
|
605
|
-
const status = res.report.summary.errors === 0
|
|
606
|
-
|
|
609
|
+
const status = res.report.summary.errors === 0 && res.report.summary.failed === 0
|
|
610
|
+
? pc.green('[PASS]')
|
|
611
|
+
: pc.red('[FAIL]');
|
|
612
|
+
console.log(`${status} ${res.filePath} (${res.report.summary.servers} servers, ${res.report.summary.failed} connection failure(s), ${res.report.summary.errors} errors, ${res.report.summary.warnings} warnings)`);
|
|
607
613
|
}
|
|
608
614
|
}
|
|
609
615
|
}
|
|
610
616
|
const hasErrors = fleetReport.totalErrors > 0;
|
|
611
617
|
const hasWarnings = fleetReport.totalWarnings > 0;
|
|
618
|
+
if (noMatches)
|
|
619
|
+
return 1;
|
|
612
620
|
if (args.failOn === 'warning') {
|
|
613
621
|
return hasErrors || hasWarnings ? 1 : 0;
|
|
614
622
|
}
|
package/dist/fleet.js
CHANGED
|
@@ -171,7 +171,10 @@ export async function runFleetChecks(globPattern, options = {}) {
|
|
|
171
171
|
}
|
|
172
172
|
const checks = options.checks ?? allChecks;
|
|
173
173
|
const report = await runChecks(config, { ...options, checks });
|
|
174
|
-
return {
|
|
174
|
+
return {
|
|
175
|
+
fileResult: { filePath, report },
|
|
176
|
+
errorCount: report.summary.errors + report.summary.failed,
|
|
177
|
+
};
|
|
175
178
|
}
|
|
176
179
|
catch (err) {
|
|
177
180
|
return {
|
package/dist/junit.js
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
function escapeXml(unsafe) {
|
|
2
|
-
|
|
2
|
+
let validXml = '';
|
|
3
|
+
for (const character of unsafe) {
|
|
4
|
+
const codePoint = character.codePointAt(0);
|
|
5
|
+
const valid = codePoint === 0x9 ||
|
|
6
|
+
codePoint === 0xa ||
|
|
7
|
+
codePoint === 0xd ||
|
|
8
|
+
(codePoint >= 0x20 && codePoint <= 0xd7ff) ||
|
|
9
|
+
(codePoint >= 0xe000 && codePoint <= 0xfffd) ||
|
|
10
|
+
(codePoint >= 0x10000 && codePoint <= 0x10ffff);
|
|
11
|
+
if (valid) {
|
|
12
|
+
validXml += character;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return validXml
|
|
3
16
|
.replace(/&/g, '&')
|
|
4
17
|
.replace(/</g, '<')
|
|
5
18
|
.replace(/>/g, '>')
|
|
@@ -50,12 +63,28 @@ export function formatReportJUnit(report) {
|
|
|
50
63
|
*/
|
|
51
64
|
export function formatFleetReportJUnit(fleetReport) {
|
|
52
65
|
const lines = [];
|
|
66
|
+
let totalTestCases = 0;
|
|
67
|
+
let totalFailures = 0;
|
|
68
|
+
for (const fileResult of fleetReport.fileResults) {
|
|
69
|
+
if (fileResult.report) {
|
|
70
|
+
totalTestCases += fileResult.report.connections.length + fileResult.report.diagnostics.length;
|
|
71
|
+
totalFailures += fileResult.report.connections.filter((conn) => conn.status !== 'connected').length;
|
|
72
|
+
totalFailures += fileResult.report.diagnostics.filter((diagnostic) => diagnostic.severity === 'error').length;
|
|
73
|
+
}
|
|
74
|
+
else if (fileResult.error) {
|
|
75
|
+
totalTestCases += 1;
|
|
76
|
+
totalFailures += 1;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
53
79
|
lines.push('<?xml version="1.0" encoding="UTF-8"?>');
|
|
54
|
-
lines.push(`<testsuites name="mcp-medic-fleet" tests="${
|
|
80
|
+
lines.push(`<testsuites name="mcp-medic-fleet" tests="${totalTestCases}" failures="${totalFailures}">`);
|
|
55
81
|
for (const fileResult of fleetReport.fileResults) {
|
|
56
82
|
if (fileResult.report) {
|
|
57
83
|
const rep = fileResult.report;
|
|
58
|
-
|
|
84
|
+
const testCases = rep.connections.length + rep.diagnostics.length;
|
|
85
|
+
const failures = rep.connections.filter((conn) => conn.status !== 'connected').length +
|
|
86
|
+
rep.diagnostics.filter((diagnostic) => diagnostic.severity === 'error').length;
|
|
87
|
+
lines.push(` <testsuite name="${escapeXml(fileResult.filePath)}" tests="${testCases}" failures="${failures}">`);
|
|
59
88
|
for (const conn of rep.connections) {
|
|
60
89
|
if (conn.status === 'connected') {
|
|
61
90
|
lines.push(` <testcase classname="${escapeXml(conn.server.name)}" name="connection.handshake" />`);
|
|
@@ -73,6 +102,9 @@ export function formatFleetReportJUnit(fleetReport) {
|
|
|
73
102
|
lines.push(` <failure message="${escapeXml(d.message)}" type="CheckError">${escapeXml(d.message)}</failure>`);
|
|
74
103
|
lines.push(' </testcase>');
|
|
75
104
|
}
|
|
105
|
+
else {
|
|
106
|
+
lines.push(` <testcase classname="${escapeXml(scope)}" name="${escapeXml(d.checkId)}"><system-out>[${d.severity}] ${escapeXml(d.message)}</system-out></testcase>`);
|
|
107
|
+
}
|
|
76
108
|
}
|
|
77
109
|
lines.push(' </testsuite>');
|
|
78
110
|
}
|