@vantageos/mcp-doctor 0.3.0 → 0.3.1
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/CHANGELOG.md +25 -0
- package/README.md +72 -7
- package/dist/doctor.js +8 -3
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,31 @@
|
|
|
3
3
|
All notable changes to `@vantageos/mcp-doctor` are documented here.
|
|
4
4
|
This project adheres to [Semantic Versioning](https://semver.org/).
|
|
5
5
|
|
|
6
|
+
## 0.3.1 — 2026-10-04
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
- **`npm test` on a clean clone failed 3 of 341.** `tests/bin-entrypoint.test.ts`
|
|
10
|
+
symlinks to `dist/doctor.js` and invokes it — deliberately, because the symlink is
|
|
11
|
+
the trigger for the defect those cases cover — but it never ensured the build
|
|
12
|
+
existed. A contributor cloning the repo and running the suite got
|
|
13
|
+
`Cannot find module .../dist/doctor.js` three times, on `main`, with nothing saying
|
|
14
|
+
a build was required first.
|
|
15
|
+
Fixed with `pretest: npm run build`, following the convention `prepack` and
|
|
16
|
+
`prepublishOnly` already use in this manifest rather than inventing a mechanism.
|
|
17
|
+
- **D9 no longer throws when no `package.json` sits above the scanned root.** It
|
|
18
|
+
returns `could-not-judge` in band, the run completes the remaining detectors, and
|
|
19
|
+
the table prints. Previously the throw reached the consumer as exit 1 with a stack
|
|
20
|
+
trace — exit 1 being this tool's code for *a violation*, so a crash was
|
|
21
|
+
indistinguishable from a finding.
|
|
22
|
+
|
|
23
|
+
### Note on why 0.3.0 shipped with the test defect
|
|
24
|
+
Every environment where the work happens already has `dist/` — so the test passed for
|
|
25
|
+
everyone who wrote it, and the one gated path (`prepublishOnly`, which runs
|
|
26
|
+
`build && test`) was the one path that could not expose it. A test depending on an
|
|
27
|
+
unstated prior step cannot fail for its author.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
6
31
|
## 0.3.0 — 2026-10-04
|
|
7
32
|
|
|
8
33
|
The documented invocation never ran. Through `node_modules/.bin/mcp-doctor` this
|
package/README.md
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
# @vantageos/mcp-doctor
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
**conformance-diff table** against the MCP **2026-07-28** standard — one row per
|
|
3
|
+
**MCP conformance checker**. Point it at an MCP server source tree and it emits a
|
|
4
|
+
**conformance-diff table** against the MCP **2026-07-28** standard — one row per detector:
|
|
5
5
|
|
|
6
6
|
```
|
|
7
|
-
item | file:line |
|
|
7
|
+
item | file:line | state | verdict
|
|
8
8
|
```
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
where `state` is one of `present`, `absent`, `non-conforming` or `could-not-judge` (see
|
|
11
|
+
[States and exit codes](#states-and-exit-codes)).
|
|
12
|
+
|
|
13
|
+
No narrative, no LLM. By default it is a deterministic **static** scan you can wire into CI; with
|
|
14
|
+
`--mode=dynamic` it also launches the server and judges what it actually serves. It reproduces,
|
|
11
15
|
unaided, the manual conformance audits the fleet ran by hand on its own MCP servers.
|
|
12
16
|
|
|
13
17
|
## Usage
|
|
@@ -37,6 +41,56 @@ D7 protocol-kit | package.json | absent | hand-rolled bu
|
|
|
37
41
|
The path is the root of the MCP server sources (the folder holding `server.ts` / `transport/` /
|
|
38
42
|
`package.json`). `node_modules`, `dist`, `build`, `.git` and test folders are skipped automatically.
|
|
39
43
|
|
|
44
|
+
### Options
|
|
45
|
+
|
|
46
|
+
The binary's own `--help` is the authority; this is what it prints:
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
usage: mcp-doctor <source-tree-path> [--mode=static|dynamic|both]
|
|
50
|
+
|
|
51
|
+
--mode=static read the tree only (default)
|
|
52
|
+
--mode=dynamic launch the server and judge what it serves
|
|
53
|
+
--mode=both both, dynamic findings taking precedence
|
|
54
|
+
--version print the version and exit
|
|
55
|
+
--help print this and exit
|
|
56
|
+
|
|
57
|
+
exit: 0 clean, 1 a violation, 2 an abstention with no violation
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## States and exit codes
|
|
61
|
+
|
|
62
|
+
| state | meaning | effect on exit |
|
|
63
|
+
|---|---|---|
|
|
64
|
+
| `present` | the standard item is satisfied | none |
|
|
65
|
+
| `absent` | the detector found nothing to judge — see the caveat below | none |
|
|
66
|
+
| `non-conforming` | the item is present and wrong | **exit 1** |
|
|
67
|
+
| `could-not-judge` | the detector could not read its input and says so in the row — in 0.3.1, only D9 does this (no `package.json` above the root); see the limits below | **exit 2**, unless something else is non-conforming |
|
|
68
|
+
|
|
69
|
+
The exit code keeps three outcomes distinct, which is what makes the tool safe in CI:
|
|
70
|
+
|
|
71
|
+
- **0** — clean.
|
|
72
|
+
- **1** — at least one violation.
|
|
73
|
+
- **2** — no violation, but at least one detector abstained. **An abstention is never a pass**: it
|
|
74
|
+
means part of the tree was not judged, and the row names which part and why.
|
|
75
|
+
|
|
76
|
+
**Two known limits in 0.3.1, stated rather than hidden:**
|
|
77
|
+
|
|
78
|
+
- **`absent` currently means two things.** It is used both for "this rule does not apply here" and,
|
|
79
|
+
for some detectors, "this is required and missing". The second should move the exit code and
|
|
80
|
+
today does not. Until that is split, read an `absent` row against the rule it names rather than
|
|
81
|
+
treating it as harmless.
|
|
82
|
+
- **Unreadable input is handled in band by D9 only.** In 0.3.1, D9 reports `could-not-judge` when no
|
|
83
|
+
`package.json` sits above the scanned root, and the run completes. Several other detectors still
|
|
84
|
+
**throw** on unreadable input — a malformed manifest, a computed value — which aborts the run with
|
|
85
|
+
exit 1 and a stack trace, indistinguishable from a violation. Converting them is in progress.
|
|
86
|
+
|
|
87
|
+
By design, a detector whose own internal self-check fails stops the run — that is the instrument
|
|
88
|
+
reporting that it is broken, and silencing it would hide the fault behind a normal-looking table.
|
|
89
|
+
|
|
90
|
+
**Upgrading from 0.2.0:** through `npx` or `node_modules/.bin`, 0.2.0 printed nothing and exited 0
|
|
91
|
+
for every target, because its entrypoint guard never fired under a symlink. A CI job that has been
|
|
92
|
+
green on 0.2.0 was green because nothing ran. Expect findings on the first run of 0.3.x.
|
|
93
|
+
|
|
40
94
|
## Programmatic use
|
|
41
95
|
|
|
42
96
|
```ts
|
|
@@ -46,7 +100,7 @@ const results = runDoctor("./mcp-server");
|
|
|
46
100
|
console.log(renderTable("./mcp-server", results));
|
|
47
101
|
```
|
|
48
102
|
|
|
49
|
-
## What it checks
|
|
103
|
+
## What it checks (MCP 2026-07-28)
|
|
50
104
|
|
|
51
105
|
Each detector maps one standard item and is proven **bipolar** on real material: a genuine defect
|
|
52
106
|
(MUST_FLAG) and a genuine conformant site (MUST_PASS).
|
|
@@ -62,7 +116,11 @@ Each detector maps one standard item and is proven **bipolar** on real material:
|
|
|
62
116
|
| **D15** cache-coherence | Server Rule 15 — every tool-set mutation (RegisteredTool `.enable/.disable/.update/.remove`, `unregisterTool`) is correlated with a `notifications/tools/list_changed` emission | present / absent / non-conforming / could-not-judge |
|
|
63
117
|
| **D16** version-observable | Server Rule 16 — `server/discover` (or an HTTP `/health` route) exposes a `version` field sourced from `package.json`, reachable pre-auth | present / absent / non-conforming / could-not-judge |
|
|
64
118
|
|
|
65
|
-
|
|
119
|
+
**This table is a sample, not the inventory.** Every registered detector runs on every invocation,
|
|
120
|
+
so the authoritative list is simply the output of a run — one row per detector. The registered set
|
|
121
|
+
lives in `src/detectors/index.ts`. No count is written here on purpose: a number typed into a README
|
|
122
|
+
is correct on the day it is written and wrong at the next detector, which is exactly how this
|
|
123
|
+
heading came to claim 6 while the tool shipped dozens. **Adding a detector is how a friction becomes durable — see [CONTRIBUTING.md](./CONTRIBUTING.md) for the loop.**
|
|
66
124
|
|
|
67
125
|
## Positioning
|
|
68
126
|
|
|
@@ -71,10 +129,17 @@ standard by naming the exact `file:line` to fix — and a **vendable product** f
|
|
|
71
129
|
MCP servers who needs a reproducible conformance gate. It is deterministic: the same tree yields the
|
|
72
130
|
same diff on any machine, so it belongs in CI, not in a review checklist.
|
|
73
131
|
|
|
132
|
+
## Contributing
|
|
133
|
+
|
|
134
|
+
`npm test` builds first (a `pretest` script runs `npm run build`), because several tests drive the
|
|
135
|
+
real published entry point through a symlinked bin and need `dist/` to exist. Earlier versions
|
|
136
|
+
assumed a prior build, which passed for anyone who had already built and failed for anyone who had
|
|
137
|
+
just cloned.
|
|
138
|
+
|
|
74
139
|
## License
|
|
75
140
|
|
|
76
141
|
FSL-1.1-Apache-2.0 (Functional Source License, Apache 2.0 future license). See [LICENSE](./LICENSE).
|
|
77
142
|
|
|
78
143
|
---
|
|
79
144
|
|
|
80
|
-
Orchestrator: Gamma — VantageOS Team | 2026-
|
|
145
|
+
Orchestrator: Gamma — VantageOS Team | 2026-10-04
|
package/dist/doctor.js
CHANGED
|
@@ -3143,9 +3143,14 @@ var d9 = {
|
|
|
3143
3143
|
);
|
|
3144
3144
|
}
|
|
3145
3145
|
if (!tree.packageJson) {
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3146
|
+
const reason = "no package.json found anywhere above the scanned root \u2014 cannot verify SemVer/CHANGELOG";
|
|
3147
|
+
return {
|
|
3148
|
+
id: this.id,
|
|
3149
|
+
anchor: "package.json",
|
|
3150
|
+
state: "could-not-judge",
|
|
3151
|
+
reason,
|
|
3152
|
+
verdict: `could not judge: ${reason}`
|
|
3153
|
+
};
|
|
3149
3154
|
}
|
|
3150
3155
|
const { path: pkgPath, json: pkgJson } = tree.packageJson;
|
|
3151
3156
|
if (!pkgJson || Object.keys(pkgJson).length === 0) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vantageos/mcp-doctor",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Static MCP conformance checker: audits an MCP server source tree against the MCP 2026-07-28 standard and emits a conformance-diff table (file:line | present | absent | non-conforming | verdict).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/doctor.js",
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"scripts": {
|
|
40
40
|
"build": "tsup",
|
|
41
41
|
"doctor": "tsx src/doctor.ts",
|
|
42
|
+
"pretest": "npm run build",
|
|
42
43
|
"test": "vitest run",
|
|
43
44
|
"test:watch": "vitest",
|
|
44
45
|
"test:d10-foreign-bite": "bash scripts/d10-foreign-bite-probe.sh",
|