@weiseer/mcp-doctor 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/README.md ADDED
@@ -0,0 +1,80 @@
1
+ # @weiseer/mcp-doctor
2
+
3
+ > Install-time trust gate for MCP servers. PASS / WARN / BLOCK + cited evidence.
4
+
5
+ Part of [weiseer](https://github.com/weiseer). Probe **P-010**.
6
+
7
+ ## What it does
8
+
9
+ Scans MCP server packages (or your entire `claude_desktop_config.json` / `cline_config.json`) and tells you which ones to trust before you install or connect to them.
10
+
11
+ Verdict is one of:
12
+ - **PASS** — no significant supply-chain or vulnerability signals
13
+ - **WARN** — material concerns; review before installing
14
+ - **BLOCK** — critical issue (known CVE, typosquat, hardcoded credentials, etc.)
15
+
16
+ All scoring is **open-source and rule-based** — see [rubric.yaml](./rubric.yaml). You can argue with our methodology; we'd rather you do that than trust a black-box ML model.
17
+
18
+ ## Install
19
+
20
+ ```bash
21
+ npm install -g @weiseer/mcp-doctor
22
+ ```
23
+
24
+ Or run on-demand:
25
+
26
+ ```bash
27
+ npx @weiseer/mcp-doctor @modelcontextprotocol/server-github
28
+ ```
29
+
30
+ ## Common uses
31
+
32
+ **1. Check a single package before installing:**
33
+
34
+ ```bash
35
+ npx @weiseer/mcp-doctor @some/mcp-server
36
+ ```
37
+
38
+ **2. Audit your existing MCP config:**
39
+
40
+ ```bash
41
+ npx @weiseer/mcp-doctor --config ~/Library/Application\ Support/Claude/claude_desktop_config.json
42
+ ```
43
+
44
+ **3. CI integration (block bad MCPs in PR):**
45
+
46
+ ```yaml
47
+ - uses: weiseer/mcp-doctor-action@v1
48
+ with:
49
+ packages: '@x/server-foo @y/server-bar'
50
+ ```
51
+
52
+ **4. README trust badge:**
53
+
54
+ ```md
55
+ ![MCP Trust](https://weiseer.com/badge/@x/server-foo)
56
+ ```
57
+
58
+ ## What gets scored
59
+
60
+ - **Supply chain hygiene** — postinstall scripts, unpinned deps, missing provenance, repo URL integrity
61
+ - **Maintainer health** — release cadence, archive status, bus factor, GitHub last-push age
62
+ - **Known vulnerabilities** — direct + transitive CVE via OSV.dev
63
+ - **MCP-specific risk** — typosquat against official servers, hardcoded credentials, capability misdeclaration
64
+
65
+ Full rubric: [rubric.yaml](./rubric.yaml). Open-source by design.
66
+
67
+ ## Why this exists
68
+
69
+ The MCP ecosystem has a security crisis. MCPwn (CVE-2026-33032, CVSS 9.8) exposed 2,600+ instances. The Shai-Hulud npm worm stole MCP auth tokens from 172 packages. MCPSafe found high-severity bugs in *official* MCP servers from Atlassian, GitHub, Cloudflare, Microsoft. Bumblebee shipped from Perplexity in May 2026 specifically because supply-chain scanning was missing for MCP.
70
+
71
+ We agree the problem is real and decided to ship a developer-friendly install gate that fits the existing MCP workflow rather than reinventing it.
72
+
73
+ ## License
74
+
75
+ Apache-2.0.
76
+
77
+ ## Related
78
+
79
+ - Open-source rubric: [rubric.yaml](./rubric.yaml)
80
+ - 9 other weiseer MCP services: [github.com/weiseer](https://github.com/weiseer)
@@ -0,0 +1,130 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @weiseer/mcp-doctor — install-time trust gate CLI for MCP servers.
4
+ * v0.1.0 — Day 3 ship.
5
+ *
6
+ * Usage:
7
+ * npx @weiseer/mcp-doctor <package1> [<package2> ...]
8
+ * npx @weiseer/mcp-doctor --config /path/to/claude_desktop_config.json
9
+ * npx @weiseer/mcp-doctor --json
10
+ *
11
+ * Exits non-zero on any BLOCK — useful for CI integration.
12
+ * License: Apache-2.0. P-010.
13
+ */
14
+ import { scanPackages } from "../lib/scan.js";
15
+ import { readFileSync } from "node:fs";
16
+ import { argv, exit, stderr } from "node:process";
17
+
18
+ function parseArgs(args) {
19
+ const out = { packages: [], json: false, configPath: null, registry: null };
20
+ for (let i = 0; i < args.length; i++) {
21
+ const a = args[i];
22
+ if (a === "--json") out.json = true;
23
+ else if (a === "--config") { out.configPath = args[++i]; }
24
+ else if (a === "--registry") { out.registry = args[++i]; }
25
+ else if (a === "--help" || a === "-h") {
26
+ printHelp();
27
+ exit(0);
28
+ } else if (a.startsWith("--")) {
29
+ stderr.write(`unknown flag: ${a}\n`);
30
+ exit(2);
31
+ } else {
32
+ out.packages.push(a);
33
+ }
34
+ }
35
+ return out;
36
+ }
37
+
38
+ function printHelp() {
39
+ const txt = `mcp-doctor — install-time trust gate for MCP servers.
40
+
41
+ USAGE
42
+ mcp-doctor <package1> [<package2> ...]
43
+ mcp-doctor --config <path-to-claude_desktop_config.json>
44
+ mcp-doctor --json
45
+
46
+ Open-source rubric: https://github.com/weiseer/mcp-doctor/blob/main/rubric.yaml
47
+
48
+ EXIT CODES
49
+ 0 = all PASS or WARN
50
+ 1 = at least one BLOCK
51
+ 2 = invalid usage
52
+ `;
53
+ process.stdout.write(txt);
54
+ }
55
+
56
+ function packagesFromConfig(configPath) {
57
+ const raw = JSON.parse(readFileSync(configPath, "utf-8"));
58
+ const mcpServers = raw.mcpServers || {};
59
+ const pkgs = new Set();
60
+ for (const [, srv] of Object.entries(mcpServers)) {
61
+ // Detect npx -y <pkg> args
62
+ if (srv.command === "npx") {
63
+ const args = srv.args || [];
64
+ // skip -y flags, pick first non-flag arg
65
+ for (const a of args) {
66
+ if (a.startsWith("-")) continue;
67
+ pkgs.add(a);
68
+ break;
69
+ }
70
+ }
71
+ }
72
+ return [...pkgs];
73
+ }
74
+
75
+ function emoji(v) {
76
+ return ({ PASS: "✓", WARN: "⚠", BLOCK: "✗", ERROR: "?" })[v] || "?";
77
+ }
78
+
79
+ function renderHuman(r) {
80
+ const head = `${emoji(r.verdict)} ${r.verdict}: ${r.package}@${r.version || "?"} (score ${r.score}/100)${r.self_disclosure ? " [self-disclosed]" : ""}`;
81
+ if (r.error) return head + `\n ERROR: ${r.error}`;
82
+ const sigs = (r.triggered_signals || []).map(s => ` -${s.deduct}${s.hard_block ? " HARD" : ""} ${s.signal_id}: ${s.evidence}`);
83
+ const m = r.metadata || {};
84
+ const tail = ` m=${m.maintainer_count} dsr=${m.days_since_release} gh_dsp=${m.github_days_since_push} stars=${m.github_stars} deps=${m.dep_count} osv=${m.osv_vuln_count} lic=${m.license}`;
85
+ return [head, ...sigs, tail].join("\n");
86
+ }
87
+
88
+ async function main() {
89
+ const args = parseArgs(argv.slice(2));
90
+ let packages = args.packages.slice();
91
+
92
+ if (args.configPath) {
93
+ try {
94
+ packages = packages.concat(packagesFromConfig(args.configPath));
95
+ } catch (e) {
96
+ stderr.write(`config parse failed: ${e.message}\n`);
97
+ exit(2);
98
+ }
99
+ }
100
+
101
+ if (packages.length === 0) {
102
+ printHelp();
103
+ exit(2);
104
+ }
105
+
106
+ // Day 3 strategy: scanner is the Python engine. For v0.1 ship, the Node CLI
107
+ // either (a) executes a hosted endpoint or (b) wraps a bundled scanning core.
108
+ // Day 3 takes the simplest correct path: call the public scoring endpoint.
109
+ //
110
+ // For users who want fully-offline: --registry file:///path/to/bundled-db.json
111
+ // will land in Day 5.
112
+ const results = await scanPackages(packages, { registry: args.registry });
113
+
114
+ if (args.json) {
115
+ process.stdout.write(JSON.stringify(results, null, 2) + "\n");
116
+ } else {
117
+ for (const r of results) {
118
+ process.stdout.write(renderHuman(r) + "\n\n");
119
+ }
120
+ const counts = results.reduce((acc, r) => { acc[r.verdict] = (acc[r.verdict] || 0) + 1; return acc; }, {});
121
+ process.stdout.write(`summary: ${JSON.stringify(counts)}\n`);
122
+ }
123
+
124
+ if (results.some(r => r.verdict === "BLOCK")) exit(1);
125
+ }
126
+
127
+ main().catch(err => {
128
+ stderr.write(`mcp-doctor: fatal: ${err.message}\n`);
129
+ exit(2);
130
+ });
package/lib/scan.js ADDED
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Scan dispatcher for mcp-doctor CLI.
3
+ *
4
+ * v0.1 (Day 3): calls the public scoring endpoint at weiseer-mcp-doctor backend.
5
+ * v0.2+ (Day 5): bundles trust DB + rubric for fully-offline scan.
6
+ *
7
+ * The hosted endpoint runs the same open-source rubric (rubric.yaml in this repo).
8
+ * The endpoint is rate-limited free / no auth required, capped at 1000 calls/day/IP.
9
+ */
10
+ const ENDPOINT = process.env.MCP_DOCTOR_ENDPOINT || "https://oracle.weiseer.com/mcp-doctor/scan";
11
+ const LOCAL_FALLBACK = process.env.MCP_DOCTOR_LOCAL_FALLBACK === "1";
12
+
13
+ async function scanOnePackage(pkg) {
14
+ const url = new URL(ENDPOINT);
15
+ url.searchParams.set("pkg", pkg);
16
+ try {
17
+ const ctrl = new AbortController();
18
+ const t = setTimeout(() => ctrl.abort(), 15000);
19
+ const res = await fetch(url, { signal: ctrl.signal, headers: { "User-Agent": "weiseer-mcp-doctor-cli/0.1.0" } });
20
+ clearTimeout(t);
21
+ if (!res.ok) {
22
+ return {
23
+ package: pkg, version: "", verdict: "ERROR", score: 0,
24
+ error: `endpoint http ${res.status}`,
25
+ triggered_signals: [], metadata: {},
26
+ scanned_at: new Date().toISOString(),
27
+ };
28
+ }
29
+ return await res.json();
30
+ } catch (e) {
31
+ return {
32
+ package: pkg, version: "", verdict: "ERROR", score: 0,
33
+ error: `endpoint unreachable: ${e.message}`,
34
+ triggered_signals: [], metadata: {},
35
+ scanned_at: new Date().toISOString(),
36
+ };
37
+ }
38
+ }
39
+
40
+ export async function scanPackages(packages, opts = {}) {
41
+ const results = [];
42
+ // Sequential for now; the rate-limit per IP is generous but we are polite.
43
+ for (const pkg of packages) {
44
+ results.push(await scanOnePackage(pkg));
45
+ }
46
+ return results;
47
+ }
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@weiseer/mcp-doctor",
3
+ "version": "0.1.0",
4
+ "mcpName": "io.github.weiseer/mcp-doctor",
5
+ "description": "Install-time trust gate for MCP servers — PASS/WARN/BLOCK + cited evidence + GitHub Action support.",
6
+ "type": "module",
7
+ "bin": {
8
+ "mcp-doctor": "./bin/mcp-doctor.js"
9
+ },
10
+ "main": "./lib/index.js",
11
+ "files": [
12
+ "bin/",
13
+ "lib/",
14
+ "rubric.yaml",
15
+ "README.md"
16
+ ],
17
+ "scripts": {
18
+ "start": "node bin/mcp-doctor.js"
19
+ },
20
+ "keywords": [
21
+ "mcp",
22
+ "model-context-protocol",
23
+ "security",
24
+ "trust",
25
+ "supply-chain",
26
+ "vulnerability",
27
+ "audit",
28
+ "ai-agent",
29
+ "weiseer"
30
+ ],
31
+ "author": "weiseer <wei@weiseer.com>",
32
+ "license": "Apache-2.0",
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "https://github.com/weiseer/mcp-doctor.git"
36
+ },
37
+ "homepage": "https://github.com/weiseer/mcp-doctor#readme",
38
+ "bugs": {
39
+ "url": "https://github.com/weiseer/mcp-doctor/issues"
40
+ },
41
+ "engines": {
42
+ "node": ">=18.0.0"
43
+ }
44
+ }