@vantageos/mcp-doctor 0.3.0 → 0.3.2

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 CHANGED
@@ -3,6 +3,62 @@
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.2 — 2026-10-04
7
+
8
+ ### Changed — read this if your CI distinguishes exit codes
9
+ - **Unreadable input is now `could-not-judge` (exit 2), no longer a crash (exit 1).**
10
+ A tree holding input a detector could not read — malformed JSON in an extension
11
+ manifest or `evals.json`, a computed or non-literal value where a literal is
12
+ required — used to abort the run with a stack trace and exit 1, the code this
13
+ tool uses for *a violation*. It now completes the run, names the unreadable
14
+ file in the row, and exits 2 (no violation, but part of the tree was not judged).
15
+ Pipelines that treat any non-zero exit as failure are unaffected. Pipelines that
16
+ distinguish 1 from 2 must read the new state: a run that used to exit 1 on
17
+ unreadable input now exits 2. (#46)
18
+
19
+ ### Fixed
20
+ - **A manifest that parses but is wrong is `non-conforming`.** It is a finding
21
+ (exit 1) rather than an abstention or a crash. (#46)
22
+ - **An unparseable `package.json` is `could-not-judge` for C17, D10 and D7** (and C14, D12), where each had judged an empty manifest and reported the emptiness as a finding (C17, D10 non-conforming) or an absence (D7), so the run exited 1 instead of 2. The parse failure is now recorded once at load time and every detector reading that file honours it.
23
+ - **Tools declared as descriptor objects are now found.** Five detectors (D3, D4, D8, D11, DServer2) and C19 reported "no registered tools" on servers that declare their tools as descriptor objects (`{ name, inputSchema }`). They now find those tools. A tree that serves tools but where none can be located reads `could-not-judge`, not `absent`. On a real vCRM tree, the "no registered tools found" rows went from 1 to 0, and D11 is now a real finding. (#48)
24
+ - **`package.json` is now what npm publishes.** `npm publish` of 0.3.1 printed
25
+ "npm auto-corrected some errors in your package.json": `bin` was `"./dist/doctor.js"`
26
+ and is now `"dist/doctor.js"`, and `files` is reformatted, both exactly as
27
+ `npm pkg fix` writes them. A new test, `tests/publish-manifest-clean.test.ts`,
28
+ runs `npm publish --dry-run` and fails if that warning returns, and asserts the
29
+ tarball lists `dist/doctor.js`, `README.md` and `LICENSE`.
30
+
31
+ ### Docs
32
+ - README: the states table and the known limits no longer say unreadable input is
33
+ handled in band by D9 only.
34
+
35
+ ---
36
+
37
+ ## 0.3.1 — 2026-10-04
38
+
39
+ ### Fixed
40
+ - **`npm test` on a clean clone failed 3 of 341.** `tests/bin-entrypoint.test.ts`
41
+ symlinks to `dist/doctor.js` and invokes it — deliberately, because the symlink is
42
+ the trigger for the defect those cases cover — but it never ensured the build
43
+ existed. A contributor cloning the repo and running the suite got
44
+ `Cannot find module .../dist/doctor.js` three times, on `main`, with nothing saying
45
+ a build was required first.
46
+ Fixed with `pretest: npm run build`, following the convention `prepack` and
47
+ `prepublishOnly` already use in this manifest rather than inventing a mechanism.
48
+ - **D9 no longer throws when no `package.json` sits above the scanned root.** It
49
+ returns `could-not-judge` in band, the run completes the remaining detectors, and
50
+ the table prints. Previously the throw reached the consumer as exit 1 with a stack
51
+ trace — exit 1 being this tool's code for *a violation*, so a crash was
52
+ indistinguishable from a finding.
53
+
54
+ ### Note on why 0.3.0 shipped with the test defect
55
+ Every environment where the work happens already has `dist/` — so the test passed for
56
+ everyone who wrote it, and the one gated path (`prepublishOnly`, which runs
57
+ `build && test`) was the one path that could not expose it. A test depending on an
58
+ unstated prior step cannot fail for its author.
59
+
60
+ ---
61
+
6
62
  ## 0.3.0 — 2026-10-04
7
63
 
8
64
  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
- Static **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 checked item:
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 | present | absent | non-conforming | verdict
7
+ item | file:line | state | verdict
8
8
  ```
9
9
 
10
- No narrative, no LLM, no network: a deterministic static scan you can wire into CI. It reproduces,
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,52 @@ 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 (this includes a manifest that parses but holds a wrong value) | **exit 1** |
67
+ | `could-not-judge` | the detector could not read its input and says so in the row — malformed JSON, a computed or non-literal value, no `package.json` above the root; the run completes and the row names the file | **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
+ **One known limit in 0.3.2, 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
+
83
+ By design, a detector whose own internal self-check fails stops the run — that is the instrument
84
+ reporting that it is broken, and silencing it would hide the fault behind a normal-looking table.
85
+
86
+ **Upgrading from 0.2.0:** through `npx` or `node_modules/.bin`, 0.2.0 printed nothing and exited 0
87
+ for every target, because its entrypoint guard never fired under a symlink. A CI job that has been
88
+ green on 0.2.0 was green because nothing ran. Expect findings on the first run of 0.3.x.
89
+
40
90
  ## Programmatic use
41
91
 
42
92
  ```ts
@@ -46,7 +96,7 @@ const results = runDoctor("./mcp-server");
46
96
  console.log(renderTable("./mcp-server", results));
47
97
  ```
48
98
 
49
- ## What it checks — the 6 Tier-A detectors (MCP 2026-07-28)
99
+ ## What it checks (MCP 2026-07-28)
50
100
 
51
101
  Each detector maps one standard item and is proven **bipolar** on real material: a genuine defect
52
102
  (MUST_FLAG) and a genuine conformant site (MUST_PASS).
@@ -62,7 +112,11 @@ Each detector maps one standard item and is proven **bipolar** on real material:
62
112
  | **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
113
  | **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
114
 
65
- The registered detector set is the source of truth (`src/detectors/index.ts`); the table above is illustrative, not the count. **Adding a detector is how a friction becomes durable — see [CONTRIBUTING.md](./CONTRIBUTING.md) for the loop.**
115
+ **This table is a sample, not the inventory.** Every registered detector runs on every invocation,
116
+ so the authoritative list is simply the output of a run — one row per detector. The registered set
117
+ lives in `src/detectors/index.ts`. No count is written here on purpose: a number typed into a README
118
+ is correct on the day it is written and wrong at the next detector, which is exactly how this
119
+ 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
120
 
67
121
  ## Positioning
68
122
 
@@ -71,10 +125,17 @@ standard by naming the exact `file:line` to fix — and a **vendable product** f
71
125
  MCP servers who needs a reproducible conformance gate. It is deterministic: the same tree yields the
72
126
  same diff on any machine, so it belongs in CI, not in a review checklist.
73
127
 
128
+ ## Contributing
129
+
130
+ `npm test` builds first (a `pretest` script runs `npm run build`), because several tests drive the
131
+ real published entry point through a symlinked bin and need `dist/` to exist. Earlier versions
132
+ assumed a prior build, which passed for anyone who had already built and failed for anyone who had
133
+ just cloned.
134
+
74
135
  ## License
75
136
 
76
137
  FSL-1.1-Apache-2.0 (Functional Source License, Apache 2.0 future license). See [LICENSE](./LICENSE).
77
138
 
78
139
  ---
79
140
 
80
- Orchestrator: Gamma — VantageOS Team | 2026-08-03
141
+ Orchestrator: Gamma — VantageOS Team | 2026-10-04
package/dist/doctor.d.ts CHANGED
@@ -13,9 +13,15 @@ interface SourceFile {
13
13
  interface Tree {
14
14
  root: string;
15
15
  files: SourceFile[];
16
+ /**
17
+ * `unreadable` is set (with the parse error) when the file exists but is not a
18
+ * JSON object; `json` is then `{}` and MUST NOT be read as the file's content.
19
+ * Detectors that read this file go through `guardUnreadablePackageJson`.
20
+ */
16
21
  packageJson: {
17
22
  path: string;
18
23
  json: Record<string, unknown>;
24
+ unreadable?: string;
19
25
  } | null;
20
26
  }
21
27