agent-capmap 0.1.0__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.
Files changed (48) hide show
  1. agent_capmap-0.1.0.dist-info/METADATA +295 -0
  2. agent_capmap-0.1.0.dist-info/RECORD +48 -0
  3. agent_capmap-0.1.0.dist-info/WHEEL +4 -0
  4. agent_capmap-0.1.0.dist-info/entry_points.txt +2 -0
  5. agent_capmap-0.1.0.dist-info/licenses/LICENSE +21 -0
  6. agentcapmap/__init__.py +3 -0
  7. agentcapmap/__main__.py +5 -0
  8. agentcapmap/baseline.py +125 -0
  9. agentcapmap/classify/__init__.py +221 -0
  10. agentcapmap/classify/cache.py +74 -0
  11. agentcapmap/classify/prompt.py +160 -0
  12. agentcapmap/classify/provider.py +122 -0
  13. agentcapmap/classify/reference.py +115 -0
  14. agentcapmap/classify/reference_labels.json +6135 -0
  15. agentcapmap/cli.py +392 -0
  16. agentcapmap/diff.py +103 -0
  17. agentcapmap/discovery/__init__.py +209 -0
  18. agentcapmap/discovery/_common.py +297 -0
  19. agentcapmap/discovery/claude_code.py +258 -0
  20. agentcapmap/discovery/claude_desktop.py +28 -0
  21. agentcapmap/discovery/cursor.py +33 -0
  22. agentcapmap/discovery/mcp_json.py +29 -0
  23. agentcapmap/discovery/vscode.py +65 -0
  24. agentcapmap/enumerate/__init__.py +278 -0
  25. agentcapmap/enumerate/hashing.py +37 -0
  26. agentcapmap/enumerate/http.py +313 -0
  27. agentcapmap/enumerate/netguard.py +60 -0
  28. agentcapmap/enumerate/stdio.py +273 -0
  29. agentcapmap/enumerate/transport.py +86 -0
  30. agentcapmap/evaluation.py +498 -0
  31. agentcapmap/findings.py +129 -0
  32. agentcapmap/graph/__init__.py +264 -0
  33. agentcapmap/graph/server_attrs.py +164 -0
  34. agentcapmap/limits.py +35 -0
  35. agentcapmap/models.py +186 -0
  36. agentcapmap/policies/default.yaml +113 -0
  37. agentcapmap/policy/__init__.py +24 -0
  38. agentcapmap/policy/engine.py +229 -0
  39. agentcapmap/policy/loader.py +223 -0
  40. agentcapmap/py.typed +0 -0
  41. agentcapmap/redact.py +268 -0
  42. agentcapmap/report/__init__.py +36 -0
  43. agentcapmap/report/_text.py +62 -0
  44. agentcapmap/report/markdown.py +152 -0
  45. agentcapmap/report/sarif.py +209 -0
  46. agentcapmap/schemas/policy.schema.json +172 -0
  47. agentcapmap/schemas/tool-tags.schema.json +57 -0
  48. agentcapmap/taxonomy.py +56 -0
@@ -0,0 +1,295 @@
1
+ Metadata-Version: 2.5
2
+ Name: agent-capmap
3
+ Version: 0.1.0
4
+ Summary: Inventory the MCP tools reachable by AI agents and flag dangerous capability combinations with policy-as-code.
5
+ Project-URL: Homepage, https://github.com/NKwolv/agent-capmap
6
+ Project-URL: Documentation, https://github.com/NKwolv/agent-capmap#readme
7
+ Project-URL: Issues, https://github.com/NKwolv/agent-capmap/issues
8
+ Project-URL: Changelog, https://github.com/NKwolv/agent-capmap/blob/main/CHANGELOG.md
9
+ Project-URL: Security, https://github.com/NKwolv/agent-capmap/security/policy
10
+ Author: NKwolv
11
+ License-Expression: MIT
12
+ License-File: LICENSE
13
+ Keywords: ai-agents,mcp,model-context-protocol,sarif,security
14
+ Classifier: Development Status :: 3 - Alpha
15
+ Classifier: Environment :: Console
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Security
22
+ Requires-Python: >=3.11
23
+ Requires-Dist: anthropic>=1.11
24
+ Requires-Dist: certifi>=2026.10.19
25
+ Requires-Dist: httpx>=0.27
26
+ Requires-Dist: jsonschema>=4.20
27
+ Requires-Dist: pydantic>=2.7
28
+ Requires-Dist: pyyaml>=6.0.2
29
+ Provides-Extra: dev
30
+ Requires-Dist: bandit>=1.8; extra == 'dev'
31
+ Requires-Dist: cyclonedx-bom>=7.5; extra == 'dev'
32
+ Requires-Dist: mypy>=1.10; extra == 'dev'
33
+ Requires-Dist: pip-audit>=2.7; extra == 'dev'
34
+ Requires-Dist: pre-commit>=3.7; extra == 'dev'
35
+ Requires-Dist: pytest>=8.0; extra == 'dev'
36
+ Requires-Dist: ruff>=0.5; extra == 'dev'
37
+ Requires-Dist: types-jsonschema; extra == 'dev'
38
+ Requires-Dist: types-pyyaml; extra == 'dev'
39
+ Requires-Dist: uv>=0.13.4; extra == 'dev'
40
+ Requires-Dist: virtualenv>=21.14.9; extra == 'dev'
41
+ Requires-Dist: zizmor>=1.0; extra == 'dev'
42
+ Description-Content-Type: text/markdown
43
+
44
+ # agent-capmap
45
+
46
+ **Find out what your AI agents can actually do, and which combinations of their tools
47
+ an attacker could abuse.**
48
+
49
+ AI assistants such as Claude Desktop, Cursor, VS Code and Claude Code get their powers
50
+ from MCP servers: read files, browse the web, send email, run shell commands. Each tool
51
+ can look harmless on its own. The danger is usually the combination: an agent that can
52
+ read private data, read attacker-written content (a web page, an issue, an email), and
53
+ send data out can be talked into leaking that data. That is the "lethal trifecta".
54
+
55
+ agent-capmap inventories every MCP server and tool reachable by each agent, classifies
56
+ what each tool can do, and applies policy-as-code rules to the **combination** of tools
57
+ per agent. Findings come with evidence and can gate CI through SARIF and exit codes.
58
+
59
+ > **Status: pre-release (0.1.0).** All features below work. The classifier's accuracy
60
+ > has not been benchmarked yet; see [Accuracy](#accuracy).
61
+
62
+ ## Install
63
+
64
+ Requires Python 3.11+.
65
+
66
+ ```sh
67
+ pipx install agent-capmap # or: uv tool install agent-capmap
68
+ ```
69
+
70
+ Releases are built and published by GitHub Actions with PyPI trusted publishing, so
71
+ each file on PyPI carries an attestation linking it to the workflow run that built it.
72
+
73
+ For a fully hash-pinned install, with the exact dependency versions CI tests, install
74
+ from a clone instead:
75
+
76
+ ```sh
77
+ git clone https://github.com/NKwolv/agent-capmap
78
+ cd agent-capmap
79
+ python -m venv .venv
80
+ . .venv/bin/activate # Windows PowerShell: .venv\Scripts\Activate.ps1
81
+ pip install --require-hashes -r requirements/runtime.lock
82
+ pip install --no-deps .
83
+ ```
84
+
85
+ > **Locked-down Windows machines:** if your organization's policy (Device Guard,
86
+ > AppLocker, Smart App Control) blocks `agentcapmap.exe` or `pipx.exe`, run the tool
87
+ > through Python instead, which is the same program. If you installed with
88
+ > `pip install agent-capmap`, run `python -m agentcapmap scan --user`. If you installed
89
+ > with pipx, the package lives in pipx's own environment, so plain `python` will not
90
+ > find it: use that environment's interpreter (`python -m pipx environment --value
91
+ > PIPX_LOCAL_VENVS` shows the folder):
92
+ > `<venvs>\agent-capmap\Scripts\python.exe -m agentcapmap scan --user`. Do not try to
93
+ > bypass the policy; ask your administrator to allow the launcher if you want it.
94
+
95
+ > **Windows:** if `pip install` fails with "No such file or directory" deep inside
96
+ > `site-packages`, the path is too long for Windows' 260-character limit. Clone to a
97
+ > short path (for example `C:\src\agent-capmap`) or enable long path support.
98
+
99
+ ## Quickstart (5 minutes, no API key)
100
+
101
+ ```sh
102
+ # 1. See findings on a ready-made inventory of five example agents
103
+ # (from a clone, use examples/demo/inventory.json directly)
104
+ curl -fsSLO https://raw.githubusercontent.com/NKwolv/agent-capmap/main/examples/demo/inventory.json
105
+ agentcapmap evaluate inventory.json --format markdown
106
+
107
+ # 2. List the MCP servers configured on your own machine (reads files only)
108
+ agentcapmap scan --user
109
+ ```
110
+
111
+ On Windows PowerShell, use `curl.exe` instead of `curl`.
112
+
113
+ Step 1 should report 2 critical, 2 high and 1 medium finding. Step 2 lists your
114
+ servers with every secret redacted, without starting anything.
115
+
116
+ ## Example output
117
+
118
+ ```markdown
119
+ ### 1. `CAP-001` | Critical
120
+
121
+ `Lethal trifecta: the agent can read private data, ingest attacker-controlled content,
122
+ and send data externally, so injected instructions can exfiltrate data.`
123
+
124
+ - **Agent:** `generic:fixtures/trifecta/mcp.json` at `fixtures/trifecta/mcp.json:3`
125
+ - **Tools:** `github/get_file_contents`, `github/list_issues`, `mailer/send_email`
126
+
127
+ | Server | Tool | Matched | Evidence |
128
+ |---|---|---|---|
129
+ | `github` | `get_file_contents` | `data_access=private_read` | `contents of a file from a private or public repository` |
130
+ | `github` | `list_issues` | `untrusted_input=true` | `including issues opened by anyone` |
131
+ | `mailer` | `send_email` | `egress=external` | `Send an email to any recipient address` |
132
+ ```
133
+
134
+ (Abridged from the real output of step 1.)
135
+
136
+ ## How it works
137
+
138
+ ```
139
+ config files ──► discover ──► enumerate ──► classify ──► policy ──► report
140
+ (read only) (initialize + (LLM tags (YAML (JSON, Markdown,
141
+ tools/list) 6 axes) rules) SARIF, exit code)
142
+ ```
143
+
144
+ 1. **Discover** MCP configs: Claude Desktop, Cursor, VS Code (`.vscode/mcp.json`,
145
+ `settings.json`), Claude Code (`.mcp.json`, and with `--user` your `~/.claude.json`
146
+ user and per-project servers, including permission rules) and generic `mcp.json`.
147
+ Secrets are redacted everywhere.
148
+ 2. **Enumerate** (`--enumerate`): start each server and ask only for its tool list.
149
+ Tools are never called.
150
+ 3. **Classify** (`--classify`): an LLM tags each tool on six axes and flags tool
151
+ descriptions that try to manipulate the model. Results are cached and validated
152
+ against a JSON schema.
153
+ 4. **Evaluate** (`--evaluate`): deterministic rules decide what is dangerous. The LLM
154
+ never makes risk decisions.
155
+ 5. **Report**: JSON, Markdown, or SARIF for GitHub code scanning; `--fail-on` sets the
156
+ exit code.
157
+
158
+ ### Full scan of your own setup (needs an API key)
159
+
160
+ ```sh
161
+ export ANTHROPIC_API_KEY=<YOUR_API_KEY> # PowerShell: $env:ANTHROPIC_API_KEY = "..."
162
+ agentcapmap scan --user --enumerate --classify --evaluate --format markdown -o report.md
163
+ ```
164
+
165
+ `--enumerate` **runs the server commands in your configs**: only use it on configs you
166
+ trust. To try the whole pipeline on two known servers first, see
167
+ [`examples/live-trifecta`](https://github.com/NKwolv/agent-capmap/blob/main/examples/README.md).
168
+
169
+ ## Capability taxonomy
170
+
171
+ | Axis | Values | Question |
172
+ |---|---|---|
173
+ | `data_access` | none, private_read, secrets_read | What non-public data can it read? |
174
+ | `untrusted_input` | false, true | Does it return content strangers can write? |
175
+ | `egress` | none, internal, external | Can it send data somewhere an outsider can read? |
176
+ | `mutation` | none, reversible_write, destructive | What can it change or delete? |
177
+ | `execution` | none, sandboxed, host | Does it run code, and where? |
178
+ | `privilege` | none, identity_admin | Does it manage users or permissions? |
179
+
180
+ Definitions and edge-case rulings: [docs/taxonomy.md](https://github.com/NKwolv/agent-capmap/blob/main/docs/taxonomy.md).
181
+
182
+ ## Policy
183
+
184
+ The built-in [`policies/default.yaml`](https://github.com/NKwolv/agent-capmap/blob/main/policies/default.yaml) has nine rules:
185
+
186
+ | ID | Severity | Fires when an agent has |
187
+ |---|---|---|
188
+ | CAP-001 | critical | private data + untrusted input + external egress (lethal trifecta) |
189
+ | CAP-002 | critical | untrusted input + host code execution |
190
+ | CAP-003 | high | a destructive or host-execution tool with no approval gate |
191
+ | CAP-004 | high | secrets access + external egress |
192
+ | CAP-005 | high | a remote server with no authentication exposing capabilities |
193
+ | CAP-006 | high | tool definitions that changed since the baseline (rug pull) |
194
+ | CAP-007 | medium | a server not pinned to an exact version, or from an unvetted source |
195
+ | CAP-008 | high | a tool description that tries to instruct the model (tool poisoning) |
196
+ | CAP-009 | medium | the same tool name on two servers (shadowing) |
197
+
198
+ Write your own rules in the same format and pass `--policy my-policy.yaml`:
199
+
200
+ ```yaml
201
+ - id: ACME-001
202
+ severity: high
203
+ when_server: {remote: true}
204
+ when_agent_has_any:
205
+ - privilege: identity_admin
206
+ message: Remote servers must not manage identities.
207
+ ```
208
+
209
+ Conditions: `when_agent_has_all`, `when_agent_has_any`, `when_server`,
210
+ `when_hash_changed`, `when_tool_name_shadowed`. The format is defined by
211
+ [`schemas/policy.schema.json`](https://github.com/NKwolv/agent-capmap/blob/main/schemas/policy.schema.json). Rug-pull detection:
212
+
213
+ ```sh
214
+ agentcapmap baseline create inventory.json -o baseline.json # once, after review
215
+ agentcapmap evaluate new-inventory.json --baseline baseline.json
216
+ ```
217
+
218
+ To see what changed between two runs (for example `main` versus a pull request), save
219
+ each `evaluate` output and compare them:
220
+
221
+ ```sh
222
+ agentcapmap diff before.json after.json --format markdown
223
+ agentcapmap diff before.json after.json --fail-on high --fail-on-new-capability
224
+ ```
225
+
226
+ `diff` lists new and resolved findings and capabilities an agent gained or lost.
227
+ `--fail-on` looks at new findings only, so existing, already-accepted findings do not
228
+ fail the build; `--fail-on-new-capability` fails when an agent gains any capability.
229
+
230
+ ## CI and GitHub code scanning
231
+
232
+ ```sh
233
+ agentcapmap evaluate inventory.json --format sarif -o agentcapmap.sarif --fail-on high
234
+ ```
235
+
236
+ Exit codes: `0` passed, `1` findings at or above `--fail-on`, `2` usage or input error.
237
+ The repository is also a GitHub Action: see [docs/github-action.md](https://github.com/NKwolv/agent-capmap/blob/main/docs/github-action.md)
238
+ and the [example workflow](https://github.com/NKwolv/agent-capmap/blob/main/docs/examples/agentcapmap.yml). By default the Action reads
239
+ configs only, which is safe for pull requests from forks.
240
+
241
+ ## Accuracy
242
+
243
+ The classifier is evaluated against hand-labeled tools from real MCP servers (see
244
+ [`eval/`](https://github.com/NKwolv/agent-capmap/blob/main/eval/README.md)). **No accuracy numbers are published yet.** The 381 labels
245
+ (28 servers) were drafted by one AI model and then reviewed by another AI model at the
246
+ maintainer's request; no person has independently reviewed them yet. A model grading a
247
+ model can overstate accuracy, so numbers will be quoted here only after a person has
248
+ reviewed the labels and the run is frozen (`python eval/run_eval.py`). The same labels
249
+ also power classification without an API key for these known tools; see
250
+ [Limitations](#limitations).
251
+
252
+ ## Limitations
253
+
254
+ - **Descriptions, not behavior.** Tools are classified by what their definitions say. A
255
+ tool that lies about itself will be mis-tagged; agent-capmap never calls tools.
256
+ - **Classifier errors.** Tags can be wrong, especially for vague descriptions. Every
257
+ tag carries a confidence and an evidence phrase; phrases not found in the definition
258
+ are marked unverified.
259
+ - **Approval gates are mostly invisible.** Claude Code's permission settings are read
260
+ (so its default "ask before each MCP call" counts as a gate), but most other clients do
261
+ not record this, and CAP-003 treats "unknown" as "no gate" by default.
262
+ - **Enumeration runs code.** Starting servers executes the configured commands. A
263
+ started server can also read its parent's environment on Linux.
264
+ - **Coverage.** Config formats evolve; Windsurf is not read yet. MCP servers that do not
265
+ live in config files (Claude Code plugins, claude.ai connectors, IDE built-in servers,
266
+ managed settings) are not seen. WebSocket servers are listed but not enumerated.
267
+ Token scopes are not visible.
268
+ - **Reference labels are AI-reviewed.** For 381 tools from 28 well-known servers, the
269
+ classifier answers from bundled labels (matched by exact definition hash) instead of
270
+ calling a model, so no API key is needed. Those labels were reviewed by an AI model, not
271
+ yet by a person, and a wrong label is returned with full confidence; any change to a
272
+ tool's definition falls back to the cache or the LLM. Treat them as a convenience.
273
+ - **Not assessed is not safe.** Servers that could not be enumerated and tools that
274
+ could not be classified are listed under `not_assessed`, never assumed safe.
275
+
276
+ ## Safety
277
+
278
+ Secrets from configs are redacted in all output and logs. Enumeration sends only
279
+ `initialize` and `tools/list`, with a minimal environment for launched servers, no
280
+ credential-forwarding redirects, and size limits on everything a server returns.
281
+ Reports render untrusted text inertly. See [SECURITY.md](https://github.com/NKwolv/agent-capmap/blob/main/SECURITY.md) and the
282
+ [threat model](https://github.com/NKwolv/agent-capmap/blob/main/docs/threat-model.md).
283
+
284
+ ## Documentation
285
+
286
+ - [Capability taxonomy](https://github.com/NKwolv/agent-capmap/blob/main/docs/taxonomy.md)
287
+ - [Threat model](https://github.com/NKwolv/agent-capmap/blob/main/docs/threat-model.md): attacks covered, OWASP / MITRE ATLAS mapping
288
+ - [GitHub Action](https://github.com/NKwolv/agent-capmap/blob/main/docs/github-action.md)
289
+ - [Comparison with Snyk Agent Scan (mcp-scan)](https://github.com/NKwolv/agent-capmap/blob/main/docs/vs-mcp-scan.md)
290
+ - [Examples](https://github.com/NKwolv/agent-capmap/blob/main/examples/README.md) · [Evaluation](https://github.com/NKwolv/agent-capmap/blob/main/eval/README.md) · [POC checklist](https://github.com/NKwolv/agent-capmap/blob/main/docs/poc-checklist.md)
291
+ - [Contributing](https://github.com/NKwolv/agent-capmap/blob/main/CONTRIBUTING.md) · [Security policy](https://github.com/NKwolv/agent-capmap/blob/main/SECURITY.md) · [Changelog](https://github.com/NKwolv/agent-capmap/blob/main/CHANGELOG.md)
292
+
293
+ ## License
294
+
295
+ MIT. See [LICENSE](https://github.com/NKwolv/agent-capmap/blob/main/LICENSE).
@@ -0,0 +1,48 @@
1
+ agentcapmap/__init__.py,sha256=obKLf0ZZ_1JxejXRMSVoQyyXCUUVpKnpg70raMECb8s,92
2
+ agentcapmap/__main__.py,sha256=7ZDXsBQ0WkuCXfmY-_jkFHDuEhAWAJluGZqddbGItYo,63
3
+ agentcapmap/baseline.py,sha256=wUD1fRL40fCcSgfI1PnyXQJeM0yhv-WZw6u1woUkURo,4676
4
+ agentcapmap/cli.py,sha256=OdRJN5_ROn5U9q9ZstDxSIY--o-JGIrgOn5TIfMmDV0,14835
5
+ agentcapmap/diff.py,sha256=XU51_reeUwDgwz14hP3-I2HWaA7B94L6gaWO2khxads,3964
6
+ agentcapmap/evaluation.py,sha256=uxHDh65c9wnVZgsEAXKPdlmuPwG4DCYJLr2iTNs-pbM,17200
7
+ agentcapmap/findings.py,sha256=lZX41bb-ffGlijX64PA_mOa7yzDsvFwZZajLrDMzHrU,3503
8
+ agentcapmap/limits.py,sha256=OE1d4VkXY5ceN_F7wOnUBROS9RzJo4jLVnTkh4wmYYc,1231
9
+ agentcapmap/models.py,sha256=74ZlxchqRzm6m_SNiVmpnSr05dB3v9kT2xtKVHq9Sbg,5931
10
+ agentcapmap/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ agentcapmap/redact.py,sha256=ZAjJVoDZ1rGIjGhwWCi32XWkGtKU4Zl6NUAF8yq4aes,9507
12
+ agentcapmap/taxonomy.py,sha256=Iuw4c_kThiyjrzwHW_O9Yc-7aCOOiZE6K_cyR53O1y4,1869
13
+ agentcapmap/classify/__init__.py,sha256=Mgw5f2loU6FBgpXGWR22LkpykDz0qKEv5OWjNMXnpKs,8606
14
+ agentcapmap/classify/cache.py,sha256=bJzpbNi4LtixN8WsvJOdiZeGftaqihpCB6Vy26soGng,2531
15
+ agentcapmap/classify/prompt.py,sha256=I1kDpKwkdCdOzcF0BpA20LyoHvY3FQBq4_MC0A0CU_c,8682
16
+ agentcapmap/classify/provider.py,sha256=fjGy0VpT_aOhkSvhGaSETaX8Pf-r89OdS_C7mgWHbaA,4878
17
+ agentcapmap/classify/reference.py,sha256=8FXGPrpKZBeviXd-aQfX9jZ2Sqa8SBhPIQHNTE4qE9E,4034
18
+ agentcapmap/classify/reference_labels.json,sha256=EB7LaRAEWrni8azwAYPlHh2oDb881rO_dbwjZ0H6xjk,208335
19
+ agentcapmap/discovery/__init__.py,sha256=a3b4t-otqWLl9XfUOs-SUwZoYMbQ6ttAM4FwsVeegn0,7554
20
+ agentcapmap/discovery/_common.py,sha256=u0WA0IA1023LBsXZnjT-Eao9P2j_X-0l-nV1AwHzjSI,10043
21
+ agentcapmap/discovery/claude_code.py,sha256=Z6AL7a95jAVGIvEN0vyLwtcXRMvrI4OTMppsMCNep-4,10239
22
+ agentcapmap/discovery/claude_desktop.py,sha256=7PsElFL8fD1L4RhxkS7UhZ-euEKYJh48a4rDpfvnLUc,833
23
+ agentcapmap/discovery/cursor.py,sha256=cTMK8hRYi67YnXqOxLcZzD07f6TnQYHVlUPpLXDeUms,1039
24
+ agentcapmap/discovery/mcp_json.py,sha256=aYfWGVHZMtM6miFCYhRax-1RoNRsOcc4Jka-LhI7KWc,936
25
+ agentcapmap/discovery/vscode.py,sha256=eewYklqnyKMOLjYfUnMG4Q1FoW2j3DHw4RaWRC9Ib-Q,2126
26
+ agentcapmap/enumerate/__init__.py,sha256=7891bEb59OuXyu-wJKmrEwXUdREFKVzyHhctycrm8GE,10958
27
+ agentcapmap/enumerate/hashing.py,sha256=5gno5bAiAuvLfKdTx8X4YmKMebkJhlQp0MESmXxb3Zc,1214
28
+ agentcapmap/enumerate/http.py,sha256=Pv3wB1hNZ2Jw2bMO7TxZok4_rzXVVkU8lBg1bKp4q0A,12321
29
+ agentcapmap/enumerate/netguard.py,sha256=Qgi4xYwi1uMu89kRJlKIFk72MW7uq2yISF26M13KknE,2554
30
+ agentcapmap/enumerate/stdio.py,sha256=2Z4Dj7Whun5IB7CrT4OXh6CObb-PBD8WFoW98wN_OE0,10403
31
+ agentcapmap/enumerate/transport.py,sha256=Q55C7k2VnSLgifXGOLbTcTvaFmm6duXczT8lX9zATKI,2771
32
+ agentcapmap/graph/__init__.py,sha256=S7wEDGXrbbE8y93ZhWlHnHnv25PNX7AzOcI4j5OK0CI,10035
33
+ agentcapmap/graph/server_attrs.py,sha256=kxTKBhHjLWIkEz__FCSeMuKBHN-crDgjg_f6DyefJr4,6748
34
+ agentcapmap/policy/__init__.py,sha256=Wgt961Ff76YBcBbmqcLel3Mj9HihrhbEPJVsdCZ0YHI,489
35
+ agentcapmap/policy/engine.py,sha256=S3S_EBI5wS4zxHjWt-XrZ7yDdOzde6SCElENqZcHOqk,8406
36
+ agentcapmap/policy/loader.py,sha256=K2TH0RcDFT_1vextVrcBHbPC17s73DSBjUtcyCErI9c,8033
37
+ agentcapmap/report/__init__.py,sha256=9Dw-xMcpEFbx7_I8C_Y-ITapDubm6SZWVD_bJ6rSXIw,1127
38
+ agentcapmap/report/_text.py,sha256=iGk1PXB6Z_Dwir9nlqFoTVXK9GJsGe0qSsMzJUgcF4g,2099
39
+ agentcapmap/report/markdown.py,sha256=Uo2fZLlJoJWYS7cNDopVyB50aF7-SBZvZth7dEkiRUs,6083
40
+ agentcapmap/report/sarif.py,sha256=lUpOM8UEQ3zaAikyucX_c3P_tbUTZHmuCxDi2hqQVl8,7696
41
+ agentcapmap/policies/default.yaml,sha256=49mVVNkh64uHTrJjBvPozSnoWWdKwmcn08_QzUF8mVY,4325
42
+ agentcapmap/schemas/policy.schema.json,sha256=7UTVJP2QiO54oONKDXp44K7kpVX9o7pg5hvcNkUg8gA,6423
43
+ agentcapmap/schemas/tool-tags.schema.json,sha256=HCiOD69uRap1rVXmflQaUbSdNXMYgwrcNQFzItz91KQ,2171
44
+ agent_capmap-0.1.0.dist-info/METADATA,sha256=qwxAB-bCUuSd7dBoJWPERlA13q40dg1Ni-tQsZyesp4,14678
45
+ agent_capmap-0.1.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
46
+ agent_capmap-0.1.0.dist-info/entry_points.txt,sha256=olKJIQuXulXL9T9E_SdreA0Ggy-Ylq-NbgVynJTfH6Y,53
47
+ agent_capmap-0.1.0.dist-info/licenses/LICENSE,sha256=FnKTHxvCRPab3DyRc6SWm-NbmCvvAfZ7IrvGSMaXhIo,1063
48
+ agent_capmap-0.1.0.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
+ agentcapmap = agentcapmap.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 NKwolv
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.
@@ -0,0 +1,3 @@
1
+ """agent-capmap: map the MCP capabilities reachable by AI agents."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,5 @@
1
+ import sys
2
+
3
+ from agentcapmap.cli import main
4
+
5
+ sys.exit(main())
@@ -0,0 +1,125 @@
1
+ """Baseline pinning of tool definition hashes, for rug-pull detection (CAP-006).
2
+
3
+ `agentcapmap baseline create` records, per server, the hash of every tool definition.
4
+ Later scans compare against it: a changed, added or removed tool means the server's
5
+ definitions changed after review.
6
+
7
+ A server is identified by its name, transport and (redacted) launch command or URL, so
8
+ a deliberate config change (e.g. a new pinned version) is a new server, while the same
9
+ config serving different definitions is a change.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import hashlib
15
+ import json
16
+ from pathlib import Path
17
+ from typing import Annotated, Literal
18
+
19
+ from pydantic import BaseModel, ConfigDict, Field, StringConstraints, ValidationError
20
+
21
+ from agentcapmap.limits import MAX_CONFIG_BYTES, MAX_TOOLS_PER_SERVER, loads_untrusted
22
+ from agentcapmap.models import EnumerationStatus, Inventory, Server
23
+
24
+ BASELINE_VERSION: Literal[1] = 1
25
+ _MAX_BASELINE_BYTES = 16 * MAX_CONFIG_BYTES
26
+ _MAX_SERVERS = 10_000
27
+
28
+ Sha256Hex = Annotated[str, StringConstraints(pattern=r"^[0-9a-f]{64}$")]
29
+ ToolName = Annotated[str, StringConstraints(min_length=1, max_length=512)]
30
+
31
+
32
+ class BaselineError(ValueError):
33
+ pass
34
+
35
+
36
+ class BaselineServer(BaseModel):
37
+ model_config = ConfigDict(extra="forbid")
38
+
39
+ name: Annotated[str, StringConstraints(min_length=1, max_length=512)]
40
+ tools: dict[ToolName, Sha256Hex] = Field(max_length=MAX_TOOLS_PER_SERVER)
41
+
42
+
43
+ class Baseline(BaseModel):
44
+ model_config = ConfigDict(extra="forbid")
45
+
46
+ baseline_version: Literal[1] = BASELINE_VERSION
47
+ servers: dict[Sha256Hex, BaselineServer] = Field(default_factory=dict, max_length=_MAX_SERVERS)
48
+
49
+
50
+ class HashDiff(BaseModel):
51
+ added: list[str] = Field(default_factory=list)
52
+ removed: list[str] = Field(default_factory=list)
53
+ changed: list[str] = Field(default_factory=list)
54
+ # name -> {"baseline": hash, "current": hash} for changed tools
55
+ hashes: dict[str, dict[str, str]] = Field(default_factory=dict)
56
+
57
+ def __bool__(self) -> bool:
58
+ return bool(self.added or self.removed or self.changed)
59
+
60
+
61
+ def server_identity(server: Server) -> str:
62
+ material = json.dumps(
63
+ [server.name, server.transport.value, server.command, server.args, server.url],
64
+ separators=(",", ":"),
65
+ ensure_ascii=False,
66
+ )
67
+ return hashlib.sha256(material.encode("utf-8")).hexdigest()
68
+
69
+
70
+ def _current_tools(server: Server) -> dict[str, str] | None:
71
+ enumeration = server.enumeration
72
+ if enumeration is None or enumeration.status is not EnumerationStatus.OK:
73
+ return None
74
+ return {tool.name: tool.definition_hash for tool in enumeration.tools}
75
+
76
+
77
+ def create_baseline(inventory: Inventory) -> Baseline:
78
+ """Pin every successfully enumerated, enabled server in the inventory."""
79
+ baseline = Baseline()
80
+ for agent in inventory.agents:
81
+ for server in agent.servers:
82
+ tools = _current_tools(server)
83
+ if server.disabled or tools is None:
84
+ continue
85
+ baseline.servers[server_identity(server)] = BaselineServer(
86
+ name=server.name, tools=dict(sorted(tools.items()))
87
+ )
88
+ baseline.servers = dict(sorted(baseline.servers.items()))
89
+ return baseline
90
+
91
+
92
+ def load_baseline(path: Path) -> Baseline:
93
+ try:
94
+ size = path.stat().st_size
95
+ raw = path.read_bytes() if size <= _MAX_BASELINE_BYTES else b""
96
+ except OSError as exc:
97
+ raise BaselineError(f"cannot read baseline: {exc.strerror}") from None
98
+ if size > _MAX_BASELINE_BYTES:
99
+ raise BaselineError(f"baseline file larger than {_MAX_BASELINE_BYTES} bytes")
100
+ try:
101
+ data = loads_untrusted(raw)
102
+ except ValueError as exc:
103
+ raise BaselineError(f"baseline is not valid JSON: {exc}") from None
104
+ try:
105
+ return Baseline.model_validate(data, strict=True)
106
+ except ValidationError as exc:
107
+ raise BaselineError(
108
+ f"baseline does not match the expected format ({exc.error_count()} error(s))"
109
+ ) from None
110
+
111
+
112
+ def diff_server(baseline: Baseline, server: Server) -> HashDiff | None:
113
+ """None when the server is not in the baseline or could not be enumerated now."""
114
+ pinned = baseline.servers.get(server_identity(server))
115
+ current = _current_tools(server)
116
+ if pinned is None or current is None:
117
+ return None
118
+ before, after = pinned.tools, current
119
+ changed = sorted(n for n in before.keys() & after.keys() if before[n] != after[n])
120
+ return HashDiff(
121
+ added=sorted(after.keys() - before.keys()),
122
+ removed=sorted(before.keys() - after.keys()),
123
+ changed=changed,
124
+ hashes={n: {"baseline": before[n], "current": after[n]} for n in changed},
125
+ )