mcp-stdio-guard 0.2.0 → 0.4.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.
Files changed (3) hide show
  1. package/README.md +243 -8
  2. package/package.json +1 -1
  3. package/src/index.js +2265 -90
package/README.md CHANGED
@@ -11,6 +11,7 @@
11
11
  <p align="center">
12
12
  <a href="https://github.com/1Utkarsh1/mcp-stdio-guard/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/1Utkarsh1/mcp-stdio-guard/actions/workflows/ci.yml/badge.svg" /></a>
13
13
  <a href="https://www.npmjs.com/package/mcp-stdio-guard"><img alt="npm" src="https://img.shields.io/npm/v/mcp-stdio-guard?color=0b6bcb" /></a>
14
+ <a href="https://badge.socket.dev/npm/package/mcp-stdio-guard/0.4.0"><img alt="Socket" src="https://badge.socket.dev/npm/package/mcp-stdio-guard/0.4.0" /></a>
14
15
  <img alt="runtime dependencies" src="https://img.shields.io/badge/runtime%20deps-0-1f8f4c" />
15
16
  <img alt="node" src="https://img.shields.io/badge/node-%3E%3D18-2f855a" />
16
17
  <a href="LICENSE"><img alt="license" src="https://img.shields.io/badge/license-MIT-111827" /></a>
@@ -22,7 +23,7 @@
22
23
 
23
24
  MCP stdio servers use stdout as their protocol channel. Debug text, banners, progress logs, `console.log`, Python `print`, or any other stray stdout output can corrupt the stream and make clients fail in confusing ways.
24
25
 
25
- `mcp-stdio-guard` starts your server, performs a real MCP initialize handshake, optionally sends a real post-initialize MCP request such as `tools/list`, validates every stdout frame, and scans source for risky stdout calls.
26
+ `mcp-stdio-guard` starts your server, performs a real MCP initialize handshake, probes advertised `tools`, `resources`, and `prompts` list capabilities, optionally sends a real post-initialize MCP request such as `tools/list`, validates every stdout frame, checks returned tool metadata, and scans source for risky stdout calls.
26
27
 
27
28
  ## Why This Exists
28
29
 
@@ -59,13 +60,25 @@ Run your MCP server behind the guard:
59
60
  mcp-stdio-guard -- node ./server.js
60
61
  ```
61
62
 
63
+ Use a deterministic profile for common workflows:
64
+
65
+ ```bash
66
+ mcp-stdio-guard --profile registry --json -- node ./server.js
67
+ ```
68
+
69
+ Use a config file for registry runs that need environment names, request lists, or explicitly safe tool calls:
70
+
71
+ ```bash
72
+ mcp-stdio-guard --config mcp-stdio-guard.config.json
73
+ ```
74
+
62
75
  Exercise a real MCP operation after initialization:
63
76
 
64
77
  ```bash
65
78
  mcp-stdio-guard --request tools/list -- node ./server.js
66
79
  ```
67
80
 
68
- Scan source for obvious stdout writes too:
81
+ Scan source for obvious stdout writes too. Findings are warnings unless `--fail-on-static` is set:
69
82
 
70
83
  ```bash
71
84
  mcp-stdio-guard --scan src --fail-on-static --request tools/list -- node ./server.js
@@ -98,6 +111,8 @@ mcp-stdio-guard --repeat 2 --request tools/list -- node ./server.js
98
111
  | Invalid JSON-RPC frames | Yes | No |
99
112
  | Server crash after `notifications/initialized` | Yes | No |
100
113
  | Missing `initialize` or operation response | Yes | No |
114
+ | Duplicate tool names or invalid `inputSchema.required` | Yes with `--request tools/list` | No |
115
+ | Cold/warm protocol, capability, or tool-list drift | Warning with `--repeat` | No |
101
116
  | stderr diagnostics | Allowed | Allowed |
102
117
 
103
118
  ## Live MCP Coverage
@@ -121,17 +136,74 @@ mcp-stdio-guard [options] -- <command> [args...]
121
136
 
122
137
  | Option | Description |
123
138
  | --- | --- |
139
+ | `--config <path>` | read a JSON config file for registry runs and explicitly safe tool calls |
140
+ | `--profile <name>` | apply a deterministic guard profile: `custom`, `smoke`, `registry`, `ci`, or `strict` |
124
141
  | `--protocol <version>` | MCP protocol version to send, default `2025-11-25` |
125
142
  | `--timeout <ms>` | initialize and request timeout, default `5000` |
126
143
  | `--repeat <count>` | run the same guard multiple times to catch cold/warm startup behavior |
127
144
  | `--request <method>` | send one MCP request after initialization, for example `tools/list` |
128
145
  | `--params <json>` | JSON params for `--request` |
129
- | `--scan <path>` | scan source for risky stdout writes |
146
+ | `--scan <path>` | scan source for risky stdout writes and visible startup-output risks |
130
147
  | `--fail-on-static` | make static scan findings fail the command |
131
148
  | `--json` | print machine-readable output |
132
149
  | `--cwd <path>` | run the server command from a specific directory |
133
150
  | `--help` | show help |
134
151
 
152
+ ## Profiles
153
+
154
+ Profiles are deterministic presets for common workflows. Existing CLI behavior remains the default `custom` profile, so current commands keep working unless `--profile` is provided.
155
+
156
+ | Profile | Behavior |
157
+ | --- | --- |
158
+ | `custom` | preserve explicit CLI flags and legacy defaults |
159
+ | `smoke` | initialize only unless `--request` is provided; skip advertised `tools/list`, `resources/list`, and `prompts/list` probes |
160
+ | `registry` | run advertised list probes and repeat twice by default for cold/warm consistency |
161
+ | `ci` | emit JSON output and make static scan findings fail when `--scan` is used |
162
+ | `strict` | combine CI-style output/static failures with registry-style repeat depth; future adversarial probes will attach here |
163
+
164
+ Explicit flags can still narrow or deepen a profile. For example, `--profile registry --repeat 1` keeps registry capability probing but disables the repeat preset.
165
+
166
+ ## Config Files
167
+
168
+ Config files let registries run repeatable checks without hiding what was executed. The file is JSON, and CLI flags still override matching config defaults. Parsing happens before the server process starts, so invalid config does not launch the target command.
169
+
170
+ Supported fields:
171
+
172
+ | Field | Meaning |
173
+ | --- | --- |
174
+ | `command` | command as either `["node", "./server.js"]` or `"node"` with `args` |
175
+ | `args` | arguments used only when `command` is a string |
176
+ | `cwd` | working directory, resolved relative to the config file |
177
+ | `env` | environment variables to pass; values are redacted in JSON output |
178
+ | `profile`, `protocol`, `timeoutMs`, `repeat`, `json` | same meaning as CLI options |
179
+ | `scan` or `scanPath` | source scan path, resolved relative to the config file |
180
+ | `failOnStatic` | make static scan findings fail |
181
+ | `request` | one explicit post-initialize request: `{ "method": "tools/list" }` |
182
+ | `requests` | list of explicit post-initialize requests |
183
+ | `safeToolCalls` | opt-in `tools/call` recipes; no tool is called unless listed here or explicitly requested |
184
+
185
+ Example:
186
+
187
+ ```json
188
+ {
189
+ "profile": "registry",
190
+ "command": ["node", "./server.js"],
191
+ "cwd": ".",
192
+ "json": true,
193
+ "env": {
194
+ "API_TOKEN": "set-in-runner"
195
+ },
196
+ "requests": [
197
+ { "method": "tools/list" }
198
+ ],
199
+ "safeToolCalls": [
200
+ { "name": "echo", "arguments": { "text": "hello" } }
201
+ ]
202
+ }
203
+ ```
204
+
205
+ The guard does not discover and call arbitrary tools from `tools/list`. Tool execution only happens through an explicit `safeToolCalls` entry or an explicit `tools/call` request you provide.
206
+
135
207
  ## JSON Contract
136
208
 
137
209
  `--json` is intended for CI, registries, and badge ingestion. The current contract is `schemaVersion: 1`; new fields may be added, but these fields are stable for consumers:
@@ -140,18 +212,91 @@ mcp-stdio-guard [options] -- <command> [args...]
140
212
  | --- | --- |
141
213
  | `schemaVersion` | JSON contract version, currently `1` |
142
214
  | `ok` | `true` when no error-severity issue was found |
215
+ | `config` | config file metadata and checks used, or `{ "enabled": false, ... }` |
216
+ | `profile` | selected guard profile, for example `custom`, `smoke`, `registry`, `ci`, or `strict` |
143
217
  | `command` | command and arguments that were validated |
144
218
  | `protocol` | MCP protocol version sent by the guard |
145
219
  | `negotiatedProtocol` | protocol version returned by the server, when available |
146
220
  | `initialized` | whether the server completed the initialize handshake |
147
221
  | `operation` | post-initialize request result, or `null` when `--request` was not used |
222
+ | `operations` | all explicit post-initialize requests, including config requests and safe tool calls |
223
+ | `toolSchema` | summary of `tools/list` metadata validation when that operation was requested or probed from an advertised tools capability |
224
+ | `capabilityProbes` | whether advertised capability list probes were enabled for this run |
225
+ | `capabilityKeys` | sorted capability keys returned by `initialize` for a single run; repeat mode exposes this inside each `runs` entry |
226
+ | `capabilityChecks` | advertised capability probes observed during a single run; repeat mode exposes this inside each `runs` entry |
227
+ | `drift` | repeat-run comparison summary for negotiated protocol, advertised capabilities, tool names/counts, and resource/prompt list counts |
228
+ | `process` | startup, timeout, exit code, signal, and guard-termination metadata for a single run; repeat mode exposes this inside each `runs` entry |
148
229
  | `checks` | badge-friendly per-class statuses |
149
- | `issues` | machine-readable diagnostics with `severity`, `code`, and `message`; repeat mode also adds `run` |
230
+ | `issueClasses` | registry-friendly summary grouped by `installRuntime`, `stdioTransport`, and `mcpProtocol` |
231
+ | `fingerprint` | redacted reproducibility metadata for debugging registry and CI runs |
232
+ | `issues` | machine-readable diagnostics with `class`, `severity`, `code`, and `message`; repeat mode also adds `run` |
150
233
  | `staticScan` | whether source scanning was enabled and whether findings fail the command |
151
- | `staticFindings` | source scan findings with file, line, and message |
234
+ | `staticFindings` | source scan findings with language, file, line, reason, and message |
152
235
  | `runs` | per-run results when `--repeat` is used |
153
236
 
154
- Check statuses are `pass`, `fail`, `warning`, or `skipped`. The `checks` object separates the signal into `initialize`, `stdout`, `jsonRpc`, `operation`, `process`, `pythonBuffering`, `staticScan`, and `repeat`, each with stable `status` and `issueCodes` fields. When `--repeat` is used, `checks.repeat` also includes `runs`, `passedRuns`, and `failedRuns`; each entry in `runs` is a normal schema-versioned result for that individual guard run.
237
+ Check statuses are `pass`, `fail`, `warning`, or `skipped`. The `checks` object separates the signal into `initialize`, `stdout`, `jsonRpc`, `operation`, `capabilities`, `toolSchema`, `process`, `pythonBuffering`, `staticScan`, and `repeat`, each with stable `status` and `issueCodes` fields. When `--repeat` is used, `checks.repeat` also includes `runs`, `passedRuns`, and `failedRuns`; each entry in `runs` is a normal schema-versioned result for that individual guard run.
238
+
239
+ `issueClasses` is additive to `checks`. It groups issue codes by the kind of problem a registry or client should display:
240
+
241
+ | Issue class | Meaning | Display guidance |
242
+ | --- | --- | --- |
243
+ | `installRuntime` | the command could not start, timed out, exited, crashed, or hit a runtime advisory | show as "needs inspection" or "runtime/install issue"; do not present it as an MCP protocol violation |
244
+ | `stdioTransport` | stdout was not a clean newline-delimited JSON-RPC channel, or source scan found risky stdout writes | show as stdio hygiene failure; ask maintainers to keep diagnostics on stderr |
245
+ | `mcpProtocol` | the server emitted invalid JSON-RPC/MCP responses, mismatched request ids, or returned initialize/operation errors | show as MCP/JSON-RPC conformance issue |
246
+
247
+ Current issue-code mapping:
248
+
249
+ | Issue class | Issue codes |
250
+ | --- | --- |
251
+ | `installRuntime` | `initialize-timeout`, `operation-missing-response`, `operation-timeout`, `python-buffered-stdio`, `server-crashed`, `server-exited`, `spawn-failed` |
252
+ | `stdioTransport` | `static-stdout-write`, `stdout-content-length-framing`, `stdout-empty-line`, `stdout-non-json`, `stdout-without-newline` |
253
+ | `mcpProtocol` | `capability-list-error`, `capability-list-missing-response`, `capability-list-timeout`, `capability-list-unsupported`, `initialize-error`, `initialize-invalid-capabilities`, `initialize-invalid-protocol-version`, `initialize-invalid-result`, `initialize-invalid-server-info`, `initialize-missing-capabilities`, `initialize-missing-protocol-version`, `initialize-missing-server-info`, `notification-response`, `operation-error`, `repeat-capability-drift`, `repeat-list-shape-drift`, `repeat-protocol-drift`, `repeat-tool-drift`, `response-id-mismatch`, `response-id-type-mismatch`, `stdout-invalid-json-rpc`, `stdout-unexpected-request-id`, `tool-description-missing`, `tool-input-schema-invalid`, `tool-input-schema-required-missing`, `tool-name-duplicate`, `tool-name-invalid`, `tools-list-invalid-result` |
254
+
255
+ Initialize lifecycle checks are part of the MCP protocol class. Missing or invalid `protocolVersion` and `capabilities` fail the run before the guard sends `notifications/initialized` or any normal request. Missing or invalid `serverInfo` is warning-level so registries can surface incomplete metadata without confusing it with a broken transport.
256
+
257
+ JSON-RPC invariant checks distinguish wrong response ids from id type round-trip problems and fail servers that respond to `notifications/initialized`. JSON-RPC error frames must be structured with numeric `code` and string `message` fields.
258
+
259
+ Tool schema checks run when `tools/list` receives a successful result, either from `--request tools/list` or from the advertised tools capability probe. Duplicate or invalid tool names, missing `inputSchema`, invalid schema shapes, and `required` entries that are absent from `properties` are MCP protocol failures. Missing tool descriptions are warning-level so registries can show quality guidance without marking the server broken.
260
+
261
+ Capability honesty checks are additive. If `initialize` advertises `capabilities.tools`, `capabilities.resources`, or `capabilities.prompts`, the guard probes the matching `tools/list`, `resources/list`, or `prompts/list` method after `notifications/initialized`. Unadvertised capabilities are `skipped`, not failed. `capability-list-unsupported` means an advertised list method returned method-not-found; `capability-list-error`, `capability-list-timeout`, and `capability-list-missing-response` mean the advertised list method existed in the contract but failed at runtime.
262
+
263
+ Repeat drift checks compare successful initialized runs against the first initialized run. Negotiated protocol changes, advertised capability key changes, added or removed tool names, tool count changes, and resource/prompt list count changes are warning-level `repeat-*` issues. Tool order is normalized before comparison, so order-only changes do not warn.
264
+
265
+ The repeat `drift` object has stable `status`, `issueCodes`, `baselineRun`, and `comparedRuns` fields. Its nested `negotiatedProtocol`, `capabilities`, `tools`, `lists.resources`, and `lists.prompts` sections include `changedRuns` so registries can show exactly what changed between cold and warm starts.
266
+
267
+ Runtime issue codes remain backward-compatible. For finer registry display, runtime issues may also include a stable `detailCode`:
268
+
269
+ | Existing issue code | Detail codes |
270
+ | --- | --- |
271
+ | `spawn-failed` | `spawn-failed-before-startup` |
272
+ | `server-exited` | `clean-exit-before-initialize`, `nonzero-exit-before-initialize`, `signal-exit-before-initialize` |
273
+ | `initialize-timeout` | `startup-timeout` |
274
+ | `operation-timeout` | `request-timeout` |
275
+ | `operation-missing-response` | `clean-exit-during-operation`, `nonzero-exit-during-operation`, `signal-exit-during-operation` |
276
+ | `server-crashed` | `nonzero-exit-after-initialize`, `signal-exit-after-initialize` |
277
+
278
+ `process` records the observed lifecycle even when the run passes. `outcome` is one of `starting`, `running`, `exited`, `timeout`, `spawn-failed`, or `guard-terminated`; `starting` is the transient initial value while the child is being created, not an expected terminal outcome. `phase` is `startup`, `initialize`, `operation`, or `post-initialize`. `exitCode` and `signal` are included when the process exits before the guard finishes; timeout runs include `timedOut`, `timeoutCode`, `timeoutMs`, and guard kill metadata. `spawnError` is either `null` or an object with `code` and `message`; the matching `spawn-failed` issue also exposes `spawnErrorCode`.
279
+
280
+ Spawn failure shape:
281
+
282
+ | Field | Shape |
283
+ | --- | --- |
284
+ | `process.spawnError` | `null` or `{ "code": "ENOENT", "message": "spawn missing-command ENOENT" }` |
285
+ | `issues[].spawnErrorCode` | short platform error code such as `ENOENT`, or `""` when unavailable |
286
+
287
+ `fingerprint` helps explain why a result reproduced in one runner but not another. It includes the guard version, redacted command argv, cwd details, protocol, timeout, repeat count, requested operation, platform/arch, relevant runtime versions, package metadata when detectable, static-scan context, and startup/total duration. Environment variable values are always emitted as `<redacted>` and only explicitly provided env names are listed.
288
+
289
+ Registry display flow:
290
+
291
+ | Step | Use |
292
+ | --- | --- |
293
+ | 1 | Show `issueClasses` first so install/runtime, stdio transport, and MCP protocol failures stay distinct |
294
+ | 2 | Use `fingerprint.command`, `fingerprint.cwd`, and `fingerprint.package` to show what was actually run |
295
+ | 3 | Show `checks.capabilities` as advertised MCP surface honesty |
296
+ | 4 | Show `checks.toolSchema` as tool metadata quality, separate from startup and stdio transport health |
297
+ | 5 | Show `drift` warnings as stability advisories, not hard failures, unless another check failed |
298
+ | 6 | Compare `fingerprint.system`, `fingerprint.runtimes`, and `fingerprint.timings` before marking a package broken |
299
+ | 7 | Show `fingerprint.env.names` only when debugging; never ask users to paste secret values |
155
300
 
156
301
  Example:
157
302
 
@@ -159,11 +304,100 @@ Example:
159
304
  {
160
305
  "schemaVersion": 1,
161
306
  "ok": true,
307
+ "config": {
308
+ "enabled": false,
309
+ "path": "",
310
+ "resolvedPath": "",
311
+ "checks": { "command": false, "cwd": false, "envNames": [], "requests": [], "safeToolCalls": [] }
312
+ },
313
+ "profile": "custom",
314
+ "fingerprint": {
315
+ "guard": { "name": "mcp-stdio-guard", "version": "0.4.0" },
316
+ "command": {
317
+ "executable": "node",
318
+ "args": ["./server.js"],
319
+ "argv": ["node", "./server.js"]
320
+ },
321
+ "cwd": {
322
+ "requested": "/repo/server",
323
+ "resolved": "/repo/server",
324
+ "exists": true
325
+ },
326
+ "protocol": "2025-11-25",
327
+ "config": {
328
+ "enabled": false,
329
+ "path": "",
330
+ "resolvedPath": "",
331
+ "checks": { "command": false, "cwd": false, "envNames": [], "requests": [], "safeToolCalls": [] }
332
+ },
333
+ "profile": "custom",
334
+ "timeoutMs": 5000,
335
+ "repeat": 1,
336
+ "capabilityProbes": true,
337
+ "operation": { "method": "tools/list", "hasParams": false, "source": "cli-request", "safeToolCallName": "" },
338
+ "operations": [{ "method": "tools/list", "hasParams": false, "source": "cli-request", "safeToolCallName": "" }],
339
+ "system": { "platform": "darwin", "arch": "arm64", "osRelease": "25.0.0" },
340
+ "runtimes": {
341
+ "node": { "version": "v24.0.0", "role": "guard-and-target" }
342
+ },
343
+ "package": null,
344
+ "env": {
345
+ "inherited": true,
346
+ "names": ["API_TOKEN"],
347
+ "values": { "API_TOKEN": "<redacted>" }
348
+ },
349
+ "staticScan": { "enabled": false, "path": "", "failOnFindings": false },
350
+ "timings": { "startupMs": 42, "totalMs": 96 }
351
+ },
352
+ "process": {
353
+ "started": true,
354
+ "pid": 12345,
355
+ "outcome": "guard-terminated",
356
+ "phase": "post-initialize",
357
+ "exitCode": null,
358
+ "signal": null,
359
+ "timedOut": false,
360
+ "timeoutCode": "",
361
+ "timeoutMs": 5000,
362
+ "killedByGuard": true,
363
+ "killSignal": "SIGTERM",
364
+ "killReason": "guard-finished",
365
+ "spawnError": null
366
+ },
367
+ "capabilityProbes": true,
368
+ "capabilityKeys": ["tools"],
369
+ "capabilityChecks": {
370
+ "tools": { "advertised": true, "method": "tools/list", "responded": true, "itemCount": 2, "error": null },
371
+ "resources": { "advertised": false, "method": "resources/list", "responded": false, "itemCount": null, "error": null },
372
+ "prompts": { "advertised": false, "method": "prompts/list", "responded": false, "itemCount": null, "error": null }
373
+ },
374
+ "issueClasses": {
375
+ "installRuntime": { "status": "pass", "issueCodes": [] },
376
+ "stdioTransport": { "status": "pass", "issueCodes": [] },
377
+ "mcpProtocol": { "status": "pass", "issueCodes": [] }
378
+ },
379
+ "toolSchema": {
380
+ "checked": true,
381
+ "toolCount": 2,
382
+ "toolNames": ["read_file", "search"],
383
+ "validToolCount": 2,
384
+ "warningCount": 0,
385
+ "errorCount": 0,
386
+ "duplicateNames": []
387
+ },
162
388
  "checks": {
163
389
  "initialize": { "status": "pass", "issueCodes": [] },
164
390
  "stdout": { "status": "pass", "issueCodes": [] },
165
391
  "jsonRpc": { "status": "pass", "issueCodes": [] },
166
392
  "operation": { "status": "pass", "issueCodes": [] },
393
+ "capabilities": {
394
+ "status": "pass",
395
+ "issueCodes": [],
396
+ "tools": { "status": "pass", "issueCodes": [], "advertised": true, "method": "tools/list", "responded": true, "itemCount": 2 },
397
+ "resources": { "status": "skipped", "issueCodes": [], "advertised": false, "method": "resources/list", "responded": false, "itemCount": null },
398
+ "prompts": { "status": "skipped", "issueCodes": [], "advertised": false, "method": "prompts/list", "responded": false, "itemCount": null }
399
+ },
400
+ "toolSchema": { "status": "pass", "issueCodes": [] },
167
401
  "process": { "status": "pass", "issueCodes": [] },
168
402
  "pythonBuffering": { "status": "pass", "issueCodes": [] },
169
403
  "staticScan": { "status": "skipped", "issueCodes": [] },
@@ -178,7 +412,7 @@ The guard is registry-agnostic. It does not care whether an install command came
178
412
 
179
413
  ```yaml
180
414
  - run: npm ci
181
- - run: npx mcp-stdio-guard --scan src --fail-on-static --request tools/list -- node ./server.js
415
+ - run: npx mcp-stdio-guard --profile ci --scan src --request tools/list -- node ./server.js
182
416
  ```
183
417
 
184
418
  ## Output
@@ -192,6 +426,7 @@ frames: 2 stdout / 0 invalid
192
426
  stderr: 0 lines
193
427
  protocol: 2025-11-25
194
428
  request: tools/list responded
429
+ tool schemas: 2/2 valid
195
430
  ```
196
431
 
197
432
  Polluted stdout:
@@ -210,7 +445,7 @@ request: tools/list responded
210
445
 
211
446
  - Runtime dependencies: zero.
212
447
  - Default behavior: validate the real process boundary.
213
- - Optional static scan: intentionally simple and conservative.
448
+ - Optional static scan: intentionally simple and conservative; catches common JavaScript and Python stdout writes, stdout logging handlers, and visible startup-output risks.
214
449
  - CI posture: fail on protocol corruption, crashes, and missing responses.
215
450
  - Promotion promise: no fake stars, no spam, just a tool that catches a real MCP failure mode.
216
451
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-stdio-guard",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "A runtime zero-dependency CLI that catches stdout pollution and handshake failures in MCP stdio servers.",
5
5
  "type": "module",
6
6
  "bin": {