mcplint-cli 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.
- mcplint/__about__.py +1 -0
- mcplint/__init__.py +3 -0
- mcplint/benchmark/__init__.py +0 -0
- mcplint/benchmark/dataset.py +34 -0
- mcplint/benchmark/providers/__init__.py +0 -0
- mcplint/benchmark/providers/anthropic_provider.py +92 -0
- mcplint/benchmark/providers/base.py +26 -0
- mcplint/benchmark/providers/factory.py +51 -0
- mcplint/benchmark/providers/fake.py +26 -0
- mcplint/benchmark/providers/openai_provider.py +25 -0
- mcplint/benchmark/runner.py +43 -0
- mcplint/benchmark/scorer.py +147 -0
- mcplint/cli/__init__.py +0 -0
- mcplint/cli/commands/__init__.py +0 -0
- mcplint/cli/commands/benchmark_cmd.py +88 -0
- mcplint/cli/commands/compare_cmd.py +139 -0
- mcplint/cli/commands/fix_cmd.py +60 -0
- mcplint/cli/commands/inspect_cmd.py +44 -0
- mcplint/cli/commands/rules_cmd.py +30 -0
- mcplint/cli/commands/scan_cmd.py +113 -0
- mcplint/cli/commands/snapshot_cmd.py +33 -0
- mcplint/cli/main.py +49 -0
- mcplint/compare/__init__.py +0 -0
- mcplint/compare/differ.py +148 -0
- mcplint/config/__init__.py +0 -0
- mcplint/config/loader.py +36 -0
- mcplint/config/schema.py +40 -0
- mcplint/core/__init__.py +0 -0
- mcplint/core/engine.py +40 -0
- mcplint/core/registry.py +52 -0
- mcplint/core/rules/__init__.py +0 -0
- mcplint/core/rules/ambiguity.py +157 -0
- mcplint/core/rules/ambiguity_rules.py +109 -0
- mcplint/core/rules/base.py +39 -0
- mcplint/core/rules/builtin.py +45 -0
- mcplint/core/rules/completeness_rules.py +217 -0
- mcplint/core/rules/description_rules.py +110 -0
- mcplint/core/rules/safety_rules.py +139 -0
- mcplint/core/rules/schema_rules.py +101 -0
- mcplint/core/score.py +132 -0
- mcplint/fix/__init__.py +0 -0
- mcplint/fix/suggest.py +183 -0
- mcplint/mcp_client/__init__.py +0 -0
- mcplint/mcp_client/canonical.py +25 -0
- mcplint/mcp_client/persistence.py +17 -0
- mcplint/mcp_client/session.py +80 -0
- mcplint/mcp_client/stdio.py +17 -0
- mcplint/models/__init__.py +0 -0
- mcplint/models/benchmark.py +67 -0
- mcplint/models/common.py +23 -0
- mcplint/models/comparison.py +53 -0
- mcplint/models/contracts.py +39 -0
- mcplint/models/findings.py +44 -0
- mcplint/models/fixes.py +13 -0
- mcplint/models/score.py +25 -0
- mcplint/models/snapshot.py +27 -0
- mcplint/py.typed +0 -0
- mcplint/reporters/__init__.py +0 -0
- mcplint/reporters/benchmark_terminal.py +46 -0
- mcplint/reporters/comparison_terminal.py +61 -0
- mcplint/reporters/fix_markdown.py +34 -0
- mcplint/reporters/html.py +71 -0
- mcplint/reporters/json_reporter.py +9 -0
- mcplint/reporters/sarif.py +77 -0
- mcplint/reporters/templates/report.html.j2 +176 -0
- mcplint/reporters/terminal.py +52 -0
- mcplint_cli-0.1.0.dist-info/METADATA +392 -0
- mcplint_cli-0.1.0.dist-info/RECORD +71 -0
- mcplint_cli-0.1.0.dist-info/WHEEL +4 -0
- mcplint_cli-0.1.0.dist-info/entry_points.txt +2 -0
- mcplint_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mcplint-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Lint and benchmark MCP tool contracts before ambiguous descriptions break LLM tool selection.
|
|
5
|
+
Project-URL: Homepage, https://github.com/akashkokare2910/mcplint
|
|
6
|
+
Project-URL: Repository, https://github.com/akashkokare2910/mcplint
|
|
7
|
+
Project-URL: Issues, https://github.com/akashkokare2910/mcplint/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/akashkokare2910/mcplint/blob/main/CHANGELOG.md
|
|
9
|
+
Author-email: Akash Kokare <akashkokare2017@gmail.com>
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: agent,ai,evaluation,lint,llm,mcp,model-context-protocol,tool-calling
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
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.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
23
|
+
Classifier: Topic :: Software Development :: Testing
|
|
24
|
+
Classifier: Typing :: Typed
|
|
25
|
+
Requires-Python: >=3.11
|
|
26
|
+
Requires-Dist: anyio>=4.4
|
|
27
|
+
Requires-Dist: httpx>=0.27
|
|
28
|
+
Requires-Dist: jinja2>=3.1
|
|
29
|
+
Requires-Dist: jsonschema>=4.22
|
|
30
|
+
Requires-Dist: mcp>=1.28
|
|
31
|
+
Requires-Dist: pydantic>=2.7
|
|
32
|
+
Requires-Dist: pyyaml>=6.0
|
|
33
|
+
Requires-Dist: rich>=13.7
|
|
34
|
+
Requires-Dist: typer>=0.12
|
|
35
|
+
Provides-Extra: anthropic
|
|
36
|
+
Requires-Dist: anthropic>=0.34; extra == 'anthropic'
|
|
37
|
+
Provides-Extra: dev
|
|
38
|
+
Requires-Dist: anthropic>=0.34; extra == 'dev'
|
|
39
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
40
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
41
|
+
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
42
|
+
Requires-Dist: pytest>=8.2; extra == 'dev'
|
|
43
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
44
|
+
Requires-Dist: types-jsonschema; extra == 'dev'
|
|
45
|
+
Requires-Dist: types-pyyaml; extra == 'dev'
|
|
46
|
+
Provides-Extra: openai
|
|
47
|
+
Requires-Dist: openai>=1.40; extra == 'openai'
|
|
48
|
+
Provides-Extra: semantic
|
|
49
|
+
Requires-Dist: sentence-transformers>=3.0; extra == 'semantic'
|
|
50
|
+
Description-Content-Type: text/markdown
|
|
51
|
+
|
|
52
|
+
# MCPLint
|
|
53
|
+
|
|
54
|
+
**Your MCP server can pass schema validation while the model still chooses the wrong tool.**
|
|
55
|
+
|
|
56
|
+
MCPLint is an "ESLint for MCP tool contracts." It connects to a [Model
|
|
57
|
+
Context Protocol](https://modelcontextprotocol.io) server, retrieves its
|
|
58
|
+
tool definitions, detects description and schema problems that cause LLM
|
|
59
|
+
agents to select the wrong tool or construct invalid arguments, suggests
|
|
60
|
+
improvements, and benchmarks tool-selection behaviour before and after
|
|
61
|
+
changes.
|
|
62
|
+
|
|
63
|
+
A tool can have a perfectly valid `inputSchema` and still be a trap for an
|
|
64
|
+
agent: two tools whose descriptions overlap, a destructive action with no
|
|
65
|
+
warning, a required parameter whose constraints are never mentioned in
|
|
66
|
+
prose. JSON Schema validation doesn't catch any of that. MCPLint does.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## 60-second quick start
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
pip install mcplint-cli
|
|
74
|
+
|
|
75
|
+
# Point it at any MCP server's launch command
|
|
76
|
+
mcplint scan --server "python my_server.py"
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
That's it — no config file, no API key, no LLM required for the core
|
|
80
|
+
linter. `mcplint scan` connects over stdio, retrieves the tool list, runs
|
|
81
|
+
15 deterministic rules, and prints a scored report with a non-zero exit
|
|
82
|
+
code when something's wrong (so it gates a PR the same way `eslint` does).
|
|
83
|
+
|
|
84
|
+
## Example output
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
$ mcplint scan --server "python examples/bad_server/server.py"
|
|
88
|
+
|
|
89
|
+
bad-customer-server — 34 finding(s) (6 error, 13 warning, 15 info) — score: 32/100
|
|
90
|
+
-8.0 1 critical/error finding(s) x 8 pts (capped at 40)
|
|
91
|
+
-20.0 18 warning/info finding(s) x 2 pts (capped at 20)
|
|
92
|
+
-10.0 2 ambiguity finding(s) x 5 pts (capped at 15)
|
|
93
|
+
-15.0 9 schema-completeness finding(s) x 3 pts (capped at 15)
|
|
94
|
+
-15.0 4 safety-clarity finding(s) x 5 pts (capped at 15)
|
|
95
|
+
|
|
96
|
+
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
|
97
|
+
┃ Rule ┃ Severity ┃ Tool ┃ Message ┃
|
|
98
|
+
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
|
|
99
|
+
│ missing-tool-description │ error │ ping │ Tool 'ping' has no description. │
|
|
100
|
+
│ description-repeats-name │ warning │ get_status │ Description only restates its name. │
|
|
101
|
+
│ vague-tool-description │ warning │ get_status │ Description is very short (2 words). │
|
|
102
|
+
│ missing-parameter-description │ warning │ fetch_record │ Parameter 'record_id' has no description. │
|
|
103
|
+
│ ... │ │ │ (30 more) │
|
|
104
|
+
└──────────────────────────────────┴──────────┴───────────────┴────────────────────────────────────────────┘
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Every finding carries a rule ID, severity, evidence, remediation text, and
|
|
108
|
+
a confidence score — heuristic findings are never presented as certain (see
|
|
109
|
+
`undocumented-error-behaviour` above at confidence 0.5 vs.
|
|
110
|
+
`missing-tool-description` at 1.0).
|
|
111
|
+
|
|
112
|
+
## Architecture
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
mcp_client/ stdio connection to the MCP server -> MCPServerSnapshot
|
|
116
|
+
(untrusted-process boundary; canonicalization for stable JSON)
|
|
117
|
+
core/ Rule ABC + RuleRegistry + lint_snapshot() engine
|
|
118
|
+
core/rules/ 15 built-in rules + the cross-tool ambiguity engine
|
|
119
|
+
core/score.py explainable 0-100 score
|
|
120
|
+
config/ mcplint.yaml loading (severity overrides, thresholds, ignores)
|
|
121
|
+
benchmark/ dataset format, ToolCallingProvider protocol, scorer, runner
|
|
122
|
+
providers/ fake (tests), anthropic (real), openai (stub)
|
|
123
|
+
compare/ pure diff functions between two snapshots/lint reports/benchmarks
|
|
124
|
+
fix/ deterministic rewrite suggestions from JSON Schema + annotations
|
|
125
|
+
models/ every persisted artifact as a typed Pydantic model
|
|
126
|
+
reporters/ terminal (Rich), JSON, SARIF 2.1.0, standalone HTML, Markdown
|
|
127
|
+
cli/ Typer commands — thin wrappers around the modules above
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
The deterministic linter (`core/`) never imports an LLM client. Only
|
|
131
|
+
`benchmark/providers/anthropic_provider.py` (and the `openai` stub) touch a
|
|
132
|
+
model API, and only when you explicitly ask for `--provider anthropic`.
|
|
133
|
+
|
|
134
|
+
## Rule catalogue
|
|
135
|
+
|
|
136
|
+
Run `mcplint rules` for the live list (id, title, default severity, tags).
|
|
137
|
+
All 15 are deterministic — no LLM key required.
|
|
138
|
+
|
|
139
|
+
| Rule | Default severity | What it catches |
|
|
140
|
+
|---|---|---|
|
|
141
|
+
| `missing-tool-description` | error | No description at all |
|
|
142
|
+
| `description-repeats-name` | warning | Description just restates the tool name |
|
|
143
|
+
| `vague-tool-description` | warning | Description under 4 words |
|
|
144
|
+
| `missing-parameter-description` | warning | A parameter with no description |
|
|
145
|
+
| `missing-return-semantics` | warning | No outputSchema and no return-related wording |
|
|
146
|
+
| `undocumented-error-behaviour` | info | Description never mentions failure/error conditions |
|
|
147
|
+
| `undocumented-required-constraint` | warning | Required param's enum/min/max not mentioned in prose |
|
|
148
|
+
| `schema-description-type-conflict` | error | Description implies a type the schema doesn't declare |
|
|
149
|
+
| `tool-name-action-conflict` | error | Name reads read-only but is annotated destructive |
|
|
150
|
+
| `destructive-tool-without-warning` | error | Destructive annotation, no warning in the description |
|
|
151
|
+
| `state-changing-tool-marked-read-only` | error | Name reads as a mutation but `readOnlyHint` is true |
|
|
152
|
+
| `ambiguous-tool-overlap` | warning | Two tools score above the ambiguity threshold |
|
|
153
|
+
| `missing-tool-distinction` | info | Ambiguous pair never states when to use which |
|
|
154
|
+
| `excessive-description-length` | info | Description over the configured character limit |
|
|
155
|
+
| `undefined-domain-term` | info | An unexplained acronym/jargon term |
|
|
156
|
+
|
|
157
|
+
See `examples/bad_server/server.py` for one deliberate trigger per rule, and
|
|
158
|
+
`examples/good_server/server.py` for a server that scores 0 findings against
|
|
159
|
+
all 15.
|
|
160
|
+
|
|
161
|
+
### The ambiguity engine
|
|
162
|
+
|
|
163
|
+
`ambiguous-tool-overlap` and `missing-tool-distinction` are backed by
|
|
164
|
+
`core/rules/ambiguity.py::compute_ambiguity`, a pairwise score (0-1) built
|
|
165
|
+
from:
|
|
166
|
+
|
|
167
|
+
- name token similarity (25%)
|
|
168
|
+
- description token similarity, stopword-filtered (45%)
|
|
169
|
+
- parameter-name overlap (30%)
|
|
170
|
+
|
|
171
|
+
Every flagged pair carries **evidence**, not just a number: shared verbs,
|
|
172
|
+
shared entities, overlapping parameters, and three explicit booleans for
|
|
173
|
+
whether the pair states an exact-vs-search, one-vs-many, or read-vs-write
|
|
174
|
+
distinction. This is deliberately explainable rather than an opaque
|
|
175
|
+
embedding score — see `examples/ambiguous_customer_server` for a worked
|
|
176
|
+
example (`get_customer` vs `search_customers`).
|
|
177
|
+
|
|
178
|
+
An optional `semantic` extra (`sentence-transformers`) is planned as an
|
|
179
|
+
additional signal on top of this token-based score, not a replacement —
|
|
180
|
+
not yet wired in (see Limitations).
|
|
181
|
+
|
|
182
|
+
## Configuration
|
|
183
|
+
|
|
184
|
+
`mcplint.yaml`, auto-loaded from the current directory (`--config` to
|
|
185
|
+
override the path):
|
|
186
|
+
|
|
187
|
+
```yaml
|
|
188
|
+
severity:
|
|
189
|
+
missing-tool-description: error
|
|
190
|
+
excessive-description-length: info
|
|
191
|
+
|
|
192
|
+
thresholds:
|
|
193
|
+
ambiguity: 0.78
|
|
194
|
+
max_description_characters: 800
|
|
195
|
+
|
|
196
|
+
ignore:
|
|
197
|
+
- tool: internal_debug
|
|
198
|
+
rules:
|
|
199
|
+
- missing-return-semantics
|
|
200
|
+
|
|
201
|
+
benchmark:
|
|
202
|
+
provider: anthropic
|
|
203
|
+
model: claude-sonnet
|
|
204
|
+
runs: 3
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Invalid config fails with a specific, actionable field-level error rather
|
|
208
|
+
than a generic traceback.
|
|
209
|
+
|
|
210
|
+
## Benchmark guide
|
|
211
|
+
|
|
212
|
+
Deterministic linting catches contract problems; the benchmark measures
|
|
213
|
+
whether a real model actually picks the right tool.
|
|
214
|
+
|
|
215
|
+
```yaml
|
|
216
|
+
# evals.yaml
|
|
217
|
+
name: customer-tools
|
|
218
|
+
version: "1"
|
|
219
|
+
cases:
|
|
220
|
+
- id: exact-customer-lookup
|
|
221
|
+
prompt: Retrieve customer CUST-1042.
|
|
222
|
+
expected:
|
|
223
|
+
tool: get_customer
|
|
224
|
+
arguments:
|
|
225
|
+
customer_id: CUST-1042
|
|
226
|
+
forbidden_tools:
|
|
227
|
+
- delete_customer
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
# No API key needed — dry-run against a live server
|
|
232
|
+
mcplint benchmark evals.yaml --server "python my_server.py" --provider fake
|
|
233
|
+
|
|
234
|
+
# Real model
|
|
235
|
+
export ANTHROPIC_API_KEY=sk-...
|
|
236
|
+
mcplint benchmark evals.yaml --server "python my_server.py" \
|
|
237
|
+
--provider anthropic --model claude-sonnet-5 --runs 3
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Metrics are entirely deterministic: exact tool-selection accuracy,
|
|
241
|
+
valid-argument rate (checked against the tool's real JSON Schema),
|
|
242
|
+
required-argument accuracy, forbidden-tool invocation rate, no-tool rate,
|
|
243
|
+
mean/P95 latency, estimated cost, and per-case stability across repeated
|
|
244
|
+
trials. **No LLM judge is used anywhere** — every pass/fail is a literal
|
|
245
|
+
comparison.
|
|
246
|
+
|
|
247
|
+
`examples/ambiguous_customer_server/customer-tools.evals.yaml` is a worked
|
|
248
|
+
example showing get/search/update/delete-customer confusion.
|
|
249
|
+
|
|
250
|
+
## Compare & fix
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
mcplint snapshot --server "python my_server.py" --output before.json
|
|
254
|
+
# ... edit your tool descriptions ...
|
|
255
|
+
mcplint snapshot --server "python my_server.py" --output after.json
|
|
256
|
+
|
|
257
|
+
mcplint compare --baseline before.json --candidate after.json \
|
|
258
|
+
--dataset evals.yaml --provider fake --min-accuracy-delta -0.02
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
`compare` diffs tool contracts (added/removed tools, schema/description
|
|
262
|
+
changes), findings (new vs. resolved), and ambiguity scores between two
|
|
263
|
+
snapshots, and — if you pass `--dataset` — re-runs the benchmark against
|
|
264
|
+
both tool lists and reports the accuracy/latency/cost deltas. Exits 1 if
|
|
265
|
+
`--min-accuracy-delta` isn't met, so it gates a PR.
|
|
266
|
+
|
|
267
|
+
```bash
|
|
268
|
+
mcplint fix --snapshot before.json --output fix-report.md
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
`fix` proposes rewrites for whatever it can derive mechanically from the
|
|
272
|
+
JSON Schema and annotations (output shape, enum values, numeric bounds,
|
|
273
|
+
destructive warnings, tool-distinction placeholders). It **never writes to
|
|
274
|
+
your source files** — only a Markdown patch report you review by hand.
|
|
275
|
+
Purely semantic issues (a vague description, one that just restates the
|
|
276
|
+
name) get an honest low-confidence TODO placeholder, since the
|
|
277
|
+
deterministic engine has no LLM to invent real prose.
|
|
278
|
+
|
|
279
|
+
## CI guide
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
mcplint scan --server "python my_server.py" --format sarif --output results.sarif --fail-on error
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
`--fail-on error` (the default) exits 1 on any error-severity finding, 0
|
|
286
|
+
otherwise; `--fail-on never` always exits 0 (useful for a report-only job).
|
|
287
|
+
See `.github/workflows/example-scan-mcp-server.yml` for a complete,
|
|
288
|
+
documented example workflow: install MCPLint, invoke your server, scan,
|
|
289
|
+
upload SARIF to GitHub code scanning, fail on error. No custom JavaScript
|
|
290
|
+
GitHub Action is required.
|
|
291
|
+
|
|
292
|
+
## Plugin guide
|
|
293
|
+
|
|
294
|
+
Third-party rules register via a Python entry point — no changes to this
|
|
295
|
+
repo needed:
|
|
296
|
+
|
|
297
|
+
```toml
|
|
298
|
+
# your_package's pyproject.toml
|
|
299
|
+
[project.entry-points."mcplint.rules"]
|
|
300
|
+
my-custom-rule = "your_package.rules:MyCustomRule"
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
```python
|
|
304
|
+
from mcplint.core.rules.base import Rule, RuleContext
|
|
305
|
+
from mcplint.models.contracts import ToolContract
|
|
306
|
+
from mcplint.models.findings import Finding, Severity
|
|
307
|
+
|
|
308
|
+
class MyCustomRule(Rule):
|
|
309
|
+
id = "my-custom-rule"
|
|
310
|
+
title = "My custom rule"
|
|
311
|
+
description = "Explain what this catches."
|
|
312
|
+
default_severity = Severity.WARNING
|
|
313
|
+
|
|
314
|
+
def check(self, tool: ToolContract, context: RuleContext) -> list[Finding]:
|
|
315
|
+
...
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
`RuleRegistry().load_entry_point_plugins()` discovers everything registered
|
|
319
|
+
under the `mcplint.rules` group and merges it with the 15 built-ins.
|
|
320
|
+
|
|
321
|
+
## Limitations
|
|
322
|
+
|
|
323
|
+
- Stdio transport only — HTTP MCP servers aren't supported yet (planned:
|
|
324
|
+
connection timeout, response-size limit, header redaction; see
|
|
325
|
+
`SECURITY.md`).
|
|
326
|
+
- The ambiguity engine's token overlap does no stemming/lemmatization, so
|
|
327
|
+
"customer" vs. "customers" reduces measured similarity between genuinely
|
|
328
|
+
related tools. Real, observed trade-off — not a bug — see
|
|
329
|
+
`IMPLEMENTATION_STATUS.md`.
|
|
330
|
+
- `undefined-domain-term`'s acronym heuristic is intentionally conservative
|
|
331
|
+
(info severity, confidence 0.4) and will both under- and over-flag.
|
|
332
|
+
- The OpenAI benchmark provider is a typed stub, not a working
|
|
333
|
+
implementation, per the spec's "don't let it block Anthropic" guidance.
|
|
334
|
+
- `fix --llm-provider` is accepted as a flag but always rejected — no
|
|
335
|
+
LLM-assisted rewriting path exists yet.
|
|
336
|
+
- The overall score is a documented, capped-per-category heuristic, **not**
|
|
337
|
+
a scientifically validated or universal quality metric. It exists so a
|
|
338
|
+
regression in one category can't silently zero out the total.
|
|
339
|
+
|
|
340
|
+
Full detail, including every deferred item and the reasoning behind it, is
|
|
341
|
+
in `IMPLEMENTATION_STATUS.md`.
|
|
342
|
+
|
|
343
|
+
## Roadmap
|
|
344
|
+
|
|
345
|
+
- HTTP MCP server transport with the security constraints above
|
|
346
|
+
- Sentence-transformer embeddings as an additional ambiguity signal
|
|
347
|
+
- OpenAI benchmark provider
|
|
348
|
+
- LLM-assisted rewriting for `mcplint fix`
|
|
349
|
+
- A JavaScript GitHub Action wrapper, if the composite-workflow approach
|
|
350
|
+
proves insufficient for real users
|
|
351
|
+
|
|
352
|
+
## Comparison
|
|
353
|
+
|
|
354
|
+
- **[MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector)**
|
|
355
|
+
is an interactive debugger for poking at a running MCP server by hand.
|
|
356
|
+
MCPLint is a non-interactive, deterministic linter meant to run
|
|
357
|
+
unattended in CI — the two are complementary, not competing.
|
|
358
|
+
- **Generic JSON Schema diff/validation tools** verify that a schema is
|
|
359
|
+
well-formed and check argument values against it. They cannot tell you
|
|
360
|
+
that two tools are semantically confusable, that a destructive tool lacks
|
|
361
|
+
a warning, or that a required constraint isn't documented in prose —
|
|
362
|
+
those require understanding the *description*, not just the schema.
|
|
363
|
+
- **Generic LLM evaluation frameworks** (promptfoo, LangSmith evals, etc.)
|
|
364
|
+
can benchmark tool-selection accuracy, and MCPLint's `benchmark` command
|
|
365
|
+
covers similar ground for MCP tools specifically. MCPLint's distinct
|
|
366
|
+
contribution is the deterministic linter (rules 1-15) that runs without
|
|
367
|
+
any model key, plus tying static analysis and benchmark deltas together
|
|
368
|
+
in one `compare` command.
|
|
369
|
+
|
|
370
|
+
We don't claim MCPLint is strictly better than any of the above at what
|
|
371
|
+
they're actually built for — it's scoped specifically to the MCP tool
|
|
372
|
+
description/schema/ambiguity problem.
|
|
373
|
+
|
|
374
|
+
## Development
|
|
375
|
+
|
|
376
|
+
```bash
|
|
377
|
+
git clone https://github.com/mcplint/mcplint.git && cd mcplint
|
|
378
|
+
python3.11 -m venv .venv && source .venv/bin/activate
|
|
379
|
+
pip install -e ".[dev]"
|
|
380
|
+
|
|
381
|
+
ruff check src tests examples
|
|
382
|
+
mypy src
|
|
383
|
+
pytest --cov=mcplint --cov-report=term-missing
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
See `CONTRIBUTING.md` for the rule/provider extension guides,
|
|
387
|
+
`IMPLEMENTATION_STATUS.md` for exactly what's done vs. deferred, and
|
|
388
|
+
`CHANGELOG.md` for the phase-by-phase build history.
|
|
389
|
+
|
|
390
|
+
## License
|
|
391
|
+
|
|
392
|
+
MIT — see `LICENSE`.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
mcplint/__about__.py,sha256=kUR5RAFc7HCeiqdlX36dZOHkUI5wI6V_43RpEcD8b-0,22
|
|
2
|
+
mcplint/__init__.py,sha256=ca0-zflxm7XLn0GvipZJXMOkhasdJclz_owtLZnJMN8,69
|
|
3
|
+
mcplint/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
mcplint/benchmark/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
mcplint/benchmark/dataset.py,sha256=lfAY6jznYA1ikkonEpaS6yQWPjTwFgYE8gQWh2fA5yU,1049
|
|
6
|
+
mcplint/benchmark/runner.py,sha256=AvkLF_HwNnnbjwPOTpFhAz01t_TvAiony6goIUzD1Mc,1414
|
|
7
|
+
mcplint/benchmark/scorer.py,sha256=DZdgudAKQCFZeaAaSVxRx5gWiitTPFVmRh7KHBs3_AE,5284
|
|
8
|
+
mcplint/benchmark/providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
mcplint/benchmark/providers/anthropic_provider.py,sha256=YF7qotrnj4Gvt1r4_b9ti88PV5MhpSLtPv-hBtGGLJ4,3485
|
|
10
|
+
mcplint/benchmark/providers/base.py,sha256=Jn-0V2NT6s7v8nN1WQouneqUo61eaCV0Ih-mR_V_82I,687
|
|
11
|
+
mcplint/benchmark/providers/factory.py,sha256=9oxmmlKgySwrT5VeCGxHCcGJrtnM4kDp7FUvvLhssD0,1991
|
|
12
|
+
mcplint/benchmark/providers/fake.py,sha256=T3LuBKDTOdupXHt1pBmAvadu-2ggsOLMl4d5UfyCl6U,763
|
|
13
|
+
mcplint/benchmark/providers/openai_provider.py,sha256=DibmbvGSVqUnuvWK6Lj1mZJ-ReBxLx5UwK6Wz0wuH6k,940
|
|
14
|
+
mcplint/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
+
mcplint/cli/main.py,sha256=ij0Mo3apa83MiWzvt8GIsb8FAfztRKh03a90D2wa_4g,1349
|
|
16
|
+
mcplint/cli/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
+
mcplint/cli/commands/benchmark_cmd.py,sha256=0bLlWb9NpgqwFj7-pzm1V1WfahoSpvd91MqhMAp0_bw,3429
|
|
18
|
+
mcplint/cli/commands/compare_cmd.py,sha256=F1BgHWkjCWW3oeNGiC30Nia4wefjjNUJymFAmyS4AI4,5675
|
|
19
|
+
mcplint/cli/commands/fix_cmd.py,sha256=87FS7mcrvxmKcZMp58eSBfGqjB53gB4hNSWwbEVvb0g,1963
|
|
20
|
+
mcplint/cli/commands/inspect_cmd.py,sha256=EcdvUkhswzg8sg-4Z2NSNAP-P7NRrcIhQZKHOnJGQlU,1371
|
|
21
|
+
mcplint/cli/commands/rules_cmd.py,sha256=XYsBtyyOue3ah0oKqydbW1q-6Errxt-BV5-8f_BQIIw,749
|
|
22
|
+
mcplint/cli/commands/scan_cmd.py,sha256=Ej7WX8U_i9h67rrehV84HayCuGGnBcMNsjrPubI2KqA,4112
|
|
23
|
+
mcplint/cli/commands/snapshot_cmd.py,sha256=hQJOaZ_Ux46ND-0yveGzImboVqXXzJ3tVxHQwXWh90o,1162
|
|
24
|
+
mcplint/compare/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
25
|
+
mcplint/compare/differ.py,sha256=936Sc95xVWbDagX8_KP-GwE8TWnw0n5EsJlpTcMrId0,5995
|
|
26
|
+
mcplint/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
27
|
+
mcplint/config/loader.py,sha256=fPJfNrrxlp-nbrAtaTHYCESLxVp2gbzic2wqMluNvPM,1020
|
|
28
|
+
mcplint/config/schema.py,sha256=-RLrv0NWPh7s7pXEmMGaXHUU-Wr25Sp944hxYC9rmvA,1359
|
|
29
|
+
mcplint/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
30
|
+
mcplint/core/engine.py,sha256=ZRe_GlFwQWdReM9lZ8wHwAUhgjpRgh5x41Byq_yWaII,1438
|
|
31
|
+
mcplint/core/registry.py,sha256=yhvmj5W3_bNbzex5GE0korD6WvuUeeRn-iipdPQ3q5g,1865
|
|
32
|
+
mcplint/core/score.py,sha256=7Q5qgG4LMV172fIbv-hsY-5w4-fL2B-rWPotuwc0-oc,4907
|
|
33
|
+
mcplint/core/rules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
34
|
+
mcplint/core/rules/ambiguity.py,sha256=p4M2BR9L-ZbzUqpPywCNjdBReW0rCRfrYtq2T40nreA,5201
|
|
35
|
+
mcplint/core/rules/ambiguity_rules.py,sha256=pDGly9js3UqDjy3FoXJK4JbMWbFf1SB23ZWHSklLfiY,4693
|
|
36
|
+
mcplint/core/rules/base.py,sha256=AIsDyV783VUqhCpxC26gyZfiDoC23sOqL7RFP0l6A6M,1055
|
|
37
|
+
mcplint/core/rules/builtin.py,sha256=Ew3u_PPiG2sqDcn-LSVEgK5tmiDw4E_Vff2roAOpoYQ,1437
|
|
38
|
+
mcplint/core/rules/completeness_rules.py,sha256=ibqFdWFcuuIGG_nqvTqFYiIi1g-1uH5lLUpL5lk5Y8A,8478
|
|
39
|
+
mcplint/core/rules/description_rules.py,sha256=jCAGha0ivPbZ8yRd0gsbZwDzWUyJK0JuYoVKlSWeyZs,4241
|
|
40
|
+
mcplint/core/rules/safety_rules.py,sha256=BmAIXDvTBEX-hUIGH_kQEPSa0R8ocv18YporIFOztmg,5061
|
|
41
|
+
mcplint/core/rules/schema_rules.py,sha256=4oTRMWVn3OeeonUEgLAFB0q4teyJuuGF6SdU2a2AiI8,4397
|
|
42
|
+
mcplint/fix/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
43
|
+
mcplint/fix/suggest.py,sha256=lWTk_5ghnkVtHLtaGZNlqtLCg8dUOLKyif81s2Y9uVQ,7635
|
|
44
|
+
mcplint/mcp_client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
45
|
+
mcplint/mcp_client/canonical.py,sha256=bZ8UUBGLfJrZdvom3MPEj6VTN6I3kuGONuGudU_YJtE,799
|
|
46
|
+
mcplint/mcp_client/persistence.py,sha256=BQe2q2T0kS9X1wsinHEXa_1BRWx_jQvm2OmPgHaF57E,556
|
|
47
|
+
mcplint/mcp_client/session.py,sha256=MGifPhD9HWMzH-FvJtGTqsEF4QxpGui_p5jjV5wsCW0,2905
|
|
48
|
+
mcplint/mcp_client/stdio.py,sha256=3JASLZNorfOsmUExk0fpttVXabGIi539QY2AomEVa98,501
|
|
49
|
+
mcplint/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
50
|
+
mcplint/models/benchmark.py,sha256=ikQ-jDz1LUD7atuZrBCIS498Y72oqI4-IuSk7dwS8kU,1792
|
|
51
|
+
mcplint/models/common.py,sha256=aYgGmsXEoHZ2tYv6LJFQLbfMCJZLLA1-YH-NhdKXXGk,592
|
|
52
|
+
mcplint/models/comparison.py,sha256=wZsHSIrrQgvxonEe8HG_QHMqd43XecZ3aVN_eiBlIOc,1670
|
|
53
|
+
mcplint/models/contracts.py,sha256=mxBfMt95pUAIqgXc0I3gRGOYT9MR1ViXNBEbb3I0jx4,965
|
|
54
|
+
mcplint/models/findings.py,sha256=HuoWVzYtaAwblB-xuREPnsKrf0S23A3p01yCbca9sIQ,992
|
|
55
|
+
mcplint/models/fixes.py,sha256=Ce_EojAwyzmEIgQPZ679g_JQQD5cyescGIXhxvu7_iQ,371
|
|
56
|
+
mcplint/models/score.py,sha256=bV8lKGl5Ek7hYoodz0mT50sZXZsXTBspCuo3q_pVT2o,774
|
|
57
|
+
mcplint/models/snapshot.py,sha256=5zC1rgyaMc4x6ng4Hn40uBTHucmxc3mlr8fmEUfGHGc,701
|
|
58
|
+
mcplint/reporters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
59
|
+
mcplint/reporters/benchmark_terminal.py,sha256=9IDERvQunN1-Wt7JvOWKjiUZAwOYwpUD5AMjB7taWOo,1871
|
|
60
|
+
mcplint/reporters/comparison_terminal.py,sha256=byqzD78EtHEXYaqogh4HOqHiC4aldIFQT5uyn9CDQ0M,2511
|
|
61
|
+
mcplint/reporters/fix_markdown.py,sha256=Xkp5Gpfy_H9_ReWMxmMHZh9I3dMylrRevos5S-9iT_0,1246
|
|
62
|
+
mcplint/reporters/html.py,sha256=VDC9Eru4Th-ieRZCvz9nLJo04Y74qdbsxc9qyQUkWj4,2411
|
|
63
|
+
mcplint/reporters/json_reporter.py,sha256=SgPWPbCMBl859C6SAsb7ZsNHJNuXK63kLbCHYFQi5tA,209
|
|
64
|
+
mcplint/reporters/sarif.py,sha256=_gQMuguCgf2MNsItifdzcCr4XGHD15OeQh5fHr10JEY,2595
|
|
65
|
+
mcplint/reporters/terminal.py,sha256=11mxX8cJ2S8utdWpvD_krUMq-qiRQVvZCCdrsnSHk7c,1739
|
|
66
|
+
mcplint/reporters/templates/report.html.j2,sha256=pdLtikIuOh_M-xcy8y0KNpr9RRu004TwozNeQ0lmybw,7039
|
|
67
|
+
mcplint_cli-0.1.0.dist-info/METADATA,sha256=Sz_Qo4lci4yox61T8WPNuS8HnPDMuNMCUNZLCqrQ2UI,17000
|
|
68
|
+
mcplint_cli-0.1.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
69
|
+
mcplint_cli-0.1.0.dist-info/entry_points.txt,sha256=hgR9Ewf6VZp-OVKc2Jcn7yY8QQo7Ften-K-miTTbKXA,49
|
|
70
|
+
mcplint_cli-0.1.0.dist-info/licenses/LICENSE,sha256=rnJsLqapccqSz8P-vEmig8WmIgwkrf7zZUeeVlsZCRw,1077
|
|
71
|
+
mcplint_cli-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 MCPLint contributors
|
|
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.
|