mcpxray-cli 1.0.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.
mcpxray/verdict.py ADDED
@@ -0,0 +1,139 @@
1
+ """Verdict engine — collapse findings into a plain-language safety verdict.
2
+
3
+ A consumer just wants to know "can I install this MCP server?" — not a raw
4
+ ``82/100 (B)``. :func:`verdict` projects a :class:`~mcpxray.ir.McpServer`
5
+ (plus its :class:`~mcpxray.score.ScoreResult`) onto a traffic-light tier with a
6
+ human headline, an actionable recommendation, and the top reasons. It is a pure
7
+ downstream view: it changes neither the score nor the rules.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from dataclasses import dataclass, field
13
+ from typing import TYPE_CHECKING
14
+
15
+ from mcpxray.ir import SEVERITY_WARNING, SOURCE_STATIC
16
+
17
+ if TYPE_CHECKING:
18
+ from mcpxray.ir import Diagnostic, McpServer
19
+ from mcpxray.score import ScoreResult
20
+
21
+ # --- tiers -------------------------------------------------------------------
22
+ TIER_OK = "ok"
23
+ TIER_CAUTION = "caution"
24
+ TIER_DANGER = "danger"
25
+ TIER_UNKNOWN = "unknown"
26
+ TIERS = (TIER_OK, TIER_CAUTION, TIER_DANGER, TIER_UNKNOWN)
27
+
28
+ # rule_id -> one short, plain-language phrase shown to the user.
29
+ _RULE_PHRASE = {
30
+ "MCP101": "Possible tool-poisoning in a tool description",
31
+ "MCP102": "Leaked secrets/keys in source",
32
+ "MCP103": "Dangerous code execution (eval/exec/shell)",
33
+ "MCP104": "Weak or empty input schemas",
34
+ "MCP105": "Schema/handler drift (declared vs actual params)",
35
+ "MCP106": "JSON-Schema incompatibilities (may break Cursor/ChatGPT)",
36
+ "MCP107": "Tool descriptions need cleanup",
37
+ "MCP108": "Unpinned dependencies (supply-chain risk)",
38
+ "MCP109": "Insecure network transport (no TLS/auth)",
39
+ }
40
+
41
+ # Display order for reasons: scariest first so the top of the card leads with
42
+ # RCE / secrets / poisoning rather than a hygiene warning.
43
+ _RULE_PRIORITY = {
44
+ "MCP103": 0,
45
+ "MCP102": 1,
46
+ "MCP101": 2,
47
+ "MCP108": 3,
48
+ "MCP104": 4,
49
+ "MCP106": 5,
50
+ "MCP107": 6,
51
+ "MCP105": 7,
52
+ "MCP109": 8,
53
+ }
54
+
55
+ _MAX_REASONS = 5
56
+
57
+
58
+ @dataclass(frozen=True)
59
+ class Reason:
60
+ """One line of the 'Why' block: a rule, its plain phrase, and its count."""
61
+
62
+ rule_id: str
63
+ phrase: str
64
+ count: int
65
+
66
+
67
+ @dataclass(frozen=True)
68
+ class Verdict:
69
+ """The consumer-facing outcome of checking a server."""
70
+
71
+ tier: str
72
+ headline: str
73
+ recommendation: str
74
+ reasons: list[Reason] = field(default_factory=list)
75
+
76
+
77
+ def _reasons(diags: list[Diagnostic]) -> list[Reason]:
78
+ """Group diagnostics by rule_id, order scariest-first, cap at ``_MAX_REASONS``."""
79
+ counts: dict[str, int] = {}
80
+ for d in diags:
81
+ counts[d.rule_id] = counts.get(d.rule_id, 0) + 1
82
+ ordered = sorted(counts, key=lambda r: (_RULE_PRIORITY.get(r, 99), r))
83
+ return [
84
+ Reason(rule_id=r, phrase=_RULE_PHRASE.get(r, r), count=counts[r])
85
+ for r in ordered[:_MAX_REASONS]
86
+ ]
87
+
88
+
89
+ def verdict(doc: McpServer, score_result: ScoreResult | None = None) -> Verdict: # noqa: ARG001
90
+ """Project a server onto a consumer-facing :class:`Verdict`.
91
+
92
+ Tier rules (checked in order):
93
+
94
+ * **unknown** — static source with zero tools extracted (a language mcpxray
95
+ doesn't parse, or a tree with no tool registrations). A manifest/runtime
96
+ capture with zero tools is *not* unknown: the user handed it to us
97
+ deliberately, so "couldn't check" would be misleading.
98
+ * **danger** — any ERROR-severity finding (poisoning / secrets / RCE).
99
+ * **caution** — no errors, but at least one WARNING.
100
+ * **ok** — nothing found.
101
+ """
102
+ if doc.source_mode == SOURCE_STATIC and not doc.tools:
103
+ return Verdict(
104
+ tier=TIER_UNKNOWN,
105
+ headline="Couldn't verify statically",
106
+ recommendation=(
107
+ "Couldn't verify statically; review manually, capture the server's "
108
+ "tools/list and re-run with --manifest, or use --runtime --command "
109
+ "to let mcpxray capture it live."
110
+ ),
111
+ )
112
+
113
+ if doc.has_errors:
114
+ return Verdict(
115
+ tier=TIER_DANGER,
116
+ headline="Do not install this server",
117
+ recommendation=(
118
+ "This server has critical issues; do not install it. "
119
+ "Fix the issues above or pick a different server."
120
+ ),
121
+ reasons=_reasons(doc.errors),
122
+ )
123
+
124
+ warnings = [d for d in doc.diagnostics if d.severity == SEVERITY_WARNING]
125
+ if warnings:
126
+ reasons = _reasons(warnings)
127
+ top = ", ".join(r.phrase for r in reasons[:3])
128
+ return Verdict(
129
+ tier=TIER_CAUTION,
130
+ headline="Use with caution",
131
+ recommendation=f"Usable, but be aware of: {top}.",
132
+ reasons=reasons,
133
+ )
134
+
135
+ return Verdict(
136
+ tier=TIER_OK,
137
+ headline="Looks clean",
138
+ recommendation="Looks clean, safe to add to Claude Code.",
139
+ )
@@ -0,0 +1,211 @@
1
+ Metadata-Version: 2.3
2
+ Name: mcpxray-cli
3
+ Version: 1.0.0
4
+ Summary: Static linter + 0-100 scorecard for MCP servers. Catches tool poisoning, secret leaks, schema drift and more — locally, deterministically, in CI.
5
+ Author: Mitia
6
+ Author-email: Mitia <mitia@cloudroad.io>
7
+ Requires-Dist: typer>=0.12
8
+ Requires-Dist: pyyaml>=6
9
+ Requires-Dist: tomli>=2 ; python_full_version < '3.11'
10
+ Requires-Python: >=3.10
11
+ Description-Content-Type: text/markdown
12
+
13
+ # mcpxray
14
+
15
+ **Static linter + 0–100 scorecard for MCP servers.** `mcpxray` scans an MCP server's source (or a captured `tools/list` manifest) and flags tool poisoning, leaked secrets, dangerous capabilities, weak schemas and more — **locally, deterministically, in CI**. Point it at a GitHub URL or a local path and get a plain-language verdict — 🟢 ok / 🟡 caution / 🔴 danger — plus a 0–100 score and an SVG badge.
16
+
17
+ > MCP is the fastest-growing dev protocol since GraphQL (~97M SDK downloads/month), yet 7%+ of servers ship with vulnerabilities and the OWASP MCP Top 10 is a list, not a tool. `mcpxray` is the missing `npm audit` + OpenSSF Scorecard for MCP — static, local-first, OSS.
18
+
19
+ ## Status
20
+
21
+ ✅ **v1.0.0** — stable public API. Python + TypeScript static extractors, manifest extractor, opt-in runtime `tools/list` capture, rules **MCP101–109** (full OWASP MCP Top-10 mapping), `check`/`scan`/`score`/`badge`/`version`, plain/json/github/sarif/card reports, 0–100 score with error cap, SVG badge, `--fix`/`--diff` for MCP108, pre-commit hook, frozen plugin API (`__all__` + SemVer policy), tokenless PyPI trusted publishing. 224 tests.
22
+
23
+ ## Install
24
+
25
+ ```bash
26
+ uv tool install mcpxray-cli
27
+ # or: pip install mcpxray-cli
28
+ ```
29
+
30
+ The PyPI distribution is `mcpxray-cli` (the name `mcpxray` is blocked on PyPI by an unrelated project); the command it installs is still `mcpxray`.
31
+
32
+ Requires Python ≥ 3.10.
33
+
34
+ ## Quick start
35
+
36
+ ```bash
37
+ # Is this MCP server safe to install? Point mcpxray at a GitHub URL or a local path.
38
+ mcpxray check https://github.com/owner/repo
39
+ mcpxray check path/to/my-mcp-server
40
+
41
+ # Verdict + the full finding list
42
+ mcpxray check path/to/my-mcp-server --details
43
+
44
+ # Not a Python server? Hand mcpxray a captured tools/list dump (any language)
45
+ mcpxray check --manifest tools-list.json
46
+
47
+ # ...or spawn the server and let mcpxray capture tools/list live (any language)
48
+ mcpxray check --runtime --command "python -m my_mcp_server"
49
+
50
+ # --- power users / CI -----------------------------------------------------
51
+ # Lint and print findings (CI gate: --check exits 1 on any ERROR)
52
+ mcpxray scan path/to/my-mcp-server --check -f github
53
+ # Just the 0–100 score, fail below a bar
54
+ mcpxray score path/to/my-mcp-server --fail-under 80
55
+ # Embed an SVG score badge in your README
56
+ mcpxray badge path/to/my-mcp-server -o docs/score.svg
57
+ ```
58
+
59
+ ## Commands
60
+
61
+ | Command | Purpose |
62
+ | --- | --- |
63
+ | `mcpxray check <URL \| PATH> [--manifest FILE] [--runtime --command CMD] [--details\|-v] [--fail-under N]` | **Friendly safety verdict** (🟢/🟡/🔴/⚪) + recommendation. Clones a GitHub URL automatically; exits 1 on 🔴 danger or below `--fail-under`. |
64
+ | `mcpxray scan [PATH] [--manifest FILE] [--runtime --command CMD] [-f plain\|json\|github\|sarif\|card] [--check] [--fix] [--diff]` | Lint a server and print findings. `--check` exits 1 on any ERROR (CI gate); `--fix` pins unpinned deps in place, `--diff` previews (local source only). |
65
+ | `mcpxray score [PATH] [--manifest FILE] [--runtime --command CMD] [--fail-under N]` | Print the 0–100 score and grade; exit 1 below `--fail-under`. |
66
+ | `mcpxray badge [PATH \| --score N] [-o FILE]` | Render an SVG score badge (`-o -` for stdout). |
67
+ | `mcpxray version` | Print the version. |
68
+
69
+ ## What the verdict means
70
+
71
+ `mcpxray check` turns findings into a traffic-light verdict instead of a raw score:
72
+
73
+ | Verdict | When | Exit | What to do |
74
+ | --- | --- | --- | --- |
75
+ | 🟢 **OK** | nothing found | 0 | Safe to add to Claude Code. |
76
+ | 🟡 **CAUTION** | no errors, but warnings (weak schemas, unpinned deps, …) | 0 | Usable — mind the listed weaknesses. |
77
+ | 🔴 **DANGER** | any error (tool poisoning, leaked secrets, RCE) | 1 | **Do not install.** |
78
+ | ⚪ **UNKNOWN** | no MCP tools found statically (unsupported language, or tools built at runtime) | 0 | Can't check statically — capture `tools/list` (`--manifest`) or spawn the server (`--runtime --command`). |
79
+
80
+ The numeric score (0–100, shown as a secondary detail) still follows the error-cap rule below: any error caps it at 60. `--fail-under N` adds a CI gate that is independent of the verdict (it can turn a 🟡/🟢 into an exit-1 without changing the displayed verdict).
81
+
82
+ > **Languages:** mcpxray checks **Python and TypeScript** statically (FastMCP `@mcp.tool` and the TS SDK's `server.tool(...)` / `registerTool(...)` / low-level `ListToolsRequestSchema` shapes). Servers in other languages — or tools built dynamically at runtime — still return ⚪ UNKNOWN; capture `tools/list` (`mcpxray check --manifest dump.json`), or spawn the server and let mcpxray capture it live (`mcpxray check --runtime --command '<launch>'`).
83
+
84
+ ## Runtime capture (`--runtime --command`)
85
+
86
+ When source isn't parseable (compiled, 3rd-party, or tools built dynamically), `--runtime` **spawns the server, performs the MCP JSON-RPC handshake over stdio (`initialize` → `notifications/initialized` → `tools/list`), and feeds the captured tools through the same rules**. It's strictly opt-in and needs an explicit launch command:
87
+
88
+ ```bash
89
+ mcpxray check --runtime --command "python -m my_mcp_server"
90
+ mcpxray check --runtime --command "node dist/index.js" path/to/server # cwd = the path
91
+ ```
92
+
93
+ > ⚠️ **`--runtime` executes the server under inspection.** It is opt-in, runs the server with a bounded lifetime (timeouts + guaranteed teardown), and parses its response defensively — but provides **no OS-level sandbox** (no filesystem/network isolation). Only point it at servers you trust; for untrusted servers, run mcpxray inside a container or VM. Prefer `--manifest` when you already have a captured `tools/list`.
94
+
95
+ Runtime-captured tools carry no source text, so the source-scanning rules (MCP102 secrets / MCP103 RCE / MCP105 drift / MCP109 transport) can't fire — but schema and description rules (MCP104/106/107) still run on the captured definitions.
96
+
97
+ ## Auto-fix (`scan --fix` / `--diff`)
98
+
99
+ The one rule that's mechanically, unambiguously fixable is **MCP108** (unpinned dependencies): `scan --fix` pins a floating spec to its concrete floor version — `requests>=2.30.0` → `requests==2.30.0` (pip), `"zod": "^1.2.3"` → `"zod": "1.2.3"` (npm). It edits `pyproject.toml` / `package.json` in place (atomically); `--diff` prints the same changes as a unified diff and writes nothing.
100
+
101
+ ```bash
102
+ mcpxray scan path/to/server --diff # preview (exits 1 if changes are pending — CI-friendly)
103
+ mcpxray scan path/to/server --fix # apply in place
104
+ ```
105
+
106
+ What it does **not** touch:
107
+
108
+ - **No floor to pin** (`*`, `latest`, a bare `flask`, `>=2` with no patch) → skipped, left for you to resolve against a registry. mcpxray never invents a version.
109
+ - **Specs with extras/env markers** (`pkg[extra]>=1.2.3`, `pkg>=1.2.3 ; python_version>'3'`) → skipped (rewriting them textually is unsafe).
110
+ - **Every other rule** (MCP101–107, MCP109) → not auto-fixable; these need human judgment (a leaked secret isn't "fixed" by deleting it).
111
+
112
+ `--fix`/`--diff` are **static-source-only** — they rewrite files in place, so they reject `--manifest`, `--runtime`, and URL targets (point them at a local path). Every edit is a literal, uniquely-anchored replacement, so an ambiguous match is skipped rather than applied wrongly. Re-run `scan` after `--fix` to confirm the score improved.
113
+
114
+ ## Rules
115
+
116
+ Each finding is a `Diagnostic` with a severity (`error`/`warning`/`info`). **Any `error` caps the score at 60**, so a leaked secret or poisoned tool can never be diluted into a green grade.
117
+
118
+ | ID | OWASP MCP | Rule | Severity |
119
+ | --- | --- | --- | --- |
120
+ | MCP101 | Tool Poisoning | hidden instructions in a tool `description` ("ignore previous", exfiltrate-to-URL, hidden format/bidi chars) | error |
121
+ | MCP102 | Token/Secret Exposure | secrets/tokens/API keys in source (regex: private keys, `sk-`, AWS, GitHub, GitLab, Slack, Google, hardcoded creds) | error |
122
+ | MCP103 | Excess Permissions (MCP04) | dangerous capabilities: `os.system`, `eval`/`exec`, `pickle.loads`, `subprocess(..., shell=True)` | error |
123
+ | MCP104 | — (hygiene) | weak schema: no `required`, or a property with an empty `{}` schema | warning |
124
+ | MCP105 | — (correctness) | schema/implementation drift: a tool's declared `inputSchema` disagrees with its handler's parameters | warning |
125
+ | MCP106 | — (compat) | JSON-Schema incompatibilities that break Cursor/ChatGPT (`$ref`/`oneOf`/`anyOf`/`allOf`, missing `type`) | warning |
126
+ | MCP107 | — (context) | missing or oversized `description` (eats the model's context budget) | warning |
127
+ | MCP108 | Supply-Chain (MCP05) | unpinned dependencies with no lockfile (pip **and** npm — `^`/`~`/`>=`/`*` drift) | warning |
128
+ | MCP109 | — (transport) | HTTP/SSE transport exposed without TLS or authentication | warning |
129
+
130
+ ## Score & grades
131
+
132
+ | Grade | Score | Meaning |
133
+ | --- | --- | --- |
134
+ | **A** | 90–100 | Clean |
135
+ | **B** | 80–89 | Minor warnings |
136
+ | **C** | 70–79 | Some hygiene debt |
137
+ | **D** | 60–69 | Serious — errors present (capped) |
138
+ | **F** | 0–59 | Critical — errors present (capped) |
139
+
140
+ Deductions: `error` = −20, `warning` = −6, `info` = −1, clamped to `[0, 100]`.
141
+
142
+ ## CI
143
+
144
+ ```yaml
145
+ # .github/workflows/mcp.yml
146
+ name: mcpxray
147
+ on: [push, pull_request]
148
+ jobs:
149
+ lint:
150
+ runs-on: ubuntu-latest
151
+ steps:
152
+ - uses: actions/checkout@v4
153
+ - uses: astral-sh/setup-uv@v5
154
+ - run: uv tool install mcpxray-cli
155
+ # GitHub annotations + SARIF-friendly; fails on any ERROR
156
+ - run: mcpxray scan ./src --check -f github
157
+ # Optional: fail below a score bar
158
+ - run: mcpxray score ./src --fail-under 80
159
+ ```
160
+
161
+ ## Pre-commit hook
162
+
163
+ Lint on every commit from any MCP-server repo. Add `mcpxray` to your `.pre-commit-config.yaml`:
164
+
165
+ ```yaml
166
+ repos:
167
+ - repo: https://github.com/cloudroad-io/mcpxray
168
+ rev: v1.0.0 # pin to a release tag
169
+ hooks:
170
+ - id: mcpxray
171
+ args: ["./src"] # path to your server source
172
+ ```
173
+
174
+ The hook runs `mcpxray scan --check <path>` and fails the commit on any ERROR (tool poisoning, leaked secrets, RCE). Requires the `mcpxray-cli` distribution on PyPI; if you install it locally instead (`uv tool install mcpxray-cli`), set `language: system` on the hook.
175
+
176
+ ## How it works
177
+
178
+ 1. **Extract.** `PythonExtractor` walks `.py` files, finds `@mcp.tool` / `@server.tool` decorators, and lifts `name`, the docstring (→ `description`) and type hints (→ JSON Schema) straight from the AST — **no imports, no execution**. `ManifestExtractor` parses a captured `tools/list` JSON dump for servers in any language. For servers you can run, `--runtime --command` spawns it and captures `tools/list` live (`runtime.py`).
179
+ 2. **Lint.** Each rule sees the resulting `McpServer` IR and emits `Diagnostic`s.
180
+ 3. **Score.** Findings collapse to a 0–100 score with an error cap (mirrors OpenSSF Scorecard's shape).
181
+ 4. **Report.** `plain`, `json`, `github` annotations, or `sarif` (for GitHub code scanning).
182
+
183
+ See [ARCHITECTURE.md](ARCHITECTURE.md) for the full pipeline and [CONTRIBUTING.md](CONTRIBUTING.md) to add a rule or extractor in one file.
184
+
185
+ ## Why mcpxray
186
+
187
+ - **Static, not runtime.** No need to start the server or trust what it reports at runtime — parse definitions from source. Deterministic and CI-safe.
188
+ - **MCP-semantic.** Knows about tool poisoning, schema compatibility, over-permissive tools — things generic SAST and OpenSSF Scorecard can't see.
189
+ - **Source-level locations.** Findings point at `file:line`, not anonymous runtime entries.
190
+ - **Plugin-friendly.** Add a rule or an extractor by subclassing + one decorator. Entry-points let external packages extend `mcpxray` without forking.
191
+
192
+ ## Dogfood
193
+
194
+ Scanned against the official [`modelcontextprotocol/python-sdk`](https://github.com/modelcontextprotocol/python-sdk) example servers:
195
+
196
+ | Server | Score | Findings |
197
+ | --- | --- | --- |
198
+ | `everything-server` | **82/100 (B)** | 3 warnings — an untyped `region` param, an all-optional schema, unpinned deps |
199
+ | `examples/mcpserver` | **58/100 (F)** | 7 warnings — missing descriptions, untyped Pydantic-model params |
200
+
201
+ (FastMCP's framework-injected `ctx: Context` parameter is correctly excluded from every tool's schema — the same way the SDK itself does it — so it doesn't generate noise.)
202
+
203
+ ## Roadmap
204
+
205
+ - **v0.2** — scope URL clones to the server entry point, TypeScript static extractor, rules MCP105/109, opt-in runtime `tools/list` capture (`--runtime --command`). ✅ shipped.
206
+ - **v1.0** — frozen plugin API (SemVer), `--fix`/`--diff`, pre-commit hook, GitHub Actions CI, PyPI trusted publishing. ✅ shipped (v1.0.0).
207
+ - **Later** (needs external services/accounts): hosted badge API + leaderboard (hosting), registry integrations (Glama/Smithery API keys). Full history: [`docs/v0.2-plan.md`](docs/v0.2-plan.md).
208
+
209
+ ## License
210
+
211
+ MIT.
@@ -0,0 +1,33 @@
1
+ mcpxray/__init__.py,sha256=nZYWjUiRg1eHxR3iZ6PH1GxzhvZLOA5joY8KUl_Aaos,758
2
+ mcpxray/__main__.py,sha256=CyHBzis7Y1UWrNvxxww5mh4x640tNWNfM2pcfKVuiO0,110
3
+ mcpxray/badge.py,sha256=zqaLDq3l09KakghyO75JpIilWJTIIpqDF3McJ-uuyy4,1727
4
+ mcpxray/cli.py,sha256=SrSiJajR-RWm0WuwEg-CPl-7A01q1yzOBWnQC2hX8Zw,11950
5
+ mcpxray/extract/__init__.py,sha256=HT7wYQraJdo5pzOAhQC_8Rzu6X984CrYBZVKezyBQQM,614
6
+ mcpxray/extract/base.py,sha256=_jF7N7yTzjgMYXQ9eL0jQboYtga-YP0FHlMefaQRmwA,2105
7
+ mcpxray/extract/manifest.py,sha256=xJ0Iz2KjjyD3GNqyaRgz-iFqG8LJFbS3QNN6lXhXKx4,3286
8
+ mcpxray/extract/python_static.py,sha256=EfTX-uqeGxmPcWDyBpV9yoLRIH5FZ2jVjkPBg4yaXOE,10946
9
+ mcpxray/extract/typescript_static.py,sha256=F9EcvJwmXRqDG13GzLmBFWcHwVjQ4S_vgZA5p4m7Fbc,19346
10
+ mcpxray/fix.py,sha256=w8o7og8Orsi_jYquGpHhA5TUVuvAP5qGfVRj80nHw_U,6085
11
+ mcpxray/ir.py,sha256=LPv_FDLcKKro7wkYYpf0yI18XqnTqkjPvzac-40t_Co,5893
12
+ mcpxray/report/__init__.py,sha256=D-6g56VCC_qEJTGce_q_enemhTbPn-S8BlWS0natjmM,1327
13
+ mcpxray/report/card.py,sha256=MlpmGSiLmxeyJqbkjyHHPsV_GX2xJAWktwGsIUA7N-U,4657
14
+ mcpxray/report/github.py,sha256=fvHMAnO0hPOV-l3jbOuWMuhDQZPL7qga8kVezhNF9Hw,927
15
+ mcpxray/report/json.py,sha256=oYm8ZxhSJabxdBEVrokptrboCLxcRFFDD6-JEqRMFhk,1371
16
+ mcpxray/report/plain.py,sha256=on3CsEKHmIPoZ8mqEhAcZTgHCbnNOqYstBBOU9vWTU4,1149
17
+ mcpxray/report/sarif.py,sha256=J5kome8-GESOM0cj4VNcf11RQLeAMsfJajDVb63UN3o,2225
18
+ mcpxray/rules/__init__.py,sha256=flv_Y2l0OWoiQyGvaPDp7j8dR7dcFLCK9BeFV6ovRbU,272
19
+ mcpxray/rules/base.py,sha256=M70ADgZUToifmVcO-xDf3dwgQYnb4e6US5_lsIrH7zQ,2060
20
+ mcpxray/rules/builtin/__init__.py,sha256=6cAvmmZuubIJMGb2G9mD6hyK0wMW2wLv2LcN45bY0TA,204
21
+ mcpxray/rules/builtin/descriptions.py,sha256=R9wi8IffiksrUjF7ymyYufkmuzlejX_F9sS31QtfcRg,3782
22
+ mcpxray/rules/builtin/schema.py,sha256=SJrBR3FXqWmNvACBcvRP4TMQe2zxhvjFc96ByfeOpTo,4572
23
+ mcpxray/rules/builtin/source.py,sha256=Lc6cTDoEz1Y-jIU2IHDMLKRjh1ziRpsZ1ssxck-l368,3455
24
+ mcpxray/rules/builtin/supply.py,sha256=gHGOVM-C9-NIp0Mjhipdfmt1lbDwHogvtui3gGWURes,3613
25
+ mcpxray/rules/builtin/transport.py,sha256=BZPoGbZIGuH7ydNL8lZMPA1K5ycOUtEnw5Z8QVSj8g8,2992
26
+ mcpxray/runtime.py,sha256=b37G3xN8dndHJ4q2NL-tmcbu1RyZP5rfXrN_FREDXW0,9445
27
+ mcpxray/score.py,sha256=5057bBxBjMO5uQ40qLTzjbFa2MVMRDZ1yhaq8p3rqZU,2096
28
+ mcpxray/source.py,sha256=v-yiXCuou01DpBhoIsGhRlKwwgMLUnAdaABw2nH2MpE,10234
29
+ mcpxray/verdict.py,sha256=UtxVsDzYKFJHV_luGP6BONLHsS2AyAquKH6CzpG_JCk,4747
30
+ mcpxray_cli-1.0.0.dist-info/WHEEL,sha256=4OL6Foqnnp3xRY5wMkjgc25_i5YJC6dKsC6LPcjqEoU,80
31
+ mcpxray_cli-1.0.0.dist-info/entry_points.txt,sha256=3SdLEVCPDwyDhHRvzjvUFl-ecAiLhktfMxEq1_HAEV0,84
32
+ mcpxray_cli-1.0.0.dist-info/METADATA,sha256=lDgyiZq3rmLHjLOBd_f1DNwAn8M1ZErjkmG4KeLVB8Q,13207
33
+ mcpxray_cli-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: uv 0.12.5
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,7 @@
1
+ [console_scripts]
2
+ mcpxray = mcpxray.cli:app
3
+
4
+ [mcpxray.extractors]
5
+
6
+ [mcpxray.rules]
7
+