mcptrustchecker 1.0.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/LICENSE +21 -0
- package/README.md +379 -0
- package/dist/acquire/clientConfig.d.ts +47 -0
- package/dist/acquire/clientConfig.js +124 -0
- package/dist/acquire/discover.d.ts +12 -0
- package/dist/acquire/discover.js +53 -0
- package/dist/acquire/index.d.ts +24 -0
- package/dist/acquire/index.js +175 -0
- package/dist/acquire/live.d.ts +47 -0
- package/dist/acquire/live.js +357 -0
- package/dist/acquire/manifest.d.ts +10 -0
- package/dist/acquire/manifest.js +94 -0
- package/dist/acquire/npm.d.ts +10 -0
- package/dist/acquire/npm.js +55 -0
- package/dist/acquire/source.d.ts +16 -0
- package/dist/acquire/source.js +109 -0
- package/dist/cli/index.d.ts +15 -0
- package/dist/cli/index.js +396 -0
- package/dist/config.d.ts +15 -0
- package/dist/config.js +81 -0
- package/dist/data/capabilityLexicon.d.ts +51 -0
- package/dist/data/capabilityLexicon.js +204 -0
- package/dist/data/confusables.d.ts +17 -0
- package/dist/data/confusables.js +65 -0
- package/dist/data/injectionPatterns.d.ts +55 -0
- package/dist/data/injectionPatterns.js +216 -0
- package/dist/data/knownCves.d.ts +24 -0
- package/dist/data/knownCves.js +49 -0
- package/dist/data/protectedPackages.d.ts +30 -0
- package/dist/data/protectedPackages.js +89 -0
- package/dist/data/ruleCatalog.d.ts +16 -0
- package/dist/data/ruleCatalog.js +94 -0
- package/dist/data/sourcePatterns.d.ts +25 -0
- package/dist/data/sourcePatterns.js +92 -0
- package/dist/data/unicode.d.ts +34 -0
- package/dist/data/unicode.js +99 -0
- package/dist/detectors/capability.d.ts +14 -0
- package/dist/detectors/capability.js +281 -0
- package/dist/detectors/collision.d.ts +11 -0
- package/dist/detectors/collision.js +63 -0
- package/dist/detectors/flowGraph.d.ts +43 -0
- package/dist/detectors/flowGraph.js +100 -0
- package/dist/detectors/index.d.ts +18 -0
- package/dist/detectors/index.js +26 -0
- package/dist/detectors/injection.d.ts +9 -0
- package/dist/detectors/injection.js +178 -0
- package/dist/detectors/meta.d.ts +8 -0
- package/dist/detectors/meta.js +33 -0
- package/dist/detectors/posture.d.ts +9 -0
- package/dist/detectors/posture.js +128 -0
- package/dist/detectors/source.d.ts +12 -0
- package/dist/detectors/source.js +87 -0
- package/dist/detectors/supplyChain.d.ts +17 -0
- package/dist/detectors/supplyChain.js +243 -0
- package/dist/detectors/toxicFlow.d.ts +22 -0
- package/dist/detectors/toxicFlow.js +201 -0
- package/dist/detectors/unicode.d.ts +21 -0
- package/dist/detectors/unicode.js +244 -0
- package/dist/engine.d.ts +27 -0
- package/dist/engine.js +135 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.js +46 -0
- package/dist/lockfile.d.ts +30 -0
- package/dist/lockfile.js +87 -0
- package/dist/policy.d.ts +13 -0
- package/dist/policy.js +48 -0
- package/dist/report/badge.d.ts +14 -0
- package/dist/report/badge.js +18 -0
- package/dist/report/json.d.ts +4 -0
- package/dist/report/json.js +4 -0
- package/dist/report/markdown.d.ts +6 -0
- package/dist/report/markdown.js +110 -0
- package/dist/report/sarif.d.ts +7 -0
- package/dist/report/sarif.js +105 -0
- package/dist/report/terminal.d.ts +15 -0
- package/dist/report/terminal.js +252 -0
- package/dist/scoring/capability.d.ts +10 -0
- package/dist/scoring/capability.js +48 -0
- package/dist/scoring/index.d.ts +10 -0
- package/dist/scoring/index.js +96 -0
- package/dist/scoring/model.d.ts +50 -0
- package/dist/scoring/model.js +101 -0
- package/dist/types.d.ts +361 -0
- package/dist/types.js +12 -0
- package/dist/util/ansi.d.ts +26 -0
- package/dist/util/ansi.js +42 -0
- package/dist/util/capabilities.d.ts +17 -0
- package/dist/util/capabilities.js +68 -0
- package/dist/util/distance.d.ts +23 -0
- package/dist/util/distance.js +119 -0
- package/dist/util/hash.d.ts +20 -0
- package/dist/util/hash.js +80 -0
- package/dist/util/text.d.ts +45 -0
- package/dist/util/text.js +170 -0
- package/dist/version.d.ts +13 -0
- package/dist/version.js +13 -0
- package/package.json +72 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Illia Haidar · https://mcptrustchecker.com · support@mcptrustchecker.com
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# 🛡️ MCP Trust Checker
|
|
4
|
+
|
|
5
|
+
### The local-first, deterministic security scanner for MCP servers
|
|
6
|
+
|
|
7
|
+
**Know whether a Model Context Protocol server is safe *before* you connect it to your data.**
|
|
8
|
+
|
|
9
|
+
[](LICENSE)
|
|
10
|
+
[](package.json)
|
|
11
|
+
[](docs/methodology.md)
|
|
12
|
+
[](test)
|
|
13
|
+
[](docs/rules.md)
|
|
14
|
+
[](#why-this-is-different)
|
|
15
|
+
[](#why-this-is-different)
|
|
16
|
+
|
|
17
|
+
<br/>
|
|
18
|
+
|
|
19
|
+
<img src="docs/hero.svg" alt="MCP Trust Checker terminal output: an MCP server graded D with a detected toxic-flow trifecta" width="720"/>
|
|
20
|
+
|
|
21
|
+
<br/>
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx mcptrustchecker # 🔍 scan every MCP server you already have installed — zero config
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
<sub>· offline · deterministic · no account · no API key · <a href="#the-algorithm-the-capability-flow-trust-model">one novel core</a> ·</sub>
|
|
28
|
+
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## What makes the algorithm unique
|
|
34
|
+
|
|
35
|
+
The **Capability-Flow Trust Model** (methodology `mcptrustchecker-1.0`) is an **original algorithm designed from scratch for this project** by [Illia Haidar](https://github.com/illiahaidar) — it is not a wrapper around, or derivative of, any existing scanner or methodology. It is named, versioned, fully specified in [docs/methodology.md](docs/methodology.md), and citable via [CITATION.cff](CITATION.cff).
|
|
36
|
+
|
|
37
|
+
MCP Trust Checker scores an MCP server the way an **attacker** reasons about it — not as a bag of regex hits, but as a **Capability-Flow Trust Model**. Every tool is reduced to the roles it can actually play — *untrusted-input ingress*, *sensitive-data source*, *external / exec sink* — derived from behavior, **never** from the server's own (attacker-controllable) annotations. Those roles are wired into a **cross-tool toxic-flow graph** that hunts the *lethal trifecta*: the moment untrusted content, private data, and an exfiltration path co-exist in one agent session — whether inside a single tool or composed across several tools plus the client's built-ins. That is the exact shape behind real-world MCP data-exfiltration exploits, and MCP Trust Checker proves the primitive exists **statically**, with an honest confidence split so a single-tool completion reads `confirmed` and a cross-tool composition reads `strong` — never overclaiming.
|
|
38
|
+
|
|
39
|
+
Three more things sit under that graph:
|
|
40
|
+
|
|
41
|
+
- **It decodes, not strips.** Unicode Tags-block and variation-selector payloads are *recovered and printed back to you as evidence*, so a hidden "read `~/.ssh/id_rsa` and BCC the attacker" becomes visible text instead of a silent flag.
|
|
42
|
+
- **It has rug-pull integrity built in.** The canonical surface is hashed and pinned; any post-approval mutation of a tool definition trips a `confirmed` finding with a per-tool diff.
|
|
43
|
+
- **Every point of the 0–100 Trust Score is an auditable, deterministic penalty vector** — fixed severity weights, a confidence multiplier, diminishing returns, per-category caps, and weakest-link gates. Fully reconstructable, identical on every run, gameable by no one. **No LLM in the loop, no telemetry, no account.**
|
|
44
|
+
|
|
45
|
+
> And it is **comprehensive by design**: the full catalog of known MCP attack techniques is covered in one offline pass — no API key, no LLM. [See the full coverage map ↓](#coverage-the-full-catalog-of-mcp-attack-techniques)
|
|
46
|
+
|
|
47
|
+
**Jump to:** [Why it's different](#why-this-is-different) · [Quick start](#quick-start) · [The algorithm](#the-algorithm-the-capability-flow-trust-model) · [Coverage](#coverage-the-full-catalog-of-mcp-attack-techniques) · [Scoring](#the-trust-score-is-auditable-by-construction) · [Embed as a library](#embed-the-exact-same-engine-marketplaces--platforms) · [CI / GitHub](#ci--github-integration) · [Rules](docs/rules.md)
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## The one question millions of MCP users can't answer
|
|
52
|
+
|
|
53
|
+
> **"Is this server safe to give access to my files, my tokens, and my conversations?"**
|
|
54
|
+
|
|
55
|
+
An MCP server hands an AI assistant a set of *tools*. Those tool descriptions are read by the model, not by you — a perfect place to hide instructions. A single server that can *read a file* and *make an HTTP request* is already a data-exfiltration weapon. And a server can look harmless on day one, then silently redefine its tools after you approve it. MCP Trust Checker turns all of that into a transparent letter grade you can act on.
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
╭────────────╮
|
|
59
|
+
│ GRADE D │ Trust Score 69/100
|
|
60
|
+
╰────────────╯ methodology mcptrustchecker-1.0
|
|
61
|
+
|
|
62
|
+
Toxic flows (untrusted-input → sensitive-source → external-sink)
|
|
63
|
+
[critical] The three trifecta roles are co-present across tools;
|
|
64
|
+
a prompt-injected agent can chain them.
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Why this is different
|
|
70
|
+
|
|
71
|
+
MCP Trust Checker's wedge is **accuracy + explainability + privacy**, with one genuinely novel core — the cross-tool toxic-flow graph. Every property below holds together in a single offline binary — no account, no LLM in the loop, no telemetry:
|
|
72
|
+
|
|
73
|
+
- 🔒 **Runs 100% offline** — no account, token, API key, or hosted service; your data never leaves the machine.
|
|
74
|
+
- 🎯 **Deterministic** — same input ⇒ byte-identical score, on every run and every machine.
|
|
75
|
+
- 🕸️ **Cross-tool toxic-flow graph** — proves the lethal trifecta statically, composed across tools, not just within one.
|
|
76
|
+
- 🔬 **Reads the code, not just the claim** — when the server's source is available (`scan ./path`), it grades what the implementation *does* (eval / shell-spawn / hardcoded egress / credential reads / obfuscated payloads), so a poisoned server can't hide behind honest-looking tool descriptions. Metadata **and** implementation, in one deterministic pass.
|
|
77
|
+
- 🔎 **Decodes, not strips** — hidden Unicode payloads (Tags block / variation-selector) are recovered and shown as evidence.
|
|
78
|
+
- 📌 **Rug-pull integrity** — the full tool surface is hashed and pinned; any post-approval drift trips a `confirmed` finding with a per-tool diff.
|
|
79
|
+
- 🧾 **Auditable Trust Score** — every point is a published, reproducible penalty vector.
|
|
80
|
+
- ⚙️ **SARIF + GitHub Action + CI gates** — machine-readable output and pass/fail thresholds out of the box.
|
|
81
|
+
- 📦 **Embeddable library** — the identical, versioned engine a marketplace can reuse on-site.
|
|
82
|
+
- 🪶 **MIT, plain-data rules** — every rule is transparent and contribution-friendly.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Measured accuracy
|
|
87
|
+
|
|
88
|
+
Most scanners *assert* they have a low false-positive rate. This one **measures** it. A labeled corpus of malicious and benign MCP servers lives in [`benchmark/`](benchmark/); `npm run benchmark` scores it and reports the numbers (and fails CI on a regression):
|
|
89
|
+
|
|
90
|
+
| Metric | Score |
|
|
91
|
+
| --- | :---: |
|
|
92
|
+
| Precision | **100%** |
|
|
93
|
+
| Recall | **100%** |
|
|
94
|
+
| F1 | **100%** |
|
|
95
|
+
| False-positive rate | **0%** |
|
|
96
|
+
|
|
97
|
+
*(64 labeled servers, held-out cases flagged; "concerning" := Trust grade C or worse. Reproduce with `npm run benchmark`.)* The corpus is honest and versioned — it grows with every calibration case, and the CI gate holds precision/recall ≥ 90%.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Install
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
npx mcptrustchecker scan ./tools.json # zero-install
|
|
105
|
+
npm i -g mcptrustchecker # CLI everywhere
|
|
106
|
+
npm i mcptrustchecker # embed the engine in your app
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Requires Node ≥ 20. Live scanning uses the official [`@modelcontextprotocol/sdk`](https://www.npmjs.com/package/@modelcontextprotocol/sdk).
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Quick start
|
|
114
|
+
|
|
115
|
+
**Zero-config — one command, scans everything you have installed:**
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
npx mcptrustchecker # auto-discovers Claude Desktop/Code, Cursor, Windsurf, Continue, VS Code configs
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**Or point it at anything:**
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
mcptrustchecker scan ./tools.json # an offline manifest (deterministic)
|
|
125
|
+
mcptrustchecker scan ./path/to/mcp-server # a local package dir — analyzes the CODE too
|
|
126
|
+
mcptrustchecker scan --command "npx -y @some/mcp-server" # a local stdio server (sandboxed)
|
|
127
|
+
mcptrustchecker scan https://mcp.example.com/mcp # a live HTTP/SSE endpoint
|
|
128
|
+
mcptrustchecker scan @modelcontextprotocol/server-filesystem --online # a package name (typosquat/CVE)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**Outputs & CI gates:**
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
mcptrustchecker scan ./tools.json --sarif > mcptrustchecker.sarif # GitHub code scanning
|
|
135
|
+
mcptrustchecker scan ./tools.json --md > report.md # PR comment
|
|
136
|
+
mcptrustchecker scan ./tools.json --json > report.json # machine-readable
|
|
137
|
+
mcptrustchecker scan ./tools.json --badge > badge.json # shields.io endpoint
|
|
138
|
+
mcptrustchecker scan ./tools.json --fail-under 80 # exit 1 below a threshold
|
|
139
|
+
mcptrustchecker scan ./tools.json --min-grade B # exit 1 below a grade
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
The terminal report is **detailed by default** — every finding prints its full description (**what** the problem is and **why** it matters), the exact location, the offending **evidence**, a **fix**, and its OWASP mapping, grouped most-severe-first. Add `--details` for external references, or `--quiet` for just the grade line.
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## The algorithm: the Capability-Flow Trust Model
|
|
147
|
+
|
|
148
|
+
A 9-stage pipeline over a **normalized, transport-agnostic surface** (tools, prompts, resources, server instructions, transport, package metadata). Each stage emits findings; the scorer turns them into an auditable grade.
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
INPUT ─ manifest.json │ live stdio/http │ client config │ package name
|
|
152
|
+
│
|
|
153
|
+
▼
|
|
154
|
+
[0] SAFE ACQUISITION allow-listed command (bare-name only) · scrubbed env · timeouts · SSRF guard
|
|
155
|
+
▼
|
|
156
|
+
[1] UNICODE INTEGRITY decode Tags/variation-selector payloads; BiDi; zero-width; homoglyph; ANSI
|
|
157
|
+
▼
|
|
158
|
+
[2] INJECTION HEURISTICS tool-poisoning · line-jumping · shadowing · secrecy · exfil · embedded secrets
|
|
159
|
+
▼
|
|
160
|
+
[3] CAPABILITY EXTRACTION tag tools (untrusted-input / sensitive-source / sink / exec / write);
|
|
161
|
+
│ annotation mismatch; sampling/elicitation; schema injection preconditions
|
|
162
|
+
▼
|
|
163
|
+
[4] TOXIC-FLOW GRAPH ★ the lethal trifecta across tools AND client built-ins; + name-collision
|
|
164
|
+
▼
|
|
165
|
+
[5] SUPPLY-CHAIN typosquat/combosquat/homoglyph · install-scripts · provenance · unpinned · deps
|
|
166
|
+
▼
|
|
167
|
+
[6] TRANSPORT POSTURE stdio-RCE · plaintext HTTP · 0.0.0.0 · DNS-rebinding · known-CVE version matcher
|
|
168
|
+
▼
|
|
169
|
+
[7] RUG-PULL INTEGRITY SHA-256 pin of the full schema → diff on every rescan
|
|
170
|
+
▼
|
|
171
|
+
[8] SCORING deterministic penalties · diminishing returns · category caps · hard gates
|
|
172
|
+
▼
|
|
173
|
+
OUTPUT ─ terminal │ JSON │ SARIF 2.1.0 │ Markdown │ badge
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
**★ The flagship — cross-tool toxic-flow analysis.** The most dangerous MCP failures aren't one bad tool; they're an innocent *combination*. Give an agent (1) exposure to **untrusted content**, (2) access to **sensitive data**, and (3) a way to **communicate externally**, and you have an exfiltration primitive. MCP Trust Checker derives each tool's roles from behavior (not from its self-declared, attacker-controllable `annotations`) and checks whether the three are co-reachable across every tool, every server, and optionally the client's own built-ins (`--include-builtins`). One tool holding all three → **critical, confirmed**; the roles spread across tools → **critical, strong**.
|
|
177
|
+
|
|
178
|
+
Full depth: **[docs/methodology.md](docs/methodology.md)**.
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Coverage: the full catalog of MCP attack techniques
|
|
183
|
+
|
|
184
|
+
MCP Trust Checker covers the full catalog of known MCP attack techniques in one offline pass — from tool-poisoning and Unicode smuggling to supply-chain risk and cross-tool toxic flows — plus the flow graph, the decoder, the integrity pin, and the auditable score on top. ★ marks a check that goes beyond what static scanners typically catch.
|
|
185
|
+
|
|
186
|
+
<details>
|
|
187
|
+
<summary><b>📋 Full technique → rule coverage map</b> (37 techniques — click to expand)</summary>
|
|
188
|
+
|
|
189
|
+
<br/>
|
|
190
|
+
|
|
191
|
+
| Attack / technique | MCP Trust Checker rule(s) |
|
|
192
|
+
| --- | --- |
|
|
193
|
+
| Tool poisoning (hidden instructions in descriptions) | `MTC-INJ-AUTH-*`, `MTC-INJ-SECRECY-*`, `MTC-INJ-TARGET-*`, `MTC-INJ-POISON` |
|
|
194
|
+
| Prompt injection / instruction override | `MTC-INJ-AUTH-2`, `MTC-INJ-SECRECY-1` |
|
|
195
|
+
| Line jumping (pre-invocation seeding) | `MTC-INJ-SEQ-1` |
|
|
196
|
+
| Tool shadowing via description redirect | `MTC-INJ-SHADOW-1` |
|
|
197
|
+
| Cross-**server** tool-name collision / homoglyph name | `MTC-INJ-SHADOW-2` ★ |
|
|
198
|
+
| Tool-selection ranking manipulation | `MTC-INJ-SHADOW-3` ★ |
|
|
199
|
+
| Invisible-Unicode channels (zero-width/BiDi/Tags/VS) | `MTC-UNI-001..008` (decoded) ★ |
|
|
200
|
+
| Homoglyph / mixed-script | `MTC-UNI-009` |
|
|
201
|
+
| ANSI terminal-escape deception | `MTC-UNI-010` ★ |
|
|
202
|
+
| Encoded-payload smuggling (base64 + decode) | `MTC-INJ-ENC-1/2` |
|
|
203
|
+
| Shell/command-injection strings in prose | `MTC-INJ-CMD-1`, `MTC-CAP-001` |
|
|
204
|
+
| Command/code-execution capability | `MTC-CAP-001` |
|
|
205
|
+
| Filesystem-mutation capability | `MTC-CAP-002` |
|
|
206
|
+
| Annotation spoofing (readOnly/destructive lie) | `MTC-CAP-003` |
|
|
207
|
+
| openWorldHint + sensitive read (trifecta signal) | `MTC-CAP-004` ★ |
|
|
208
|
+
| **Toxic-flow analysis / lethal trifecta** | `MTC-FLOW-001..005` ★ |
|
|
209
|
+
| Command-injection sink precondition (schema) | `MTC-CAP-006` ★ |
|
|
210
|
+
| SSRF / cloud-metadata sink precondition (schema) | `MTC-CAP-007` ★ |
|
|
211
|
+
| Path-traversal precondition (schema) | `MTC-CAP-008` ★ |
|
|
212
|
+
| Sampling-capability abuse | `MTC-CAP-009` ★ (static proxy) |
|
|
213
|
+
| Elicitation abuse / consent phishing | `MTC-CAP-010` ★ (static proxy) |
|
|
214
|
+
| Rug pull / silent tool-definition mutation | `MTC-TOFU-001` + lockfile |
|
|
215
|
+
| Unpinned / @latest auto-update (rug-pull enabler) | `MTC-SUP-013` ★ |
|
|
216
|
+
| Typosquat / combosquat / homoglyph squat | `MTC-SUP-001..006` |
|
|
217
|
+
| Install-script / provenance risk | `MTC-SUP-010/011/012` |
|
|
218
|
+
| Dependency squat / advisory match | `MTC-SUP-014` ★ |
|
|
219
|
+
| Known-CVE version matching | `MTC-NET-001` |
|
|
220
|
+
| stdio-RCE (unallowlisted command) | `MTC-NET-002` + sandboxed acquisition |
|
|
221
|
+
| Plaintext HTTP / 0.0.0.0 bind | `MTC-NET-003/004` |
|
|
222
|
+
| DNS rebinding on localhost transport | `MTC-NET-006` ★ |
|
|
223
|
+
| Embedded credential value in metadata | `MTC-INJ-SECRET-1` ★ |
|
|
224
|
+
| Empty/malformed surface ≠ clean | `MTC-META-001` ★ |
|
|
225
|
+
| Config discovery across MCP clients | client-config parser + zero-config auto-discovery |
|
|
226
|
+
| Malicious URL / exfil endpoint in tool metadata | `MTC-INJ-URL-1` ★ |
|
|
227
|
+
| **Implementation-level sinks** — eval / shell-spawn / hardcoded egress / deserialization | `MTC-SRC-001…007` ★ |
|
|
228
|
+
| Credential-path read / environment dump in server code | `MTC-SRC-006` ★ |
|
|
229
|
+
| Hardcoded secret in the server's source (not just metadata) | `MTC-SRC-008` ★ |
|
|
230
|
+
</details>
|
|
231
|
+
|
|
232
|
+
**Deliberately out of scope** (so the deterministic, offline, no-account promise holds): LLM-as-judge semantic classification, hosted threat-intel, a runtime guardrail *proxy*, live authN/replay/signing probing, and OAuth-endpoint source analysis. Where a runtime-only class has a static proxy — a *declared* sampling/elicitation capability, an *unbounded* URL/command/path parameter — MCP Trust Checker flags the precondition offline instead.
|
|
233
|
+
|
|
234
|
+
Full list: **[docs/rules.md](docs/rules.md)** · run `mcptrustchecker rules`.
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## Two axes: Trust (grade) and Capability (blast radius)
|
|
239
|
+
|
|
240
|
+
A single number can't answer "should I use this server?" — because **"powerful" and "malicious" are different questions.** A web scraper legitimately needs to fetch untrusted pages and act on them; that's a large *blast radius*, not evidence it's a bad actor. So MCP Trust Checker reports two independent things:
|
|
241
|
+
|
|
242
|
+
- **Trust — the A–F grade.** Driven by *threat* signals: prompt-injection with concealment, embedded secrets, Unicode smuggling, typosquatting, known CVEs, rug-pull drift, annotation lies, a single tool built as an exfiltration primitive. Answers **"any sign this server is malicious or negligent?"**
|
|
243
|
+
- **Capability — a level (Minimal → Critical).** Driven by what the server *can do*: code execution, filesystem writes, network egress, the cross-tool toxic-flow surface. Answers **"how much damage if the model driving it is manipulated?"** — a fact to size access against, **not** a mark against the server.
|
|
244
|
+
|
|
245
|
+
```
|
|
246
|
+
firecrawl Trust B (81/100) Capability CRITICAL → trustworthy, but huge blast radius — grant carefully
|
|
247
|
+
poisoned Trust F Capability HIGH → actual malice signals — avoid
|
|
248
|
+
memory Trust A (100) Capability MINIMAL → safe and low-power
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
This is why MCP Trust Checker doesn't collapse every capable server into "F" (which would make the grade useless). Popularity is never an input — popular packages get compromised — but a legitimate powerful server keeps a high Trust grade while its Capability is surfaced honestly.
|
|
252
|
+
|
|
253
|
+
## The Trust grade is auditable by construction
|
|
254
|
+
|
|
255
|
+
```
|
|
256
|
+
TrustScore = clamp( 100 − Σ_categories min(CategoryCap, Σ penalty), 0, 100 )
|
|
257
|
+
penalty = severity_weight × confidence_multiplier × diminishing_factor (threat findings only)
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
| Severity | Weight | | Confidence | × |
|
|
261
|
+
| --- | ---: | --- | --- | ---: |
|
|
262
|
+
| Critical | 45 | | Confirmed | 1.0 |
|
|
263
|
+
| High | 22 | | Strong | 0.7 |
|
|
264
|
+
| Medium | 9 | | Heuristic | 0.4 |
|
|
265
|
+
| Low | 3 | | Speculative | 0.2 |
|
|
266
|
+
|
|
267
|
+
- **Diminishing returns** (`1 · ½ · ¼ · …`) so 40 copies of one nit can't tank a server and benign passes can't dilute one critical.
|
|
268
|
+
- **Per-category caps** (injection 50, exfiltration 50, permissions 35, supply-chain 30, network 25, hygiene 10).
|
|
269
|
+
- **Hard gates** (weakest-link): a **confirmed critical → F**; **any** critical → at most **D**; a confirmed high → at most **C**; two → **D**. Most gates fire only on `confirmed` findings so a guess never forces a cap — but no critical of any confidence can score above D.
|
|
270
|
+
- **Bands:** A 90–100 · B 80–89 · C 70–79 · D 60–69 · F 0–59.
|
|
271
|
+
|
|
272
|
+
Every report ships the full itemized `vector` and `methodologyVersion`. **Same methodology version + same target ⇒ byte-identical score.** Details: **[docs/scoring.md](docs/scoring.md)**.
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
## Embed the exact same engine (marketplaces & platforms)
|
|
277
|
+
|
|
278
|
+
MCP Trust Checker is a library first. A marketplace can vet every listed server with **the identical, versioned open-source engine** users audit on GitHub — so "we run unique security checks on every MCP server" becomes a *verifiable* claim.
|
|
279
|
+
|
|
280
|
+
```ts
|
|
281
|
+
import { surfaceFromManifest, scanSurface, renderBadge } from 'mcptrustchecker';
|
|
282
|
+
|
|
283
|
+
const surface = surfaceFromManifest(toolsJson, 'acme/weather-mcp');
|
|
284
|
+
const report = await scanSurface(surface);
|
|
285
|
+
|
|
286
|
+
report.score.grade; // 'A' … 'F'
|
|
287
|
+
report.score.score; // 0 … 100
|
|
288
|
+
report.score.methodologyVersion; // 'mcptrustchecker-1.0' ← pin & display this
|
|
289
|
+
report.toxicFlows; // enumerated exfiltration primitives
|
|
290
|
+
renderBadge(report); // shields.io endpoint JSON for a live trust badge
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
`scanSurface` is pure, deterministic, and offline. See **[examples/programmatic.ts](examples/programmatic.ts)**.
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
## Rug-pull protection (Trust On First Use)
|
|
298
|
+
|
|
299
|
+
```bash
|
|
300
|
+
mcptrustchecker pin ./tools.json # writes mcptrustchecker.lock — commit it to git
|
|
301
|
+
mcptrustchecker diff ./tools.json # exits non-zero if the surface changed
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
A tool that quietly rewrites its description after you approve it (the MCPoison / rug-pull class) shows up as **drift** with a human-readable diff, and re-approval is required.
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
## CI / GitHub integration
|
|
309
|
+
|
|
310
|
+
```yaml
|
|
311
|
+
# .github/workflows/mcptrustchecker.yml
|
|
312
|
+
name: MCP Trust Checker
|
|
313
|
+
on: [push, pull_request]
|
|
314
|
+
jobs:
|
|
315
|
+
scan:
|
|
316
|
+
runs-on: ubuntu-latest
|
|
317
|
+
permissions: { contents: read, security-events: write }
|
|
318
|
+
steps:
|
|
319
|
+
- uses: actions/checkout@v4
|
|
320
|
+
- uses: illiahaidar/mcptrustchecker@v0
|
|
321
|
+
with:
|
|
322
|
+
target: ./tools.json
|
|
323
|
+
min-grade: B
|
|
324
|
+
sarif: true # uploads to the Security tab
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
See **[docs/ci-integration.md](docs/ci-integration.md)** and **[action.yml](action.yml)**.
|
|
328
|
+
|
|
329
|
+
---
|
|
330
|
+
|
|
331
|
+
## Safety: scanning is itself an attack surface
|
|
332
|
+
|
|
333
|
+
Connecting to an MCP config can run arbitrary commands. MCP Trust Checker's acquisition is sandboxed by default:
|
|
334
|
+
|
|
335
|
+
- stdio commands are **allow-listed by bare name** (`npx, uvx, python, python3, node, docker, deno`) — a **path-qualified** command (e.g. `/tmp/evil/node`) is refused, closing the basename-spoof bypass.
|
|
336
|
+
- child env is **scrubbed** of execution-hijacking variables (`NODE_OPTIONS`, `LD_PRELOAD`, `DYLD_*`, `PYTHON*`, `()`-functions) — an allow-listed runtime can't be redirected.
|
|
337
|
+
- servers in a client config are **not spawned** unless you pass `--run`; config-derived HTTP targets are **SSRF-guarded** (private/loopback/link-local blocked).
|
|
338
|
+
- HTTP targets are scheme/host-validated; responses are size-capped; all connects are timeout-bounded.
|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
## Configuration
|
|
343
|
+
|
|
344
|
+
`mcptrustchecker.config.json` (auto-discovered) overrides any default — see [examples/mcptrustchecker.config.json](examples/mcptrustchecker.config.json). Two adoption features worth calling out:
|
|
345
|
+
|
|
346
|
+
- **Baseline / suppressions** — waive a specific finding on a specific tool with a justification (`suppress: [{ rule, tool?, field?, reason }]`), or drop a standalone `.mtcignore` JSON array in your repo. Location-scoped waivers keep CI green without silencing a rule everywhere.
|
|
347
|
+
- **Policy-as-code** — declare what "acceptable" means once and gate every scan on it: `policy: { minGrade, maxCapability, denyRules, denyCapabilities }`. Violations print and fail the run.
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
## What MCP Trust Checker is *not*
|
|
352
|
+
|
|
353
|
+
- Not a runtime proxy/firewall — it analyzes the declared surface, it doesn't sit in the request path.
|
|
354
|
+
- Static analysis proves a toxic-flow **primitive exists**, not that a chain **will execute**.
|
|
355
|
+
- It does not test server-side auth/authorization (noted, not claimed).
|
|
356
|
+
- Heuristics have false positives; every finding carries a `confidence`, and grade gates fire only on `confirmed` findings — so a guess never forces a grade. [Report false positives](.github/ISSUE_TEMPLATE/false-positive.md); the rules are open.
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
360
|
+
## Documentation
|
|
361
|
+
|
|
362
|
+
- **[docs/methodology.md](docs/methodology.md)** — every pipeline stage in depth
|
|
363
|
+
- **[docs/scoring.md](docs/scoring.md)** — the scoring model & reproducibility contract
|
|
364
|
+
- **[docs/rules.md](docs/rules.md)** — the complete 74-rule catalogue
|
|
365
|
+
- **[docs/architecture.md](docs/architecture.md)** — code layout & how to extend
|
|
366
|
+
- **[docs/ci-integration.md](docs/ci-integration.md)** — Action, SARIF, baselines
|
|
367
|
+
- **[SECURITY.md](SECURITY.md)** · **[CONTRIBUTING.md](CONTRIBUTING.md)**
|
|
368
|
+
|
|
369
|
+
---
|
|
370
|
+
|
|
371
|
+
## Contributing
|
|
372
|
+
|
|
373
|
+
Issues, rules, and PRs welcome — see [CONTRIBUTING.md](CONTRIBUTING.md). New detectors implement one small interface; new threat data is a plain, reviewable data file with a test.
|
|
374
|
+
|
|
375
|
+
## License
|
|
376
|
+
|
|
377
|
+
[MIT](LICENSE) © Illia Haidar · [mcptrustchecker.com](https://mcptrustchecker.com) · [support@mcptrustchecker.com](mailto:support@mcptrustchecker.com).
|
|
378
|
+
|
|
379
|
+
Created and maintained by **[Illia Haidar](https://github.com/illiahaidar)** — author of the Capability-Flow Trust Model.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/*! MCP Trust Checker · https://mcptrustchecker.com · support@mcptrustchecker.com · © 2026 Illia Haidar · MIT */
|
|
2
|
+
/**
|
|
3
|
+
* Parse an MCP client config (`claude_desktop_config.json`, Cursor, Windsurf,
|
|
4
|
+
* VS Code, Continue, …) into a list of server specs. All these clients share
|
|
5
|
+
* the `mcpServers` map shape.
|
|
6
|
+
*/
|
|
7
|
+
export interface ConfigServerSpec {
|
|
8
|
+
id: string;
|
|
9
|
+
transport: 'stdio' | 'http';
|
|
10
|
+
command?: string;
|
|
11
|
+
args?: string[];
|
|
12
|
+
env?: Record<string, string>;
|
|
13
|
+
url?: string;
|
|
14
|
+
}
|
|
15
|
+
/** Detect whether a parsed JSON object is a client config (vs a manifest). */
|
|
16
|
+
export declare function isClientConfig(json: unknown): boolean;
|
|
17
|
+
/** Extract server specs from a client config object. */
|
|
18
|
+
export declare function parseClientConfig(json: unknown): ConfigServerSpec[];
|
|
19
|
+
/**
|
|
20
|
+
* Expand `${VAR}` / `$VAR` references using the given env (typically the
|
|
21
|
+
* server entry's own `env` block merged over process.env) — exactly what MCP
|
|
22
|
+
* clients do before spawning, so an auth header like `Authorization:${AUTH}`
|
|
23
|
+
* resolves. Unknown variables are left untouched rather than silently blanked.
|
|
24
|
+
*/
|
|
25
|
+
export declare function expandVars(input: string, env: Record<string, string | undefined>): string;
|
|
26
|
+
/** Strip a trailing `@version` from a package spec, preserving the scope. */
|
|
27
|
+
export declare function stripVersion(spec: string): string;
|
|
28
|
+
/** Split a package spec into its name and version token (if any). */
|
|
29
|
+
export declare function versionOf(spec: string): string | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* Best-effort: pull the npm/PyPI package name (and version token) out of an
|
|
32
|
+
* `npx`/`uvx`-style stdio spec so the supply-chain stage has something to check
|
|
33
|
+
* without running the server.
|
|
34
|
+
*/
|
|
35
|
+
export declare function packageSpecFromStdio(command: string, args?: string[]): {
|
|
36
|
+
name: string;
|
|
37
|
+
version?: string;
|
|
38
|
+
} | undefined;
|
|
39
|
+
/** Package name only (back-compat convenience). */
|
|
40
|
+
export declare function packageFromStdio(command: string, args?: string[]): string | undefined;
|
|
41
|
+
/**
|
|
42
|
+
* Redact secrets from a spawn arg vector before storing/printing them — a
|
|
43
|
+
* security scanner must not leak the user's own tokens in its report. Handles
|
|
44
|
+
* inline `Authorization:Bearer …` values, header flags (secret value only), and
|
|
45
|
+
* bare values following a dedicated secret flag (redacted regardless of length).
|
|
46
|
+
*/
|
|
47
|
+
export declare function redactSensitiveArgs(args: string[]): string[];
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/*! MCP Trust Checker · https://mcptrustchecker.com · support@mcptrustchecker.com · © 2026 Illia Haidar · MIT */
|
|
2
|
+
/**
|
|
3
|
+
* Parse an MCP client config (`claude_desktop_config.json`, Cursor, Windsurf,
|
|
4
|
+
* VS Code, Continue, …) into a list of server specs. All these clients share
|
|
5
|
+
* the `mcpServers` map shape.
|
|
6
|
+
*/
|
|
7
|
+
/** Detect whether a parsed JSON object is a client config (vs a manifest). */
|
|
8
|
+
export function isClientConfig(json) {
|
|
9
|
+
return Boolean(json && typeof json === 'object' && ('mcpServers' in json || 'servers' in json));
|
|
10
|
+
}
|
|
11
|
+
/** Extract server specs from a client config object. */
|
|
12
|
+
export function parseClientConfig(json) {
|
|
13
|
+
const root = (json ?? {});
|
|
14
|
+
const servers = (root.mcpServers ?? root.servers ?? {});
|
|
15
|
+
const specs = [];
|
|
16
|
+
for (const [id, raw] of Object.entries(servers)) {
|
|
17
|
+
if (!raw || typeof raw !== 'object')
|
|
18
|
+
continue;
|
|
19
|
+
const s = raw;
|
|
20
|
+
const url = typeof s.url === 'string' ? s.url : undefined;
|
|
21
|
+
if (url) {
|
|
22
|
+
specs.push({ id, transport: 'http', url });
|
|
23
|
+
}
|
|
24
|
+
else if (typeof s.command === 'string') {
|
|
25
|
+
specs.push({
|
|
26
|
+
id,
|
|
27
|
+
transport: 'stdio',
|
|
28
|
+
command: s.command,
|
|
29
|
+
args: Array.isArray(s.args) ? s.args : [],
|
|
30
|
+
env: s.env ?? undefined,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return specs;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Expand `${VAR}` / `$VAR` references using the given env (typically the
|
|
38
|
+
* server entry's own `env` block merged over process.env) — exactly what MCP
|
|
39
|
+
* clients do before spawning, so an auth header like `Authorization:${AUTH}`
|
|
40
|
+
* resolves. Unknown variables are left untouched rather than silently blanked.
|
|
41
|
+
*/
|
|
42
|
+
export function expandVars(input, env) {
|
|
43
|
+
return input.replace(/\$\{(\w+)\}|\$(\w+)/g, (match, braced, bare) => {
|
|
44
|
+
const name = braced ?? bare;
|
|
45
|
+
const val = env[name];
|
|
46
|
+
return val === undefined ? match : val;
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
/** Strip a trailing `@version` from a package spec, preserving the scope. */
|
|
50
|
+
export function stripVersion(spec) {
|
|
51
|
+
if (spec.startsWith('@')) {
|
|
52
|
+
const slash = spec.indexOf('/');
|
|
53
|
+
if (slash < 0)
|
|
54
|
+
return spec;
|
|
55
|
+
const rest = spec.slice(slash + 1);
|
|
56
|
+
const at = rest.indexOf('@');
|
|
57
|
+
return at >= 0 ? spec.slice(0, slash + 1 + at) : spec;
|
|
58
|
+
}
|
|
59
|
+
const at = spec.indexOf('@');
|
|
60
|
+
return at >= 0 ? spec.slice(0, at) : spec;
|
|
61
|
+
}
|
|
62
|
+
/** Split a package spec into its name and version token (if any). */
|
|
63
|
+
export function versionOf(spec) {
|
|
64
|
+
if (spec.startsWith('@')) {
|
|
65
|
+
const slash = spec.indexOf('/');
|
|
66
|
+
if (slash < 0)
|
|
67
|
+
return undefined;
|
|
68
|
+
const at = spec.slice(slash + 1).indexOf('@');
|
|
69
|
+
return at >= 0 ? spec.slice(slash + 1 + at + 1) : undefined;
|
|
70
|
+
}
|
|
71
|
+
const at = spec.indexOf('@');
|
|
72
|
+
return at >= 0 ? spec.slice(at + 1) : undefined;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Best-effort: pull the npm/PyPI package name (and version token) out of an
|
|
76
|
+
* `npx`/`uvx`-style stdio spec so the supply-chain stage has something to check
|
|
77
|
+
* without running the server.
|
|
78
|
+
*/
|
|
79
|
+
export function packageSpecFromStdio(command, args = []) {
|
|
80
|
+
const base = command.replace(/\\/g, '/');
|
|
81
|
+
const runner = base.slice(base.lastIndexOf('/') + 1).replace(/\.(cmd|exe)$/, '');
|
|
82
|
+
const skip = new Set(['-y', '--yes', 'dlx', 'exec', '-q', '--quiet', 'run']);
|
|
83
|
+
if (['npx', 'bunx', 'pnpm', 'yarn', 'uvx', 'pipx'].includes(runner)) {
|
|
84
|
+
for (const a of args) {
|
|
85
|
+
if (a.startsWith('-') || skip.has(a))
|
|
86
|
+
continue;
|
|
87
|
+
return { name: stripVersion(a), version: versionOf(a) };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
/** Package name only (back-compat convenience). */
|
|
93
|
+
export function packageFromStdio(command, args = []) {
|
|
94
|
+
return packageSpecFromStdio(command, args)?.name;
|
|
95
|
+
}
|
|
96
|
+
function redactValue(a) {
|
|
97
|
+
return a
|
|
98
|
+
.replace(/(bearer\s+)[A-Za-z0-9._~+/=-]+/gi, '$1<redacted>')
|
|
99
|
+
// Redact the WHOLE Authorization value (Basic <base64>, Digest …), not just
|
|
100
|
+
// the first token — the credential lives after the scheme word.
|
|
101
|
+
.replace(/(authorization\s*[:=]\s*)(?!bearer\b).+$/gi, '$1<redacted>')
|
|
102
|
+
.replace(/(cookie\s*[:=]\s*).+$/gi, '$1<redacted>')
|
|
103
|
+
.replace(/((?:api[_-]?key|token|secret|password|access[_-]?token)\s*[:=]\s*)\S+/gi, '$1<redacted>');
|
|
104
|
+
}
|
|
105
|
+
// Flags whose FOLLOWING arg is the secret itself → always redact (any length).
|
|
106
|
+
const VALUE_IS_SECRET = /^(--token|--api-?key|--auth|--authorization|--secret|--password|--pass|--bearer|--key|--pat)$/i;
|
|
107
|
+
// Header flags whose following arg is "Name: value" → redact only a secret value.
|
|
108
|
+
const HEADER_FLAG = /^(--header|-h|--headers)$/i;
|
|
109
|
+
/**
|
|
110
|
+
* Redact secrets from a spawn arg vector before storing/printing them — a
|
|
111
|
+
* security scanner must not leak the user's own tokens in its report. Handles
|
|
112
|
+
* inline `Authorization:Bearer …` values, header flags (secret value only), and
|
|
113
|
+
* bare values following a dedicated secret flag (redacted regardless of length).
|
|
114
|
+
*/
|
|
115
|
+
export function redactSensitiveArgs(args) {
|
|
116
|
+
return args.map((a, i) => {
|
|
117
|
+
const prev = args[i - 1] ?? '';
|
|
118
|
+
if (VALUE_IS_SECRET.test(prev))
|
|
119
|
+
return '<redacted>';
|
|
120
|
+
if (HEADER_FLAG.test(prev))
|
|
121
|
+
return redactValue(a); // header value only if it looks secret
|
|
122
|
+
return redactValue(a); // inline secret patterns anywhere
|
|
123
|
+
});
|
|
124
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/*! MCP Trust Checker · https://mcptrustchecker.com · support@mcptrustchecker.com · © 2026 Illia Haidar · MIT */
|
|
2
|
+
/**
|
|
3
|
+
* Zero-config discovery of installed MCP client configs, so `mcptrustchecker` with no
|
|
4
|
+
* arguments "just works" — the same one-command onboarding as the popular
|
|
5
|
+
* scanners. All the supported clients share the `mcpServers`/`servers` map.
|
|
6
|
+
*/
|
|
7
|
+
export interface DiscoveredConfig {
|
|
8
|
+
client: string;
|
|
9
|
+
path: string;
|
|
10
|
+
}
|
|
11
|
+
/** Return config files that exist AND parse as an MCP client config. */
|
|
12
|
+
export declare function discoverClientConfigs(): DiscoveredConfig[];
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/*! MCP Trust Checker · https://mcptrustchecker.com · support@mcptrustchecker.com · © 2026 Illia Haidar · MIT */
|
|
2
|
+
/**
|
|
3
|
+
* Zero-config discovery of installed MCP client configs, so `mcptrustchecker` with no
|
|
4
|
+
* arguments "just works" — the same one-command onboarding as the popular
|
|
5
|
+
* scanners. All the supported clients share the `mcpServers`/`servers` map.
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
8
|
+
import { homedir } from 'node:os';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
import { isClientConfig } from './clientConfig.js';
|
|
11
|
+
/** Candidate config locations across the common MCP clients and OSes. */
|
|
12
|
+
function candidates() {
|
|
13
|
+
const home = homedir();
|
|
14
|
+
const cwd = process.cwd();
|
|
15
|
+
const appData = process.env.APPDATA;
|
|
16
|
+
const list = [
|
|
17
|
+
// Claude Desktop
|
|
18
|
+
{ client: 'Claude Desktop', path: join(home, 'Library/Application Support/Claude/claude_desktop_config.json') },
|
|
19
|
+
{ client: 'Claude Desktop', path: join(home, '.config/Claude/claude_desktop_config.json') },
|
|
20
|
+
appData ? { client: 'Claude Desktop', path: join(appData, 'Claude/claude_desktop_config.json') } : null,
|
|
21
|
+
// Claude Code
|
|
22
|
+
{ client: 'Claude Code', path: join(home, '.claude.json') },
|
|
23
|
+
// Cursor
|
|
24
|
+
{ client: 'Cursor', path: join(home, '.cursor/mcp.json') },
|
|
25
|
+
{ client: 'Cursor (project)', path: join(cwd, '.cursor/mcp.json') },
|
|
26
|
+
// Windsurf
|
|
27
|
+
{ client: 'Windsurf', path: join(home, '.codeium/windsurf/mcp_config.json') },
|
|
28
|
+
// Continue
|
|
29
|
+
{ client: 'Continue', path: join(home, '.continue/config.json') },
|
|
30
|
+
// VS Code (project) & generic project configs
|
|
31
|
+
{ client: 'VS Code (project)', path: join(cwd, '.vscode/mcp.json') },
|
|
32
|
+
{ client: 'project', path: join(cwd, '.mcp.json') },
|
|
33
|
+
];
|
|
34
|
+
return list.filter((c) => c !== null);
|
|
35
|
+
}
|
|
36
|
+
/** Return config files that exist AND parse as an MCP client config. */
|
|
37
|
+
export function discoverClientConfigs() {
|
|
38
|
+
const found = [];
|
|
39
|
+
const seen = new Set();
|
|
40
|
+
for (const c of candidates()) {
|
|
41
|
+
if (seen.has(c.path) || !existsSync(c.path))
|
|
42
|
+
continue;
|
|
43
|
+
seen.add(c.path);
|
|
44
|
+
try {
|
|
45
|
+
if (isClientConfig(JSON.parse(readFileSync(c.path, 'utf8'))))
|
|
46
|
+
found.push(c);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
/* unreadable / not JSON — skip */
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return found;
|
|
53
|
+
}
|