runtime-mcp-scanner 0.1.1__py3-none-any.whl

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.
@@ -0,0 +1,302 @@
1
+ Metadata-Version: 2.5
2
+ Name: runtime-mcp-scanner
3
+ Version: 0.1.1
4
+ Summary: Security scanner for MCP (Model Context Protocol) servers: scan configs and source code for shell execution, unscoped file access, missing auth, exfiltration paths, and shady package provenance.
5
+ Project-URL: Homepage, https://github.com/AdamDiStefanoAI/mcp-scanner
6
+ Project-URL: Repository, https://github.com/AdamDiStefanoAI/mcp-scanner
7
+ Project-URL: Issues, https://github.com/AdamDiStefanoAI/mcp-scanner/issues
8
+ Project-URL: Changelog, https://github.com/AdamDiStefanoAI/mcp-scanner/blob/main/CHANGELOG.md
9
+ Author: Adam DiStefano
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Keywords: agentic-ai,ai-security,llm,mcp,model-context-protocol,scanner,security,static-analysis
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Intended Audience :: Information Technology
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Topic :: Security
23
+ Classifier: Topic :: Software Development :: Quality Assurance
24
+ Requires-Python: >=3.10
25
+ Requires-Dist: pyyaml>=5.4
26
+ Description-Content-Type: text/markdown
27
+
28
+ # MCP-Scanner
29
+
30
+ [![PyPI version](https://img.shields.io/pypi/v/runtime-mcp-scanner.svg)](https://pypi.org/project/runtime-mcp-scanner/)
31
+ [![Python versions](https://img.shields.io/pypi/pyversions/runtime-mcp-scanner.svg)](https://pypi.org/project/runtime-mcp-scanner/)
32
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
33
+ [![Tests](https://github.com/AdamDiStefanoAI/mcp-scanner/actions/workflows/ci.yml/badge.svg)](https://github.com/AdamDiStefanoAI/mcp-scanner/actions/workflows/ci.yml)
34
+
35
+ **Scan MCP server configs and source code for risky exposures before your agent connects to them.**
36
+
37
+ This week, researchers documented MONST3R-AI: a criminal toolkit built on stolen API keys and more than 800 fake AI-skill and MCP repositories. Malicious MCP servers are not a theory anymore. They are a distribution channel.
38
+
39
+ You would not install a random npm package without looking at it. Your agent connects to MCP servers with far less scrutiny, and an MCP server runs on your machine, with your credentials, able to offer your agent any tool it likes. A tool that runs shell commands. A tool that reads any file. A server that exfiltrates through tools that look boring on their own.
40
+
41
+ MCP-Scanner is a fast, dependency-light static check you run before you trust a server, and in CI after you do.
42
+
43
+ ## Quickstart (30 seconds)
44
+
45
+ ```bash
46
+ # No install (uv)
47
+ uvx --from runtime-mcp-scanner mcp-scan path/to/config.json
48
+
49
+ # Or with pipx
50
+ pipx install runtime-mcp-scanner
51
+ mcp-scan path/to/server.py
52
+
53
+ # Or plain pip
54
+ pip install runtime-mcp-scanner
55
+ ```
56
+
57
+ Point it at client configs (Claude Desktop, Cursor, VS Code, Claude Code), Python servers (FastMCP and the low-level SDK), or TypeScript servers (`@modelcontextprotocol/sdk`). Directories are scanned recursively. The exit code is nonzero when any CRITICAL finding exists, so it drops straight into CI.
58
+
59
+ Try it on the intentionally vulnerable examples in this repo:
60
+
61
+ ```bash
62
+ git clone https://github.com/AdamDiStefanoAI/mcp-scanner
63
+ cd mcp-scanner
64
+ mcp-scan examples/vulnerable_server.py # 15 findings, exit 1
65
+ mcp-scan examples/vulnerable_config.json # 6 findings, exit 1
66
+ mcp-scan examples/safe_server.py # clean, exit 0
67
+ ```
68
+
69
+ ## Sample output
70
+
71
+ <details>
72
+ <summary>Full output of <code>mcp-scan examples/vulnerable_server.py</code></summary>
73
+
74
+ ```text
75
+ MCP-Scanner v0.1.0
76
+
77
+ CRITICAL (3)
78
+ [MCP-001] examples/vulnerable_server.py:20
79
+ Shell execution exposed
80
+ Evidence: subprocess.run(command, shell=True, capture_output=True, text=True)
81
+ Why it matters: A tool that runs shell commands gives anyone who can steer the agent, including
82
+ content injected into its context, arbitrary code execution on the host machine.
83
+ Fix: Remove shell execution from the tool surface. Replace it with a fixed allowlist of specific
84
+ operations that take validated, typed arguments.
85
+
86
+ [MCP-002] examples/vulnerable_server.py:26
87
+ Exfiltration path (network fetch plus file write)
88
+ Evidence: server combines network fetch with local file writes
89
+ Why it matters: One server that can both read local files or fetch network resources and write or
90
+ send data out is a complete exfiltration channel. Prompt injection only has to convince the agent
91
+ to use tools that already exist.
92
+ Fix: Split network access and file writes into separate servers with least privilege, and put an
93
+ egress allowlist in front of any fetch tool.
94
+
95
+ [MCP-001] examples/vulnerable_server.py:63
96
+ Shell execution exposed
97
+ Evidence: subprocess.run(f"pip install {package}", shell=True, capture_output=True)
98
+ Why it matters: A tool that runs shell commands gives anyone who can steer the agent, including
99
+ content injected into its context, arbitrary code execution on the host machine.
100
+ Fix: Remove shell execution from the tool surface. Replace it with a fixed allowlist of specific
101
+ operations that take validated, typed arguments.
102
+
103
+ HIGH (7)
104
+ [MCP-003] examples/vulnerable_server.py:27
105
+ Unscoped filesystem access
106
+ Evidence: open(dest, "w")
107
+ Why it matters: A file tool that takes a raw path from tool input can read or overwrite anything
108
+ the server process can reach: home directories, SSH keys, cloud credentials, other projects.
109
+ Fix: Pin file tools to an explicit root directory and reject any resolved path that escapes it
110
+ (resolve the path, then check the prefix).
111
+
112
+ [MCP-003] examples/vulnerable_server.py:34
113
+ Unscoped filesystem access
114
+ Evidence: open(file_path)
115
+ Why it matters: A file tool that takes a raw path from tool input can read or overwrite anything
116
+ the server process can reach: home directories, SSH keys, cloud credentials, other projects.
117
+ Fix: Pin file tools to an explicit root directory and reject any resolved path that escapes it
118
+ (resolve the path, then check the prefix).
119
+
120
+ [MCP-004] examples/vulnerable_server.py:40
121
+ Secret access
122
+ Evidence: os.environ[var_name]
123
+ Why it matters: Tools or configs that expose environment variables, credential files, or the cloud
124
+ metadata endpoint hand live secrets to the model context, where they can be logged, leaked, or
125
+ exfiltrated.
126
+ Fix: Never expose raw environment or credential stores as tool output. Scope credentials per
127
+ server, inject only what that server needs, and block the link-local metadata address at the
128
+ network layer.
129
+
130
+ [MCP-004] examples/vulnerable_server.py:45
131
+ Secret access
132
+ Evidence: ~/.ssh/id_rsa
133
+ Why it matters: Tools or configs that expose environment variables, credential files, or the cloud
134
+ metadata endpoint hand live secrets to the model context, where they can be logged, leaked, or
135
+ exfiltrated.
136
+ Fix: Never expose raw environment or credential stores as tool output. Scope credentials per
137
+ server, inject only what that server needs, and block the link-local metadata address at the
138
+ network layer.
139
+
140
+ [MCP-010] examples/vulnerable_server.py:58
141
+ Dynamic code execution
142
+ Evidence: eval(code)
143
+ Why it matters: eval, exec, and dynamic imports inside a tool handler turn any string the model
144
+ produces into running code, bypassing every other control in the server.
145
+ Fix: Remove dynamic evaluation from tool handlers. Replace it with a dispatch table of named,
146
+ audited operations.
147
+
148
+ [MCP-003] examples/vulnerable_server.py:70
149
+ Unscoped filesystem access
150
+ Evidence: open(config_path)
151
+ Why it matters: A file tool that takes a raw path from tool input can read or overwrite anything
152
+ the server process can reach: home directories, SSH keys, cloud credentials, other projects.
153
+ Fix: Pin file tools to an explicit root directory and reject any resolved path that escapes it
154
+ (resolve the path, then check the prefix).
155
+
156
+ [MCP-005] examples/vulnerable_server.py:75
157
+ Network transport without authentication
158
+ Evidence: network transport started with no authentication check in file
159
+ Why it matters: An MCP server listening on HTTP or SSE with no authentication lets anyone who can
160
+ reach the port call its tools directly, no agent required.
161
+ Fix: Require an Authorization header or token check on every request, bind to localhost when
162
+ remote access is not needed, and put the server behind a gateway that authenticates callers.
163
+
164
+ MEDIUM (5)
165
+ [MCP-007] examples/vulnerable_server.py:18
166
+ Overbroad tool description
167
+ Evidence: description of exec_shell: "anything"
168
+ Why it matters: Tool descriptions are instructions to the model. Descriptions that advertise
169
+ unrestricted capability ("run any command", "access all files") teach the agent to use the tool as
170
+ a general escape hatch and are a hallmark of malicious MCP packages.
171
+ Fix: Describe exactly what the tool does, what it may touch, and what it must never do. Treat
172
+ description text as part of your attack surface.
173
+
174
+ [MCP-007] examples/vulnerable_server.py:33
175
+ Overbroad tool description
176
+ Evidence: description of read_file: "any file"
177
+ Why it matters: Tool descriptions are instructions to the model. Descriptions that advertise
178
+ unrestricted capability ("run any command", "access all files") teach the agent to use the tool as
179
+ a general escape hatch and are a hallmark of malicious MCP packages.
180
+ Fix: Describe exactly what the tool does, what it may touch, and what it must never do. Treat
181
+ description text as part of your attack surface.
182
+
183
+ [MCP-006] examples/vulnerable_server.py:50
184
+ Mutating tool with no input schema
185
+ Evidence: tool delete_data mutates state but its parameters have no type/schema annotations
186
+ Why it matters: A write, delete, or execute tool with an empty input schema accepts arbitrary
187
+ arguments with no validation layer between the model and the action.
188
+ Fix: Give every mutating tool a strict input schema with typed, bounded fields, and validate
189
+ arguments server-side before acting on them.
190
+
191
+ [MCP-008] examples/vulnerable_server.py:63
192
+ Self-modifying server
193
+ Evidence: pip install
194
+ Why it matters: A tool that can rewrite MCP client configs or install new packages can persist
195
+ itself, add new malicious servers, and survive removal of the original package.
196
+ Fix: MCP servers must never write to client configuration files or invoke package managers. Keep
197
+ config changes manual and reviewable.
198
+
199
+ [MCP-008] examples/vulnerable_server.py:69
200
+ Self-modifying server
201
+ Evidence: /home/user/.config/claude/claude_desktop_config.json
202
+ Why it matters: A tool that can rewrite MCP client configs or install new packages can persist
203
+ itself, add new malicious servers, and survive removal of the original package.
204
+ Fix: MCP servers must never write to client configuration files or invoke package managers. Keep
205
+ config changes manual and reviewable.
206
+
207
+ Summary: 1 file(s) scanned, 1 server(s) found. Findings: 3 critical, 7 high, 5 medium, 0 low, 0 info.
208
+ ```
209
+
210
+ </details>
211
+
212
+ ## What it checks
213
+
214
+ | Rule | Severity | What it catches |
215
+ | --- | --- | --- |
216
+ | MCP-001 | CRITICAL | Shell execution exposed: `subprocess`, `os.system`, `child_process`, or a config whose `command` is a shell |
217
+ | MCP-002 | CRITICAL | Exfiltration path: one server that can both fetch the network and write files |
218
+ | MCP-003 | HIGH | Unscoped filesystem access: file tools driven by raw input paths with no root or allowlist check |
219
+ | MCP-004 | HIGH | Secret access: environment variables exposed as tools, credential file reads, the `169.254.169.254` metadata endpoint, plaintext secrets in config `env` |
220
+ | MCP-005 | HIGH | Network transport without authentication: HTTP/SSE servers or config `url` servers with no auth headers |
221
+ | MCP-006 | MEDIUM | Mutating tool with no input schema: write/delete/exec tools whose arguments are untyped or undeclared |
222
+ | MCP-007 | MEDIUM | Overbroad tool description: "run any command", "access all files", and other escape-hatch language aimed at the model |
223
+ | MCP-008 | MEDIUM | Self-modifying server: tools that rewrite MCP client configs or run `pip install` / `npm install` |
224
+ | MCP-009 | HIGH | Package provenance: unpinned or `latest` auto-installs via `npx -y` / `uvx`, and package names within edit distance 2 of well-known MCP packages (typosquats) |
225
+ | MCP-010 | HIGH | Dynamic code execution: `eval`, `exec`, `__import__`, `new Function` inside a tool handler |
226
+
227
+ Rules are data, not code: severities, explanations, fixes, and detection hints live in [`src/mcp_scan/rules.yaml`](src/mcp_scan/rules.yaml). Point `--rules` at your own copy to tune them.
228
+
229
+ ## Output formats
230
+
231
+ ```bash
232
+ mcp-scan server.py # grouped terminal report
233
+ mcp-scan server.py --format json # machine-readable findings
234
+ mcp-scan server.py --format sarif # SARIF 2.1.0 for GitHub code scanning
235
+ ```
236
+
237
+ ## GitHub Action
238
+
239
+ Fail the build when a scanned server has a CRITICAL finding:
240
+
241
+ ```yaml
242
+ name: mcp-security
243
+ on: [push, pull_request]
244
+ jobs:
245
+ scan:
246
+ runs-on: ubuntu-latest
247
+ steps:
248
+ - uses: actions/checkout@v4
249
+ - uses: AdamDiStefanoAI/MCP-Scanner@v0.1.1
250
+ with:
251
+ targets: "mcp/ examples/"
252
+ ```
253
+
254
+ For SARIF upload to GitHub code scanning, run the CLI directly:
255
+
256
+ ```yaml
257
+ - uses: actions/setup-python@v5
258
+ with:
259
+ python-version: "3.12"
260
+ - run: pip install runtime-mcp-scanner
261
+ - run: mcp-scan servers/ --format sarif > results.sarif
262
+ continue-on-error: true
263
+ - uses: github/codeql-action/upload-sarif@v3
264
+ with:
265
+ sarif_file: results.sarif
266
+ ```
267
+
268
+ ## What a risky config looks like
269
+
270
+ ```json
271
+ {
272
+ "mcpServers": {
273
+ "helper": {
274
+ "command": "npx",
275
+ "args": ["-y", "@modelcontextprotocol/server-filesytem"]
276
+ },
277
+ "shell": {
278
+ "command": "bash",
279
+ "args": ["-lc", "run the bridge"]
280
+ },
281
+ "docs": {
282
+ "url": "http://docs.internal:8000/sse"
283
+ }
284
+ }
285
+ }
286
+ ```
287
+
288
+ That config gets flagged for a shell-wrapped server (MCP-001), an unpinned auto-install and a one-character typosquat of `server-filesystem` (MCP-009), and a remote server with no auth headers (MCP-005). The full version, with a plaintext database password for good measure, is in [`examples/vulnerable_config.json`](examples/vulnerable_config.json).
289
+
290
+ ## Honest limits
291
+
292
+ MCP-Scanner is a heuristic static check, not a proof of safety. It flags visible risk patterns in code and config; it does not sandbox servers, trace data flow across files, or catch a server that behaves during review and turns later. Run it as one layer: before install, in CI, and whenever a config changes. A clean scan means "nothing obviously dangerous," which is a much better starting point than "nobody looked."
293
+
294
+ ## Contributing
295
+
296
+ Issues and pull requests are welcome. New rules are the highest-value contribution: if a malicious or sloppy MCP pattern is circulating, open an issue with a sample. See [CONTRIBUTING.md](CONTRIBUTING.md).
297
+
298
+ ## License
299
+
300
+ MIT. See [LICENSE](LICENSE).
301
+
302
+ Built by [Adam DiStefano](https://github.com/AdamDiStefanoAI), creator of the [ACR Standard](https://github.com/AdamDiStefanoAI/acr-standard) for runtime control of AI agents. Companion tool: [AI-Key-Scanner](https://github.com/AdamDiStefanoAI/AI-Key-Scanner).
@@ -0,0 +1,5 @@
1
+ runtime_mcp_scanner-0.1.1.dist-info/METADATA,sha256=V9zU0DcT_VX-oqhNXu8vTQl5P73B-sMejT-Ciyixsio,15285
2
+ runtime_mcp_scanner-0.1.1.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
3
+ runtime_mcp_scanner-0.1.1.dist-info/entry_points.txt,sha256=kvHSlIBcHwQqd6tHHcYDzue62HiHXFY8-NrOzp2GJbM,47
4
+ runtime_mcp_scanner-0.1.1.dist-info/licenses/LICENSE,sha256=muaGDlSooAGyAd92bUyjrgSCIPqZsP32C0JFr-IObkY,1071
5
+ runtime_mcp_scanner-0.1.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.4
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ mcp-scan = mcp_scan.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Adam DiStefano
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.