mcp-proof 0.3.6 → 0.3.8
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 +31 -2
- package/bin/mcp-proof.js +96 -25
- package/engine.js +64 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -28,6 +28,15 @@ npx mcp-proof --config ~/.config/Claude/claude_desktop_config.json
|
|
|
28
28
|
# Scan a Claude SKILL.md for hidden attack instructions
|
|
29
29
|
npx mcp-proof --skill ~/.claude/skills/example/SKILL.md
|
|
30
30
|
|
|
31
|
+
# Inspect an exact tools/call payload before execution
|
|
32
|
+
cat call.json | npx mcp-proof --execute --json
|
|
33
|
+
|
|
34
|
+
# Create a shareable Trust Card for a PR or README
|
|
35
|
+
npx mcp-proof tools.json --markdown > MCP_TRUST.md
|
|
36
|
+
|
|
37
|
+
# Emit SARIF for GitHub code scanning
|
|
38
|
+
npx mcp-proof tools.json --sarif > results.sarif
|
|
39
|
+
|
|
31
40
|
# Or pipe stdin
|
|
32
41
|
cat tools.json | npx mcp-proof --transport http --bind 0.0.0.0:3001
|
|
33
42
|
|
|
@@ -125,6 +134,10 @@ Options:
|
|
|
125
134
|
--stdio <cmd> launch a real stdio MCP server, call tools/list, scan returned tools
|
|
126
135
|
--config <path> scan every stdio server in an MCP client config with mcpServers
|
|
127
136
|
--skill <path> scan a Claude SKILL.md (educational/warning context aware)
|
|
137
|
+
--execute [<path>] inspect a tools/call payload before execution
|
|
138
|
+
--markdown emit a shareable Markdown Trust Card
|
|
139
|
+
--trust-card alias for --markdown
|
|
140
|
+
--sarif emit SARIF 2.1.0 for GitHub code scanning
|
|
128
141
|
--live confirm npm publisher on registry.npmjs.org (one network call)
|
|
129
142
|
--timeout <ms> MCP launch/protocol timeout per server (default: 15000)
|
|
130
143
|
--source <s> npm:@scope/pkg, git+https://..., file://...
|
|
@@ -140,9 +153,20 @@ Exit codes: 0 allow · 1 warn · 2 block · 64 usage · 65 parse error
|
|
|
140
153
|
|
|
141
154
|
The non-zero exit code on `warn` / `block` makes this drop-in for CI: pin in your `package.json` and any tool poisoning fails the pipeline.
|
|
142
155
|
|
|
156
|
+
## Trust Cards and GitHub SARIF
|
|
157
|
+
|
|
158
|
+
Every scan can produce a portable review artifact without uploading server metadata:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
npx mcp-proof --stdio "npx -y @modelcontextprotocol/server-filesystem /tmp" --markdown > MCP_TRUST.md
|
|
162
|
+
npx mcp-proof ./tools.json --sarif > mcp-proof-results.sarif
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
`MCP_TRUST.md` is suitable for a pull request or repository README. SARIF can be uploaded to GitHub code scanning by a workflow or the included `action.yml`. The output is generated locally from the same verdict and includes the package version and findings; it is not a cryptographic authorization receipt.
|
|
166
|
+
|
|
143
167
|
## Real MCP scanning
|
|
144
168
|
|
|
145
|
-
`
|
|
169
|
+
`mcp-proof` can scan a live stdio MCP server, not just a saved JSON manifest. The CLI launches the server locally, performs the MCP `initialize` handshake, calls `tools/list`, and scans the returned tool metadata. It uses a timeout cap so a broken or malicious server cannot hang CI forever.
|
|
146
170
|
|
|
147
171
|
```bash
|
|
148
172
|
npx mcp-proof --stdio "npx -y @modelcontextprotocol/server-filesystem /tmp"
|
|
@@ -170,7 +194,7 @@ The scanner only supports stdio MCP servers in this release. HTTP/SSE runtime di
|
|
|
170
194
|
## Programmatic API
|
|
171
195
|
|
|
172
196
|
```js
|
|
173
|
-
const { evaluate, scanSkillManifest } = require('mcp-proof');
|
|
197
|
+
const { evaluate, evaluateCall, scanSkillManifest } = require('mcp-proof');
|
|
174
198
|
|
|
175
199
|
const result = evaluate({
|
|
176
200
|
source: 'npm:@modelcontextprotocol/server-filesystem',
|
|
@@ -184,6 +208,10 @@ console.log(result.composite); // 0.0 – 10.0
|
|
|
184
208
|
console.log(result.reasons); // human-readable explanations
|
|
185
209
|
console.log(result.toolPoisoning.perTool); // per-tool findings
|
|
186
210
|
|
|
211
|
+
// Inspect an exact tool call before execution (does not execute or authorize it)
|
|
212
|
+
const call = evaluateCall({ tool: 'read_file', arguments: { path: './README.md' } });
|
|
213
|
+
console.log(call.decision);
|
|
214
|
+
|
|
187
215
|
// Scan a Claude SKILL.md
|
|
188
216
|
const skill = scanSkillManifest(fs.readFileSync('./SKILL.md', 'utf8'));
|
|
189
217
|
console.log(skill.decision);
|
|
@@ -194,6 +222,7 @@ console.log(skill.decision);
|
|
|
194
222
|
- **Publisher verification is lexical only by default.** npm scope match + Levenshtein typosquat. Pass `--live` to confirm npm packages against `registry.npmjs.org` with one network call. *No GitHub-API lookup, no signature verification.*
|
|
195
223
|
- **No source-tree scan.** The full VyriAI gateway also runs a static scanner over the package source for `child_process`, postinstall scripts, obfuscation. That step needs the source unpacked locally and isn't shipped in this CLI.
|
|
196
224
|
- **No dependency CVE check.** The full gateway wraps `npm audit`; this CLI doesn't shell out.
|
|
225
|
+
- **Limited runtime-payload inspection.** `--execute` scans a supplied `tools/call` name and arguments before execution; it does not execute the call, enforce an authorization policy, verify identity, or observe server runtime behavior.
|
|
197
226
|
- **Stdio discovery only.** `--stdio` / `--config` launches local stdio MCP servers and calls `tools/list`. It does not yet connect to HTTP/SSE MCP servers.
|
|
198
227
|
- **Skill context is keyword-based.** The skill detector looks for warning/example/demonstration framing around sensitive signals. A well-disguised attack without that framing may not downgrade.
|
|
199
228
|
- **Local payload cap.** The CLI refuses inputs over 5 MB by default to avoid accidental memory blowups in CI. Override with `MCP_SCAN_MAX_BYTES=20mb` if you have an unusually large tools manifest.
|
package/bin/mcp-proof.js
CHANGED
|
@@ -23,7 +23,7 @@ const fs = require('fs');
|
|
|
23
23
|
const os = require('os');
|
|
24
24
|
const path = require('path');
|
|
25
25
|
const { spawn } = require('child_process');
|
|
26
|
-
const { evaluate, scanSkillManifest, verifyPublisherAsync } = require('../engine.js');
|
|
26
|
+
const { evaluate, evaluateCall, scanSkillManifest, verifyPublisherAsync } = require('../engine.js');
|
|
27
27
|
|
|
28
28
|
const DEFAULT_MAX_INPUT_BYTES = 5 * 1024 * 1024;
|
|
29
29
|
const DEFAULT_TIMEOUT_MS = 15000;
|
|
@@ -43,13 +43,18 @@ Usage:
|
|
|
43
43
|
mcp-proof --stdio "<command to launch MCP server>"
|
|
44
44
|
mcp-proof --config <claude-or-cursor-config.json>
|
|
45
45
|
mcp-proof --skill <SKILL.md>
|
|
46
|
+
mcp-proof --execute [<call.json>] runtime authorization of a tools/call payload
|
|
46
47
|
|
|
47
|
-
If <file.json> is omitted and --stdio/--config/--skill are not used, JSON is read from stdin.
|
|
48
|
+
If <file.json> is omitted and --stdio/--config/--skill/--execute are not used, JSON is read from stdin.
|
|
48
49
|
|
|
49
50
|
Options:
|
|
50
51
|
--stdio <cmd> launch a real stdio MCP server, call tools/list, scan returned tools
|
|
51
52
|
--config <path> scan every stdio server in an MCP client config with mcpServers
|
|
52
53
|
--skill <path> scan a Claude SKILL.md (educational/warning context aware)
|
|
54
|
+
--execute [<path>] inspect a tools/call payload before execution (reads stdin if path omitted)
|
|
55
|
+
--markdown emit a shareable Markdown Trust Card
|
|
56
|
+
--trust-card alias for --markdown
|
|
57
|
+
--sarif emit SARIF 2.1.0 for GitHub code scanning
|
|
53
58
|
--live confirm npm publisher against registry.npmjs.org (one network call)
|
|
54
59
|
--timeout <ms> MCP launch/protocol timeout per server (default: 15000)
|
|
55
60
|
--source <s> npm:@scope/pkg, git+https://..., or file://... (drives publisher verification)
|
|
@@ -89,9 +94,9 @@ async function main() {
|
|
|
89
94
|
if (args.selfTest) {
|
|
90
95
|
return runSelfTest(args);
|
|
91
96
|
}
|
|
92
|
-
const exclusiveModes = [args.stdio, args.config, args.skill].filter(Boolean).length;
|
|
97
|
+
const exclusiveModes = [args.stdio, args.config, args.skill, args.execute].filter(Boolean).length;
|
|
93
98
|
if (exclusiveModes > 1) {
|
|
94
|
-
process.stderr.write('mcp-proof: use one of --stdio, --config, --skill at a time.\n');
|
|
99
|
+
process.stderr.write('mcp-proof: use one of --stdio, --config, --skill, --execute at a time.\n');
|
|
95
100
|
process.exit(64);
|
|
96
101
|
}
|
|
97
102
|
|
|
@@ -103,11 +108,7 @@ async function main() {
|
|
|
103
108
|
process.stderr.write(`mcp-proof: ${err.message}\n`);
|
|
104
109
|
process.exit(err.exitCode || 65);
|
|
105
110
|
}
|
|
106
|
-
|
|
107
|
-
process.stdout.write(JSON.stringify(output, null, 2) + '\n');
|
|
108
|
-
} else {
|
|
109
|
-
renderMulti(output);
|
|
110
|
-
}
|
|
111
|
+
emitOutput(output, args);
|
|
111
112
|
process.exit(exitCodeForDecision(output.decision));
|
|
112
113
|
}
|
|
113
114
|
|
|
@@ -127,11 +128,7 @@ async function main() {
|
|
|
127
128
|
process.stderr.write(`mcp-proof: ${err.message}\n`);
|
|
128
129
|
process.exit(err.exitCode || 65);
|
|
129
130
|
}
|
|
130
|
-
|
|
131
|
-
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
132
|
-
} else {
|
|
133
|
-
renderHumanReadable(result, result.tool_count || 0, result.name || '');
|
|
134
|
-
}
|
|
131
|
+
emitOutput(result, args);
|
|
135
132
|
process.exit(exitCodeForDecision(result.decision));
|
|
136
133
|
}
|
|
137
134
|
|
|
@@ -143,11 +140,19 @@ async function main() {
|
|
|
143
140
|
process.stderr.write(`mcp-proof: ${err.message}\n`);
|
|
144
141
|
process.exit(err.exitCode || 65);
|
|
145
142
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
143
|
+
emitOutput(result, args);
|
|
144
|
+
process.exit(exitCodeForDecision(result.decision));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (args.execute) {
|
|
148
|
+
let result;
|
|
149
|
+
try {
|
|
150
|
+
result = runExecute(args);
|
|
151
|
+
} catch (err) {
|
|
152
|
+
process.stderr.write(`mcp-proof: ${err.message}\n`);
|
|
153
|
+
process.exit(err.exitCode || 65);
|
|
150
154
|
}
|
|
155
|
+
emitOutput(result, args);
|
|
151
156
|
process.exit(exitCodeForDecision(result.decision));
|
|
152
157
|
}
|
|
153
158
|
|
|
@@ -193,11 +198,7 @@ async function main() {
|
|
|
193
198
|
result.source = args.source || (parsed.source || '');
|
|
194
199
|
result.tool_count = tools.length;
|
|
195
200
|
|
|
196
|
-
|
|
197
|
-
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
198
|
-
} else {
|
|
199
|
-
renderHumanReadable(result, tools.length, result.name);
|
|
200
|
-
}
|
|
201
|
+
emitOutput(result, args);
|
|
201
202
|
|
|
202
203
|
process.exit(result.decision === 'allow' ? 0 : result.decision === 'warn' ? 1 : 2);
|
|
203
204
|
}
|
|
@@ -206,14 +207,16 @@ function parseArgs(argv) {
|
|
|
206
207
|
const out = {
|
|
207
208
|
source: '', transport: '', bind: '', json: false, selfTest: false,
|
|
208
209
|
help: false, version: false, file: '', stdio: '', config: '', skill: '',
|
|
209
|
-
live: false, timeoutMs: DEFAULT_TIMEOUT_MS,
|
|
210
|
+
execute: false, format: 'human', live: false, timeoutMs: DEFAULT_TIMEOUT_MS,
|
|
210
211
|
};
|
|
211
212
|
for (let i = 0; i < argv.length; i++) {
|
|
212
213
|
const a = argv[i];
|
|
213
214
|
switch (a) {
|
|
214
215
|
case '-h': case '--help': out.help = true; break;
|
|
215
216
|
case '-V': case '--version': out.version = true; break;
|
|
216
|
-
case '--json': out.json = true; break;
|
|
217
|
+
case '--json': out.json = true; out.format = 'json'; break;
|
|
218
|
+
case '--markdown': case '--trust-card': out.format = 'markdown'; break;
|
|
219
|
+
case '--sarif': out.format = 'sarif'; break;
|
|
217
220
|
case '--self-test': out.selfTest = true; break;
|
|
218
221
|
case '--source': out.source = argv[++i] || ''; break;
|
|
219
222
|
case '--transport': out.transport = argv[++i] || ''; break;
|
|
@@ -221,6 +224,10 @@ function parseArgs(argv) {
|
|
|
221
224
|
case '--stdio': out.stdio = argv[++i] || ''; break;
|
|
222
225
|
case '--config': out.config = expandHome(argv[++i] || ''); break;
|
|
223
226
|
case '--skill': out.skill = expandHome(argv[++i] || ''); break;
|
|
227
|
+
case '--execute':
|
|
228
|
+
out.execute = true;
|
|
229
|
+
if (argv[i + 1] && !argv[i + 1].startsWith('-')) out.file = argv[++i];
|
|
230
|
+
break;
|
|
224
231
|
case '--live': out.live = true; break;
|
|
225
232
|
case '--timeout': out.timeoutMs = parseTimeout(argv[++i] || ''); break;
|
|
226
233
|
default:
|
|
@@ -577,6 +584,70 @@ function worstDecision(decisions) {
|
|
|
577
584
|
return 'allow';
|
|
578
585
|
}
|
|
579
586
|
|
|
587
|
+
function emitOutput(result, args) {
|
|
588
|
+
if (args.format === 'json') {
|
|
589
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
590
|
+
} else if (args.format === 'sarif') {
|
|
591
|
+
process.stdout.write(JSON.stringify(toSarif(result), null, 2) + '\n');
|
|
592
|
+
} else if (args.format === 'markdown') {
|
|
593
|
+
process.stdout.write(toTrustCard(result) + '\n');
|
|
594
|
+
} else if (result.results) {
|
|
595
|
+
renderMulti(result);
|
|
596
|
+
} else {
|
|
597
|
+
renderHumanReadable(result, result.tool_count || 0, result.name || result.tool || '');
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
function toTrustCard(r) {
|
|
602
|
+
const verdict = String(r.decision || 'unknown').toUpperCase();
|
|
603
|
+
const source = r.source || r.name || 'stdin';
|
|
604
|
+
const findings = r.toolPoisoning && r.toolPoisoning.perTool
|
|
605
|
+
? r.toolPoisoning.perTool.flatMap(t => (t.patterns || []).map(p => `- **${p.name}** (${p.severity}) — ${p.reason}`))
|
|
606
|
+
: r.call_scan && r.call_scan.detected ? [`- **runtime_payload** (${r.call_scan.severity}) — ${r.call_scan.reason}`] : [];
|
|
607
|
+
const hash = r.manifestHash || r.payloadHash || 'not emitted';
|
|
608
|
+
return [
|
|
609
|
+
'# MCP Trust Card', '',
|
|
610
|
+
`**Verdict: ${verdict}**`, '',
|
|
611
|
+
'| Field | Value |', '|---|---|',
|
|
612
|
+
`| Source | ${String(source).replace(/\|/g, '\\|')} |`,
|
|
613
|
+
`| Tools scanned | ${r.tool_count ?? 'n/a'} |`,
|
|
614
|
+
`| Trust score | ${r.composite ?? 'n/a'} / 10 |`,
|
|
615
|
+
`| Findings | ${findings.length} |`,
|
|
616
|
+
`| Evidence hash | ${hash} |`,
|
|
617
|
+
`| mcp-proof | ${require('../package.json').version} |`, '',
|
|
618
|
+
'## Findings', '', findings.length ? findings.join('\n') : '- No actionable findings.', '',
|
|
619
|
+
'> Reproduce locally with `npx mcp-proof --json`.'
|
|
620
|
+
].join('\n');
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
function toSarif(r) {
|
|
624
|
+
const results = [];
|
|
625
|
+
const findings = r.toolPoisoning && r.toolPoisoning.perTool
|
|
626
|
+
? r.toolPoisoning.perTool.flatMap(t => (t.patterns || []).map(p => ({ ruleId: p.name, level: p.severity === 'critical' || p.severity === 'high' ? 'error' : 'warning', message: `${p.reason} (tool: ${t.tool})` })))
|
|
627
|
+
: r.call_scan && r.call_scan.detected ? [{ ruleId: 'runtime_payload', level: r.call_scan.severity === 'critical' ? 'error' : 'warning', message: r.call_scan.reason }] : [];
|
|
628
|
+
for (const f of findings) results.push({ ruleId: f.ruleId, level: f.level, message: { text: f.message } });
|
|
629
|
+
return { version: '2.1.0', $schema: 'https://json.schemastore.org/sarif-2.1.0.json', runs: [{ tool: { driver: { name: 'mcp-proof', version: require('../package.json').version, rules: [...new Map(findings.map(f => [f.ruleId, { id: f.ruleId, shortDescription: { text: f.ruleId } }])).values()] } }, results }] };
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
function runExecute(args) {
|
|
633
|
+
const raw = args.file ? readFileLimited(args.file) : readStdinLimited();
|
|
634
|
+
if (!raw.trim()) {
|
|
635
|
+
throw new Error('empty input. Pipe a tools/call JSON or pass a path.');
|
|
636
|
+
}
|
|
637
|
+
let parsed;
|
|
638
|
+
try {
|
|
639
|
+
parsed = JSON.parse(raw);
|
|
640
|
+
} catch (err) {
|
|
641
|
+
const e = new Error(`invalid JSON: ${err.message}`);
|
|
642
|
+
e.exitCode = 65;
|
|
643
|
+
throw e;
|
|
644
|
+
}
|
|
645
|
+
const result = evaluateCall(parsed);
|
|
646
|
+
result.mode = 'execute';
|
|
647
|
+
result.name = args.file || 'stdin';
|
|
648
|
+
return result;
|
|
649
|
+
}
|
|
650
|
+
|
|
580
651
|
function exitCodeForDecision(d) {
|
|
581
652
|
return d === 'allow' ? 0 : d === 'warn' ? 1 : 2;
|
|
582
653
|
}
|
package/engine.js
CHANGED
|
@@ -634,4 +634,67 @@ function evaluate(req) {
|
|
|
634
634
|
|
|
635
635
|
function round1(v) { return Math.round(v * 10) / 10; }
|
|
636
636
|
|
|
637
|
-
|
|
637
|
+
// ── Runtime tool-call authorization ──────────────────────────────────────────
|
|
638
|
+
//
|
|
639
|
+
// evaluateCall answers the execution-time question: given the exact tool and
|
|
640
|
+
// the exact arguments, is this call still authorized? It applies the same
|
|
641
|
+
// signatures used for static tool manifest scanning to the live payload.
|
|
642
|
+
|
|
643
|
+
function flattenArgs(args) {
|
|
644
|
+
const values = [];
|
|
645
|
+
function walk(v) {
|
|
646
|
+
if (v == null) return;
|
|
647
|
+
if (typeof v === 'string') values.push(v);
|
|
648
|
+
else if (Array.isArray(v)) v.forEach(walk);
|
|
649
|
+
else if (typeof v === 'object') Object.values(v).forEach(walk);
|
|
650
|
+
else values.push(String(v));
|
|
651
|
+
}
|
|
652
|
+
walk(args);
|
|
653
|
+
return values.join('\n');
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
function scanCall(toolName, args) {
|
|
657
|
+
const hay = [toolName, flattenArgs(args)].join('\n');
|
|
658
|
+
for (const sig of SIGNATURES) {
|
|
659
|
+
if (sig.re.test(hay)) {
|
|
660
|
+
return { detected: true, severity: sig.sev, reason: sig.reason };
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
if (HIDDEN_CHARS.test(hay)) {
|
|
664
|
+
return { detected: true, severity: 'high', reason: 'arguments contain hidden unicode characters' };
|
|
665
|
+
}
|
|
666
|
+
const b64 = hay.match(BASE64_RE);
|
|
667
|
+
if (b64) {
|
|
668
|
+
for (const m of b64) {
|
|
669
|
+
try {
|
|
670
|
+
const decoded = Buffer.from(m, 'base64').toString('utf8');
|
|
671
|
+
if (SUSPICIOUS_DECODED.test(decoded)) {
|
|
672
|
+
return { detected: true, severity: 'high', reason: 'arguments contain base64 with suspicious decoded content' };
|
|
673
|
+
}
|
|
674
|
+
} catch (_) { /* ignore */ }
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
return { detected: false, severity: 'low', reason: 'clean' };
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
function evaluateCall(req) {
|
|
681
|
+
const start = Date.now();
|
|
682
|
+
const toolName = req.tool || (req.params && req.params.name) || '';
|
|
683
|
+
const args = req.arguments || (req.params && req.params.arguments) || {};
|
|
684
|
+
const scan = scanCall(toolName, args);
|
|
685
|
+
const score = {
|
|
686
|
+
tool: toolName,
|
|
687
|
+
call_scan: scan,
|
|
688
|
+
reasons: [],
|
|
689
|
+
decision: 'allow',
|
|
690
|
+
};
|
|
691
|
+
if (scan.detected) {
|
|
692
|
+
score.reasons.push(`${scan.severity}: ${scan.reason}`);
|
|
693
|
+
if (scan.severity === 'critical') score.decision = 'block';
|
|
694
|
+
else score.decision = 'warn';
|
|
695
|
+
}
|
|
696
|
+
score.durationMs = Date.now() - start;
|
|
697
|
+
return score;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
module.exports = { evaluate, evaluateCall, scanTools, scanCall, scanSkillManifest, verifyPublisher, verifyPublisherAsync, validateTransport, levenshtein };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-proof",
|
|
3
|
-
"version": "0.3.
|
|
4
|
-
"description": "Unbranded, free MCP (Model Context Protocol) server verifier — detects tool poisoning, typosquats, hidden unicode, base64 payloads, exposed transports, and skill-manifest attacks.
|
|
3
|
+
"version": "0.3.8",
|
|
4
|
+
"description": "Unbranded, free MCP (Model Context Protocol) server verifier with local Markdown Trust Cards and SARIF output — detects tool poisoning, typosquats, hidden unicode, base64 payloads, exposed transports, and skill-manifest attacks.",
|
|
5
5
|
"main": "engine.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"mcp-proof": "bin/mcp-proof.js"
|