mcplint-cli 0.1.0__tar.gz
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_cli-0.1.0/.github/ISSUE_TEMPLATE/bug_report.md +29 -0
- mcplint_cli-0.1.0/.github/ISSUE_TEMPLATE/feature_request.md +23 -0
- mcplint_cli-0.1.0/.github/PULL_REQUEST_TEMPLATE.md +11 -0
- mcplint_cli-0.1.0/.github/workflows/ci.yml +101 -0
- mcplint_cli-0.1.0/.github/workflows/example-scan-mcp-server.yml +54 -0
- mcplint_cli-0.1.0/.github/workflows/release.yml +125 -0
- mcplint_cli-0.1.0/.gitignore +11 -0
- mcplint_cli-0.1.0/.pre-commit-config.yaml +27 -0
- mcplint_cli-0.1.0/CHANGELOG.md +96 -0
- mcplint_cli-0.1.0/CODE_OF_CONDUCT.md +51 -0
- mcplint_cli-0.1.0/CONTRIBUTING.md +75 -0
- mcplint_cli-0.1.0/Dockerfile +17 -0
- mcplint_cli-0.1.0/IMPLEMENTATION_STATUS.md +271 -0
- mcplint_cli-0.1.0/LICENSE +21 -0
- mcplint_cli-0.1.0/PKG-INFO +392 -0
- mcplint_cli-0.1.0/README.md +341 -0
- mcplint_cli-0.1.0/SECURITY.md +52 -0
- mcplint_cli-0.1.0/docs/superpowers/plans/2026-07-26-mcplint-phase1.md +1208 -0
- mcplint_cli-0.1.0/docs/superpowers/plans/2026-07-26-mcplint-phase2.md +1647 -0
- mcplint_cli-0.1.0/examples/ambiguous_customer_server/customer-tools.evals.yaml +35 -0
- mcplint_cli-0.1.0/examples/ambiguous_customer_server/pyproject.toml +5 -0
- mcplint_cli-0.1.0/examples/ambiguous_customer_server/server.py +46 -0
- mcplint_cli-0.1.0/examples/bad_server/pyproject.toml +5 -0
- mcplint_cli-0.1.0/examples/bad_server/server.py +133 -0
- mcplint_cli-0.1.0/examples/good_server/pyproject.toml +5 -0
- mcplint_cli-0.1.0/examples/good_server/server.py +53 -0
- mcplint_cli-0.1.0/pyproject.toml +102 -0
- mcplint_cli-0.1.0/src/mcplint/__about__.py +1 -0
- mcplint_cli-0.1.0/src/mcplint/__init__.py +3 -0
- mcplint_cli-0.1.0/src/mcplint/benchmark/__init__.py +0 -0
- mcplint_cli-0.1.0/src/mcplint/benchmark/dataset.py +34 -0
- mcplint_cli-0.1.0/src/mcplint/benchmark/providers/__init__.py +0 -0
- mcplint_cli-0.1.0/src/mcplint/benchmark/providers/anthropic_provider.py +92 -0
- mcplint_cli-0.1.0/src/mcplint/benchmark/providers/base.py +26 -0
- mcplint_cli-0.1.0/src/mcplint/benchmark/providers/factory.py +51 -0
- mcplint_cli-0.1.0/src/mcplint/benchmark/providers/fake.py +26 -0
- mcplint_cli-0.1.0/src/mcplint/benchmark/providers/openai_provider.py +25 -0
- mcplint_cli-0.1.0/src/mcplint/benchmark/runner.py +43 -0
- mcplint_cli-0.1.0/src/mcplint/benchmark/scorer.py +147 -0
- mcplint_cli-0.1.0/src/mcplint/cli/__init__.py +0 -0
- mcplint_cli-0.1.0/src/mcplint/cli/commands/__init__.py +0 -0
- mcplint_cli-0.1.0/src/mcplint/cli/commands/benchmark_cmd.py +88 -0
- mcplint_cli-0.1.0/src/mcplint/cli/commands/compare_cmd.py +139 -0
- mcplint_cli-0.1.0/src/mcplint/cli/commands/fix_cmd.py +60 -0
- mcplint_cli-0.1.0/src/mcplint/cli/commands/inspect_cmd.py +44 -0
- mcplint_cli-0.1.0/src/mcplint/cli/commands/rules_cmd.py +30 -0
- mcplint_cli-0.1.0/src/mcplint/cli/commands/scan_cmd.py +113 -0
- mcplint_cli-0.1.0/src/mcplint/cli/commands/snapshot_cmd.py +33 -0
- mcplint_cli-0.1.0/src/mcplint/cli/main.py +49 -0
- mcplint_cli-0.1.0/src/mcplint/compare/__init__.py +0 -0
- mcplint_cli-0.1.0/src/mcplint/compare/differ.py +148 -0
- mcplint_cli-0.1.0/src/mcplint/config/__init__.py +0 -0
- mcplint_cli-0.1.0/src/mcplint/config/loader.py +36 -0
- mcplint_cli-0.1.0/src/mcplint/config/schema.py +40 -0
- mcplint_cli-0.1.0/src/mcplint/core/__init__.py +0 -0
- mcplint_cli-0.1.0/src/mcplint/core/engine.py +40 -0
- mcplint_cli-0.1.0/src/mcplint/core/registry.py +52 -0
- mcplint_cli-0.1.0/src/mcplint/core/rules/__init__.py +0 -0
- mcplint_cli-0.1.0/src/mcplint/core/rules/ambiguity.py +157 -0
- mcplint_cli-0.1.0/src/mcplint/core/rules/ambiguity_rules.py +109 -0
- mcplint_cli-0.1.0/src/mcplint/core/rules/base.py +39 -0
- mcplint_cli-0.1.0/src/mcplint/core/rules/builtin.py +45 -0
- mcplint_cli-0.1.0/src/mcplint/core/rules/completeness_rules.py +217 -0
- mcplint_cli-0.1.0/src/mcplint/core/rules/description_rules.py +110 -0
- mcplint_cli-0.1.0/src/mcplint/core/rules/safety_rules.py +139 -0
- mcplint_cli-0.1.0/src/mcplint/core/rules/schema_rules.py +101 -0
- mcplint_cli-0.1.0/src/mcplint/core/score.py +132 -0
- mcplint_cli-0.1.0/src/mcplint/fix/__init__.py +0 -0
- mcplint_cli-0.1.0/src/mcplint/fix/suggest.py +183 -0
- mcplint_cli-0.1.0/src/mcplint/mcp_client/__init__.py +0 -0
- mcplint_cli-0.1.0/src/mcplint/mcp_client/canonical.py +25 -0
- mcplint_cli-0.1.0/src/mcplint/mcp_client/persistence.py +17 -0
- mcplint_cli-0.1.0/src/mcplint/mcp_client/session.py +80 -0
- mcplint_cli-0.1.0/src/mcplint/mcp_client/stdio.py +17 -0
- mcplint_cli-0.1.0/src/mcplint/models/__init__.py +0 -0
- mcplint_cli-0.1.0/src/mcplint/models/benchmark.py +67 -0
- mcplint_cli-0.1.0/src/mcplint/models/common.py +23 -0
- mcplint_cli-0.1.0/src/mcplint/models/comparison.py +53 -0
- mcplint_cli-0.1.0/src/mcplint/models/contracts.py +39 -0
- mcplint_cli-0.1.0/src/mcplint/models/findings.py +44 -0
- mcplint_cli-0.1.0/src/mcplint/models/fixes.py +13 -0
- mcplint_cli-0.1.0/src/mcplint/models/score.py +25 -0
- mcplint_cli-0.1.0/src/mcplint/models/snapshot.py +27 -0
- mcplint_cli-0.1.0/src/mcplint/py.typed +0 -0
- mcplint_cli-0.1.0/src/mcplint/reporters/__init__.py +0 -0
- mcplint_cli-0.1.0/src/mcplint/reporters/benchmark_terminal.py +46 -0
- mcplint_cli-0.1.0/src/mcplint/reporters/comparison_terminal.py +61 -0
- mcplint_cli-0.1.0/src/mcplint/reporters/fix_markdown.py +34 -0
- mcplint_cli-0.1.0/src/mcplint/reporters/html.py +71 -0
- mcplint_cli-0.1.0/src/mcplint/reporters/json_reporter.py +9 -0
- mcplint_cli-0.1.0/src/mcplint/reporters/sarif.py +77 -0
- mcplint_cli-0.1.0/src/mcplint/reporters/templates/report.html.j2 +176 -0
- mcplint_cli-0.1.0/src/mcplint/reporters/terminal.py +52 -0
- mcplint_cli-0.1.0/tests/__init__.py +0 -0
- mcplint_cli-0.1.0/tests/cli/__init__.py +0 -0
- mcplint_cli-0.1.0/tests/cli/test_benchmark.py +97 -0
- mcplint_cli-0.1.0/tests/cli/test_compare.py +100 -0
- mcplint_cli-0.1.0/tests/cli/test_fix.py +77 -0
- mcplint_cli-0.1.0/tests/cli/test_inspect.py +21 -0
- mcplint_cli-0.1.0/tests/cli/test_rules.py +13 -0
- mcplint_cli-0.1.0/tests/cli/test_scan.py +108 -0
- mcplint_cli-0.1.0/tests/cli/test_snapshot.py +22 -0
- mcplint_cli-0.1.0/tests/integration/__init__.py +0 -0
- mcplint_cli-0.1.0/tests/integration/test_ambiguous_customer_server.py +28 -0
- mcplint_cli-0.1.0/tests/integration/test_bad_server.py +38 -0
- mcplint_cli-0.1.0/tests/integration/test_example_servers.py +31 -0
- mcplint_cli-0.1.0/tests/packaging/__init__.py +0 -0
- mcplint_cli-0.1.0/tests/packaging/test_distribution_name.py +112 -0
- mcplint_cli-0.1.0/tests/unit/__init__.py +0 -0
- mcplint_cli-0.1.0/tests/unit/benchmark/__init__.py +0 -0
- mcplint_cli-0.1.0/tests/unit/benchmark/test_anthropic_provider.py +84 -0
- mcplint_cli-0.1.0/tests/unit/benchmark/test_dataset.py +50 -0
- mcplint_cli-0.1.0/tests/unit/benchmark/test_factory.py +34 -0
- mcplint_cli-0.1.0/tests/unit/benchmark/test_openai_provider.py +25 -0
- mcplint_cli-0.1.0/tests/unit/benchmark/test_runner.py +83 -0
- mcplint_cli-0.1.0/tests/unit/benchmark/test_scorer.py +122 -0
- mcplint_cli-0.1.0/tests/unit/compare/__init__.py +0 -0
- mcplint_cli-0.1.0/tests/unit/compare/test_differ.py +158 -0
- mcplint_cli-0.1.0/tests/unit/config/__init__.py +0 -0
- mcplint_cli-0.1.0/tests/unit/config/test_loader.py +51 -0
- mcplint_cli-0.1.0/tests/unit/config/test_schema.py +30 -0
- mcplint_cli-0.1.0/tests/unit/core/__init__.py +0 -0
- mcplint_cli-0.1.0/tests/unit/core/rules/__init__.py +0 -0
- mcplint_cli-0.1.0/tests/unit/core/rules/test_ambiguity.py +74 -0
- mcplint_cli-0.1.0/tests/unit/core/rules/test_ambiguity_rules.py +80 -0
- mcplint_cli-0.1.0/tests/unit/core/rules/test_completeness_rules.py +130 -0
- mcplint_cli-0.1.0/tests/unit/core/rules/test_description_rules.py +101 -0
- mcplint_cli-0.1.0/tests/unit/core/rules/test_safety_rules.py +88 -0
- mcplint_cli-0.1.0/tests/unit/core/rules/test_schema_rules.py +134 -0
- mcplint_cli-0.1.0/tests/unit/core/test_engine.py +73 -0
- mcplint_cli-0.1.0/tests/unit/core/test_engine_config.py +69 -0
- mcplint_cli-0.1.0/tests/unit/core/test_registry.py +88 -0
- mcplint_cli-0.1.0/tests/unit/core/test_score.py +112 -0
- mcplint_cli-0.1.0/tests/unit/fix/__init__.py +0 -0
- mcplint_cli-0.1.0/tests/unit/fix/test_suggest.py +239 -0
- mcplint_cli-0.1.0/tests/unit/mcp_client/__init__.py +0 -0
- mcplint_cli-0.1.0/tests/unit/mcp_client/test_canonical.py +56 -0
- mcplint_cli-0.1.0/tests/unit/mcp_client/test_persistence.py +47 -0
- mcplint_cli-0.1.0/tests/unit/mcp_client/test_session.py +39 -0
- mcplint_cli-0.1.0/tests/unit/models/__init__.py +0 -0
- mcplint_cli-0.1.0/tests/unit/models/test_common.py +20 -0
- mcplint_cli-0.1.0/tests/unit/models/test_contracts.py +53 -0
- mcplint_cli-0.1.0/tests/unit/models/test_findings.py +59 -0
- mcplint_cli-0.1.0/tests/unit/models/test_snapshot.py +52 -0
- mcplint_cli-0.1.0/tests/unit/reporters/__init__.py +0 -0
- mcplint_cli-0.1.0/tests/unit/reporters/test_fix_markdown.py +22 -0
- mcplint_cli-0.1.0/tests/unit/reporters/test_html.py +89 -0
- mcplint_cli-0.1.0/tests/unit/reporters/test_json_reporter.py +32 -0
- mcplint_cli-0.1.0/tests/unit/reporters/test_sarif.py +58 -0
- mcplint_cli-0.1.0/tests/unit/reporters/test_terminal.py +40 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Bug report
|
|
3
|
+
about: Something in MCPLint isn't working as documented
|
|
4
|
+
title: ""
|
|
5
|
+
labels: bug
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
**What happened**
|
|
9
|
+
|
|
10
|
+
**What you expected**
|
|
11
|
+
|
|
12
|
+
**Command you ran**
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
mcplint ...
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
**MCP server involved** (or a minimal repro server, if you can share one)
|
|
19
|
+
|
|
20
|
+
**Environment**
|
|
21
|
+
- MCPLint version: `mcplint --version` output, or commit SHA
|
|
22
|
+
- Python version:
|
|
23
|
+
- OS:
|
|
24
|
+
|
|
25
|
+
**Relevant output**
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
paste terminal output / JSON / SARIF here
|
|
29
|
+
```
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Feature request
|
|
3
|
+
about: Propose a new rule, command, or reporter
|
|
4
|
+
title: ""
|
|
5
|
+
labels: enhancement
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
**Problem**
|
|
9
|
+
|
|
10
|
+
What's the concrete scenario this doesn't handle today? (An example MCP
|
|
11
|
+
server / tool description that should be flagged or reported differently is
|
|
12
|
+
the most useful thing you can include.)
|
|
13
|
+
|
|
14
|
+
**Proposed solution**
|
|
15
|
+
|
|
16
|
+
- New rule? Name it, describe the deterministic check, and note the
|
|
17
|
+
severity/confidence you'd expect.
|
|
18
|
+
- New command/flag? Sketch the CLI invocation.
|
|
19
|
+
- New reporter/format? What should the output look like?
|
|
20
|
+
|
|
21
|
+
**Alternatives considered**
|
|
22
|
+
|
|
23
|
+
**Willing to submit a PR?**
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
## What
|
|
2
|
+
|
|
3
|
+
## Why
|
|
4
|
+
|
|
5
|
+
## Testing
|
|
6
|
+
|
|
7
|
+
- [ ] `ruff check src tests examples` passes
|
|
8
|
+
- [ ] `mypy src` passes
|
|
9
|
+
- [ ] `pytest --cov=mcplint` passes, new code has tests
|
|
10
|
+
- [ ] Updated `CHANGELOG.md` under `[Unreleased]`
|
|
11
|
+
- [ ] Updated `IMPLEMENTATION_STATUS.md` if this changes what's done/incomplete
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
# Least privilege by default; no job in this workflow ever needs write
|
|
9
|
+
# access to the repo, and nothing here ever touches PyPI or creates a
|
|
10
|
+
# release — that only happens from release.yml, triggered by a tag push.
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
lint-typecheck-test:
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
|
|
20
|
+
- name: Set up Python 3.11
|
|
21
|
+
uses: actions/setup-python@v5
|
|
22
|
+
with:
|
|
23
|
+
python-version: "3.11"
|
|
24
|
+
|
|
25
|
+
- name: Install MCPLint (dev extras)
|
|
26
|
+
run: pip install -e ".[dev]"
|
|
27
|
+
|
|
28
|
+
- name: Ruff check
|
|
29
|
+
run: ruff check src tests examples
|
|
30
|
+
|
|
31
|
+
- name: Ruff format check
|
|
32
|
+
run: ruff format --check src tests examples
|
|
33
|
+
|
|
34
|
+
- name: MyPy
|
|
35
|
+
run: mypy src
|
|
36
|
+
|
|
37
|
+
- name: Unit + CLI tests with coverage
|
|
38
|
+
run: pytest --cov=mcplint --cov-report=term-missing --cov-fail-under=80 tests/unit tests/cli
|
|
39
|
+
|
|
40
|
+
- name: Integration tests (spawns real example MCP servers over stdio)
|
|
41
|
+
run: pytest tests/integration
|
|
42
|
+
|
|
43
|
+
build-and-verify-package:
|
|
44
|
+
runs-on: ubuntu-latest
|
|
45
|
+
needs: lint-typecheck-test
|
|
46
|
+
steps:
|
|
47
|
+
- uses: actions/checkout@v4
|
|
48
|
+
|
|
49
|
+
- name: Set up Python 3.11
|
|
50
|
+
uses: actions/setup-python@v5
|
|
51
|
+
with:
|
|
52
|
+
python-version: "3.11"
|
|
53
|
+
|
|
54
|
+
- name: Install build backend and metadata checker
|
|
55
|
+
run: pip install build twine
|
|
56
|
+
|
|
57
|
+
- name: Build sdist and wheel
|
|
58
|
+
run: python -m build
|
|
59
|
+
|
|
60
|
+
- name: Inspect package metadata
|
|
61
|
+
run: twine check dist/*
|
|
62
|
+
|
|
63
|
+
- uses: actions/upload-artifact@v4
|
|
64
|
+
with:
|
|
65
|
+
name: dist
|
|
66
|
+
path: dist/
|
|
67
|
+
|
|
68
|
+
install-and-smoke-test:
|
|
69
|
+
runs-on: ubuntu-latest
|
|
70
|
+
needs: build-and-verify-package
|
|
71
|
+
steps:
|
|
72
|
+
- uses: actions/checkout@v4 # only for examples/good_server, not for source install
|
|
73
|
+
|
|
74
|
+
- name: Set up Python 3.11
|
|
75
|
+
uses: actions/setup-python@v5
|
|
76
|
+
with:
|
|
77
|
+
python-version: "3.11"
|
|
78
|
+
|
|
79
|
+
- uses: actions/download-artifact@v4
|
|
80
|
+
with:
|
|
81
|
+
name: dist
|
|
82
|
+
path: dist/
|
|
83
|
+
|
|
84
|
+
- name: Install the built wheel into a clean environment
|
|
85
|
+
run: |
|
|
86
|
+
python -m venv /tmp/mcplint-smoke
|
|
87
|
+
/tmp/mcplint-smoke/bin/pip install --quiet dist/*.whl
|
|
88
|
+
|
|
89
|
+
- name: CLI smoke tests
|
|
90
|
+
# Activate the venv (rather than calling mcplint by full path) so the
|
|
91
|
+
# `python examples/good_server/server.py` subprocess mcplint spawns
|
|
92
|
+
# also resolves to this venv's python — which has `mcp` installed as
|
|
93
|
+
# mcplint's own runtime dependency. Calling the binary by full path
|
|
94
|
+
# would leave `python` on PATH pointing at the bare system
|
|
95
|
+
# interpreter, which doesn't have `mcp` installed, and the spawned
|
|
96
|
+
# server would fail to import.
|
|
97
|
+
run: |
|
|
98
|
+
source /tmp/mcplint-smoke/bin/activate
|
|
99
|
+
mcplint --version
|
|
100
|
+
mcplint rules
|
|
101
|
+
mcplint scan --server "python examples/good_server/server.py"
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Example workflow for MCPLint users: gate a PR on MCP tool-contract quality.
|
|
2
|
+
#
|
|
3
|
+
# This is a *documented example*, not part of MCPLint's own CI — copy it into
|
|
4
|
+
# your MCP server's repo and adjust the "Start the MCP server" step. It:
|
|
5
|
+
# 1. Installs MCPLint
|
|
6
|
+
# 2. Invokes the local MCP server
|
|
7
|
+
# 3. Runs the scanner
|
|
8
|
+
# 4. Uploads SARIF to GitHub code scanning
|
|
9
|
+
# 5. Fails the job on error-severity findings
|
|
10
|
+
#
|
|
11
|
+
# No custom JavaScript Action is required — this composite-style workflow
|
|
12
|
+
# (plain steps) is sufficient, per MCPLint's own security stance of never
|
|
13
|
+
# running a hosted service that executes arbitrary server commands.
|
|
14
|
+
name: mcplint-scan-example
|
|
15
|
+
|
|
16
|
+
on:
|
|
17
|
+
workflow_dispatch: # example only — trigger manually; adapt `on:` for real use
|
|
18
|
+
|
|
19
|
+
jobs:
|
|
20
|
+
scan:
|
|
21
|
+
runs-on: ubuntu-latest
|
|
22
|
+
steps:
|
|
23
|
+
- uses: actions/checkout@v4
|
|
24
|
+
|
|
25
|
+
- name: Set up Python
|
|
26
|
+
uses: actions/setup-python@v5
|
|
27
|
+
with:
|
|
28
|
+
python-version: "3.11"
|
|
29
|
+
|
|
30
|
+
# 1. Install MCPLint (PyPI distribution name is mcplint-cli; the
|
|
31
|
+
# installed command is still `mcplint`)
|
|
32
|
+
- name: Install MCPLint
|
|
33
|
+
run: pip install mcplint-cli
|
|
34
|
+
|
|
35
|
+
# 2 & 3. Invoke the local MCP server and run the scanner.
|
|
36
|
+
# Replace the --server command with however your server is started —
|
|
37
|
+
# `python server.py`, `node server.js`, a built binary, etc.
|
|
38
|
+
- name: Scan the MCP server
|
|
39
|
+
run: |
|
|
40
|
+
mcplint scan \
|
|
41
|
+
--server "python examples/good_server/server.py" \
|
|
42
|
+
--format sarif \
|
|
43
|
+
--output mcplint-results.sarif \
|
|
44
|
+
--fail-on error
|
|
45
|
+
|
|
46
|
+
# 4. Upload SARIF to GitHub code scanning (visible in the Security tab).
|
|
47
|
+
- name: Upload SARIF
|
|
48
|
+
if: always()
|
|
49
|
+
uses: github/codeql-action/upload-sarif@v3
|
|
50
|
+
with:
|
|
51
|
+
sarif_file: mcplint-results.sarif
|
|
52
|
+
|
|
53
|
+
# 5. `mcplint scan` above already exits non-zero on error-severity
|
|
54
|
+
# findings (--fail-on error is the default), which fails this job.
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
# Production publishing happens ONLY from an explicit `vX.Y.Z` tag push.
|
|
4
|
+
# Nothing in ci.yml (which runs on every PR and every push to main) has
|
|
5
|
+
# any path to PyPI credentials or release-creation permissions — only this
|
|
6
|
+
# workflow does, and only after a maintainer pushes a tag (tag creation
|
|
7
|
+
# requires write access to the repo; it cannot be triggered by an
|
|
8
|
+
# untrusted pull request).
|
|
9
|
+
on:
|
|
10
|
+
push:
|
|
11
|
+
tags:
|
|
12
|
+
- "v*.*.*" # e.g. v0.1.0 — the build job's version-match step rejects anything non-semver
|
|
13
|
+
|
|
14
|
+
permissions:
|
|
15
|
+
contents: read
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
# Build once. Every later job consumes these exact artifacts rather than
|
|
19
|
+
# rebuilding, so what's tested is byte-for-byte what gets published.
|
|
20
|
+
build:
|
|
21
|
+
runs-on: ubuntu-latest
|
|
22
|
+
steps:
|
|
23
|
+
- uses: actions/checkout@v4
|
|
24
|
+
|
|
25
|
+
- name: Set up Python 3.11
|
|
26
|
+
uses: actions/setup-python@v5
|
|
27
|
+
with:
|
|
28
|
+
python-version: "3.11"
|
|
29
|
+
|
|
30
|
+
- name: Install MCPLint (dev extras) for the pre-publish test run
|
|
31
|
+
run: pip install -e ".[dev]"
|
|
32
|
+
|
|
33
|
+
- name: Verify the tag matches the package version
|
|
34
|
+
run: |
|
|
35
|
+
TAG_VERSION="${GITHUB_REF_NAME#v}"
|
|
36
|
+
PKG_VERSION="$(python -c 'import mcplint; print(mcplint.__version__)')"
|
|
37
|
+
echo "Tag version: $TAG_VERSION"
|
|
38
|
+
echo "Package version: $PKG_VERSION"
|
|
39
|
+
if [ "$TAG_VERSION" != "$PKG_VERSION" ]; then
|
|
40
|
+
echo "::error::Tag $GITHUB_REF_NAME does not match src/mcplint/__about__.py ($PKG_VERSION)"
|
|
41
|
+
exit 1
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
- name: Ruff check
|
|
45
|
+
run: ruff check src tests examples
|
|
46
|
+
|
|
47
|
+
- name: Ruff format check
|
|
48
|
+
run: ruff format --check src tests examples
|
|
49
|
+
|
|
50
|
+
- name: MyPy
|
|
51
|
+
run: mypy src
|
|
52
|
+
|
|
53
|
+
- name: Full test suite
|
|
54
|
+
run: pytest --cov=mcplint --cov-fail-under=80 tests/unit tests/cli
|
|
55
|
+
|
|
56
|
+
- name: Integration tests
|
|
57
|
+
run: pytest tests/integration
|
|
58
|
+
|
|
59
|
+
- name: Install build backend and metadata checker
|
|
60
|
+
run: pip install build twine
|
|
61
|
+
|
|
62
|
+
- name: Build sdist and wheel
|
|
63
|
+
run: python -m build
|
|
64
|
+
|
|
65
|
+
- name: Inspect package metadata
|
|
66
|
+
run: twine check dist/*
|
|
67
|
+
|
|
68
|
+
- name: Install the built wheel into a clean environment and smoke-test it
|
|
69
|
+
run: |
|
|
70
|
+
python -m venv /tmp/mcplint-release-smoke
|
|
71
|
+
/tmp/mcplint-release-smoke/bin/pip install --quiet dist/*.whl
|
|
72
|
+
source /tmp/mcplint-release-smoke/bin/activate
|
|
73
|
+
mcplint --version
|
|
74
|
+
mcplint scan --server "python examples/good_server/server.py"
|
|
75
|
+
|
|
76
|
+
- uses: actions/upload-artifact@v4
|
|
77
|
+
with:
|
|
78
|
+
name: dist
|
|
79
|
+
path: dist/
|
|
80
|
+
|
|
81
|
+
# Publish the exact artifacts from `build` to PyPI via Trusted Publishing.
|
|
82
|
+
# No long-lived token: the `id-token: write` permission lets PyPI verify
|
|
83
|
+
# this specific workflow run via OIDC. This is the only job in the whole
|
|
84
|
+
# repo with any PyPI-facing permission.
|
|
85
|
+
publish-to-pypi:
|
|
86
|
+
runs-on: ubuntu-latest
|
|
87
|
+
needs: build
|
|
88
|
+
environment:
|
|
89
|
+
name: pypi
|
|
90
|
+
url: https://pypi.org/p/mcplint-cli
|
|
91
|
+
permissions:
|
|
92
|
+
id-token: write
|
|
93
|
+
steps:
|
|
94
|
+
- uses: actions/download-artifact@v4
|
|
95
|
+
with:
|
|
96
|
+
name: dist
|
|
97
|
+
path: dist/
|
|
98
|
+
|
|
99
|
+
- name: Publish to PyPI
|
|
100
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
101
|
+
with:
|
|
102
|
+
attestations: true
|
|
103
|
+
|
|
104
|
+
# Create the GitHub Release and attach the same artifacts, after PyPI
|
|
105
|
+
# publish succeeds so a Release is never created for a build that failed
|
|
106
|
+
# to publish.
|
|
107
|
+
github-release:
|
|
108
|
+
runs-on: ubuntu-latest
|
|
109
|
+
needs: publish-to-pypi
|
|
110
|
+
permissions:
|
|
111
|
+
contents: write
|
|
112
|
+
steps:
|
|
113
|
+
- uses: actions/download-artifact@v4
|
|
114
|
+
with:
|
|
115
|
+
name: dist
|
|
116
|
+
path: dist/
|
|
117
|
+
|
|
118
|
+
- name: Create GitHub Release
|
|
119
|
+
env:
|
|
120
|
+
GH_TOKEN: ${{ github.token }}
|
|
121
|
+
run: |
|
|
122
|
+
gh release create "${{ github.ref_name }}" dist/* \
|
|
123
|
+
--repo "${{ github.repository }}" \
|
|
124
|
+
--title "${{ github.ref_name }}" \
|
|
125
|
+
--generate-notes
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
3
|
+
rev: v0.6.9
|
|
4
|
+
hooks:
|
|
5
|
+
- id: ruff
|
|
6
|
+
args: [--fix]
|
|
7
|
+
- id: ruff-format
|
|
8
|
+
|
|
9
|
+
- repo: https://github.com/pre-commit/mirrors-mypy
|
|
10
|
+
rev: v1.11.2
|
|
11
|
+
hooks:
|
|
12
|
+
- id: mypy
|
|
13
|
+
additional_dependencies:
|
|
14
|
+
- pydantic>=2.7
|
|
15
|
+
- types-pyyaml
|
|
16
|
+
- types-jsonschema
|
|
17
|
+
args: [src]
|
|
18
|
+
pass_filenames: false
|
|
19
|
+
|
|
20
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
21
|
+
rev: v4.6.0
|
|
22
|
+
hooks:
|
|
23
|
+
- id: trailing-whitespace
|
|
24
|
+
- id: end-of-file-fixer
|
|
25
|
+
- id: check-yaml
|
|
26
|
+
- id: check-toml
|
|
27
|
+
- id: check-merge-conflict
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [Unreleased]
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Project scaffolding (`pyproject.toml`, package layout, dev tooling).
|
|
7
|
+
- Core Pydantic models: `ArtifactMetadata`, `SourceLocation`, `ParameterContract`,
|
|
8
|
+
`ToolAnnotation`, `ToolContract`, `MCPServerSnapshot`.
|
|
9
|
+
- Canonicalization module: deterministic stable tool IDs, byte-stable snapshot JSON.
|
|
10
|
+
- Stdio MCP client: `collect_stdio_snapshot`, mapping SDK `Tool` objects to `ToolContract`.
|
|
11
|
+
- `examples/good_server`: a well-documented example MCP server fixture.
|
|
12
|
+
- `mcplint inspect --server "<command>"` CLI command with Rich table output.
|
|
13
|
+
- `Severity`, `Finding`, `RuleMetadata`, `LintReport` models.
|
|
14
|
+
- `Rule` ABC, `RuleContext`, `RuleRegistry` (with entry-point plugin loading support).
|
|
15
|
+
- Five built-in rules: `missing-tool-description`, `description-repeats-name`,
|
|
16
|
+
`vague-tool-description`, `missing-parameter-description`,
|
|
17
|
+
`schema-description-type-conflict`.
|
|
18
|
+
- `lint_snapshot()` engine wiring the registry over every tool in a snapshot.
|
|
19
|
+
- Snapshot persistence: `save_snapshot`/`load_snapshot`.
|
|
20
|
+
- Terminal and JSON reporters (`render_terminal`, `render_json`).
|
|
21
|
+
- `mcplint snapshot --server "<command>" --output PATH` CLI command.
|
|
22
|
+
- `mcplint scan --server "<command>"` / `mcplint scan --snapshot PATH` CLI command
|
|
23
|
+
with `--format terminal|json` and `--fail-on error|warning|never`.
|
|
24
|
+
- Remaining 10 built-in rules: `missing-return-semantics`,
|
|
25
|
+
`undocumented-error-behaviour`, `undocumented-required-constraint`,
|
|
26
|
+
`tool-name-action-conflict`, `destructive-tool-without-warning`,
|
|
27
|
+
`state-changing-tool-marked-read-only`, `ambiguous-tool-overlap`,
|
|
28
|
+
`missing-tool-distinction`, `excessive-description-length`,
|
|
29
|
+
`undefined-domain-term` — all 15 spec rules now implemented.
|
|
30
|
+
- Semantic ambiguity engine (`compute_ambiguity`): name/description/parameter
|
|
31
|
+
Jaccard similarity with structured, inspectable evidence (shared verbs,
|
|
32
|
+
shared entities, overlapping parameters, absent exact-vs-search /
|
|
33
|
+
one-vs-many / read-vs-write distinctions).
|
|
34
|
+
- `mcplint.yaml` configuration loading (`MCPLintConfig`): severity overrides,
|
|
35
|
+
ambiguity/description-length thresholds, per-tool rule ignores, benchmark
|
|
36
|
+
defaults. Wired into `scan --config`.
|
|
37
|
+
- `mcplint rules` CLI command listing the full rule catalogue.
|
|
38
|
+
- `examples/bad_server`: intentionally triggers all 15 built-in rules
|
|
39
|
+
(verified by an integration test).
|
|
40
|
+
- `examples/ambiguous_customer_server`: get/search/update/delete customer
|
|
41
|
+
tools for the upcoming benchmark dataset, verified to trigger
|
|
42
|
+
`ambiguous-tool-overlap`.
|
|
43
|
+
- `examples/good_server` reworked to use `Annotated[..., Field(description=...)]`
|
|
44
|
+
parameters and explicit tool annotations; verified to produce zero findings.
|
|
45
|
+
- Benchmark models: `ExpectedToolCall`, `BenchmarkCase`, `BenchmarkDataset`,
|
|
46
|
+
`ActualToolCall`, `BenchmarkTrial`, `BenchmarkResult`.
|
|
47
|
+
- `ToolCallingProvider` protocol + `ProviderResult`; `FakeProvider` for
|
|
48
|
+
network-free tests and dry runs.
|
|
49
|
+
- Deterministic scorer (`score_trial`, `aggregate_metrics`): exact
|
|
50
|
+
tool-selection accuracy, valid-argument rate (JSON Schema validated),
|
|
51
|
+
required-argument accuracy, forbidden-tool invocation rate, no-tool rate,
|
|
52
|
+
mean/P95 latency, total estimated cost, per-case pass rate, and stability
|
|
53
|
+
across repeated trials. No LLM judge.
|
|
54
|
+
- `mcplint benchmark DATASET --server "<command>" --provider fake --runs N`
|
|
55
|
+
CLI command with terminal/JSON output and `--output PATH`.
|
|
56
|
+
`--provider anthropic|openai` is recognized but raises a clear
|
|
57
|
+
"not implemented yet" error (Phase 5).
|
|
58
|
+
- `examples/ambiguous_customer_server/customer-tools.evals.yaml`: the
|
|
59
|
+
get/search/update/delete-customer confusion dataset required by the spec.
|
|
60
|
+
- `AnthropicProvider`: verified against the real `anthropic` SDK (0.120.0);
|
|
61
|
+
API errors surface as a scored `ProviderResult.error`, not a crash;
|
|
62
|
+
illustrative per-model cost table. `OpenAIProvider` is a typed stub.
|
|
63
|
+
`--provider anthropic` now works end-to-end via `mcplint benchmark`.
|
|
64
|
+
- `ComparisonReport` model + `compare/differ.py` (`diff_tool_names`,
|
|
65
|
+
`diff_tool_contracts`, `diff_findings`, `diff_ambiguity`, `diff_benchmarks`).
|
|
66
|
+
- `mcplint compare --baseline --candidate [--dataset --min-accuracy-delta]`
|
|
67
|
+
CLI command with a Rich terminal reporter; fails CI (exit 1) when the
|
|
68
|
+
accuracy delta falls below the threshold.
|
|
69
|
+
- `RewriteSuggestion` model + `fix/suggest.py`: deterministic, schema-derived
|
|
70
|
+
rewrite suggestions (output shape, enum/numeric constraints, destructive
|
|
71
|
+
warnings, tool-distinction placeholders, description truncation).
|
|
72
|
+
Markdown patch report reporter.
|
|
73
|
+
- `mcplint fix --snapshot [--output PATH]` CLI command — never overwrites
|
|
74
|
+
source files; `--llm-provider` is recognized but explicitly rejected
|
|
75
|
+
until LLM-assisted rewriting is implemented.
|
|
76
|
+
- `ScoreBreakdown` model + `core/score.py::compute_score`: explainable 0-100
|
|
77
|
+
score, capped per category (critical/error, warning/info, ambiguity,
|
|
78
|
+
schema completeness, safety clarity, optional benchmark accuracy), with
|
|
79
|
+
an explicit not-a-scientific-metric disclaimer. Shown in the terminal
|
|
80
|
+
reporter.
|
|
81
|
+
- SARIF 2.1.0 reporter (`reporters/sarif.py`) with a full rule catalogue
|
|
82
|
+
and per-finding results/locations.
|
|
83
|
+
- Standalone, self-contained HTML report (`reporters/html.py` + Jinja2
|
|
84
|
+
template, embedded CSS, no external requests): score, findings by
|
|
85
|
+
severity, tool inventory, ambiguity pairs, and optional
|
|
86
|
+
benchmark/comparison/fix-suggestion sections.
|
|
87
|
+
- `mcplint scan --format sarif|html` and a new global `--output PATH` to
|
|
88
|
+
also write the rendered report to a file.
|
|
89
|
+
- `mcplint --version`.
|
|
90
|
+
- Packaging: `LICENSE` (MIT), `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`,
|
|
91
|
+
`SECURITY.md`, GitHub issue/PR templates, `ci.yml`/`release.yml`
|
|
92
|
+
GitHub Actions, a documented example scan-and-upload-SARIF workflow,
|
|
93
|
+
`Dockerfile`, `.pre-commit-config.yaml`.
|
|
94
|
+
- Full README rewrite: quickstart, real example output, architecture,
|
|
95
|
+
rule catalogue, ambiguity engine explanation, benchmark/compare/fix/CI/
|
|
96
|
+
plugin guides, limitations, roadmap, and a comparison section.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Code of Conduct
|
|
2
|
+
|
|
3
|
+
## Our Pledge
|
|
4
|
+
|
|
5
|
+
We as members, contributors, and maintainers pledge to make participation in
|
|
6
|
+
the MCPLint project a harassment-free experience for everyone, regardless of
|
|
7
|
+
age, body size, visible or invisible disability, ethnicity, sex
|
|
8
|
+
characteristics, gender identity and expression, level of experience,
|
|
9
|
+
education, socio-economic status, nationality, personal appearance, race,
|
|
10
|
+
religion, or sexual identity and orientation.
|
|
11
|
+
|
|
12
|
+
## Our Standards
|
|
13
|
+
|
|
14
|
+
Examples of behavior that contributes to a positive environment:
|
|
15
|
+
|
|
16
|
+
- Demonstrating empathy and kindness toward other people
|
|
17
|
+
- Being respectful of differing opinions, viewpoints, and experiences
|
|
18
|
+
- Giving and gracefully accepting constructive feedback
|
|
19
|
+
- Focusing on what is best for the community
|
|
20
|
+
|
|
21
|
+
Examples of unacceptable behavior:
|
|
22
|
+
|
|
23
|
+
- The use of sexualized language or imagery, and unwelcome sexual attention
|
|
24
|
+
- Trolling, insulting or derogatory comments, and personal or political attacks
|
|
25
|
+
- Public or private harassment
|
|
26
|
+
- Publishing others' private information without explicit permission
|
|
27
|
+
|
|
28
|
+
## Enforcement Responsibilities
|
|
29
|
+
|
|
30
|
+
Project maintainers are responsible for clarifying and enforcing our
|
|
31
|
+
standards of acceptable behavior and will take appropriate and fair
|
|
32
|
+
corrective action in response to any behavior they deem inappropriate,
|
|
33
|
+
threatening, offensive, or harmful.
|
|
34
|
+
|
|
35
|
+
## Scope
|
|
36
|
+
|
|
37
|
+
This Code of Conduct applies within all project spaces (issues, pull
|
|
38
|
+
requests, discussions) and when an individual is officially representing the
|
|
39
|
+
project in public spaces.
|
|
40
|
+
|
|
41
|
+
## Enforcement
|
|
42
|
+
|
|
43
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
|
44
|
+
reported to the project maintainers via the contact method listed in
|
|
45
|
+
`SECURITY.md`. All complaints will be reviewed and investigated promptly and
|
|
46
|
+
fairly.
|
|
47
|
+
|
|
48
|
+
## Attribution
|
|
49
|
+
|
|
50
|
+
This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org),
|
|
51
|
+
version 2.1.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Contributing to MCPLint
|
|
2
|
+
|
|
3
|
+
Thanks for considering a contribution. MCPLint is a young project — the
|
|
4
|
+
architecture in `IMPLEMENTATION_STATUS.md` and this guide are both likely to
|
|
5
|
+
evolve.
|
|
6
|
+
|
|
7
|
+
## Development setup
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
git clone https://github.com/mcplint/mcplint.git
|
|
11
|
+
cd mcplint
|
|
12
|
+
python3.11 -m venv .venv
|
|
13
|
+
source .venv/bin/activate
|
|
14
|
+
pip install -e ".[dev]"
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
`dev` includes `anthropic` so the Anthropic provider's test suite runs by
|
|
18
|
+
default. If you only want the deterministic linter, `pip install -e .` is
|
|
19
|
+
enough — no extras are required for `inspect`, `snapshot`, `scan`, `rules`,
|
|
20
|
+
or `fix`.
|
|
21
|
+
|
|
22
|
+
## Before opening a PR
|
|
23
|
+
|
|
24
|
+
Run the same gate CI runs:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
ruff check src tests examples
|
|
28
|
+
mypy src
|
|
29
|
+
pytest --cov=mcplint --cov-report=term-missing
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
All three must pass. New code should come with tests — see
|
|
33
|
+
`tests/unit/core/rules/` for the pattern most rules follow (a flag case and
|
|
34
|
+
a pass case per behavior, using hand-built `ToolContract`/`RuleContext`
|
|
35
|
+
fixtures, no live MCP server needed).
|
|
36
|
+
|
|
37
|
+
## Adding a built-in rule
|
|
38
|
+
|
|
39
|
+
1. Subclass `mcplint.core.rules.base.Rule` in the appropriate module under
|
|
40
|
+
`src/mcplint/core/rules/` (`description_rules.py`, `schema_rules.py`,
|
|
41
|
+
`safety_rules.py`, `completeness_rules.py`, or a new file if it's a new
|
|
42
|
+
category).
|
|
43
|
+
2. Set `id`, `title`, `description`, `default_severity`, and optionally
|
|
44
|
+
`tags` as class attributes; implement `check(self, tool, context) ->
|
|
45
|
+
list[Finding]`.
|
|
46
|
+
3. Register it in `BUILTIN_RULES` in `core/rules/builtin.py`.
|
|
47
|
+
4. Add unit tests: at least one case that triggers the finding and one that
|
|
48
|
+
doesn't.
|
|
49
|
+
5. If it's meant to fire on `examples/bad_server`, add a tool there and
|
|
50
|
+
confirm `tests/integration/test_bad_server.py` still passes (it asserts
|
|
51
|
+
every built-in rule ID fires at least once against that server).
|
|
52
|
+
|
|
53
|
+
Third-party rules don't need to touch this repo at all — see the plugin
|
|
54
|
+
guide in the README for the `mcplint.rules` entry-point mechanism.
|
|
55
|
+
|
|
56
|
+
## Adding a benchmark provider
|
|
57
|
+
|
|
58
|
+
Implement the `ToolCallingProvider` protocol (`benchmark/providers/base.py`):
|
|
59
|
+
a `name`/`model` pair and an async `run(prompt, tools) -> ProviderResult`.
|
|
60
|
+
See `benchmark/providers/anthropic_provider.py` for a real example and
|
|
61
|
+
`benchmark/providers/fake.py` for a test-only one. Wire it into
|
|
62
|
+
`benchmark/providers/factory.py::create_provider`.
|
|
63
|
+
|
|
64
|
+
## Commit and PR conventions
|
|
65
|
+
|
|
66
|
+
- Keep commits scoped to one logical change; the existing history (`git log
|
|
67
|
+
--oneline`) is the style to match.
|
|
68
|
+
- Update `CHANGELOG.md` under `[Unreleased]` for user-visible changes.
|
|
69
|
+
- Update `IMPLEMENTATION_STATUS.md` if you complete, start, or discover a
|
|
70
|
+
gap in a tracked feature.
|
|
71
|
+
|
|
72
|
+
## Reporting bugs / requesting features
|
|
73
|
+
|
|
74
|
+
Use the GitHub issue templates. For security issues, see `SECURITY.md`
|
|
75
|
+
instead of opening a public issue.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# MCPLint CLI image. The MCP server you point --server at is spawned as a
|
|
2
|
+
# subprocess *inside this container*, so it (and any language runtime it
|
|
3
|
+
# needs, e.g. Node) must be available on the image or mounted in. This base
|
|
4
|
+
# image ships Python only — extend it if your server needs something else.
|
|
5
|
+
FROM python:3.11-slim AS base
|
|
6
|
+
|
|
7
|
+
WORKDIR /app
|
|
8
|
+
|
|
9
|
+
COPY pyproject.toml README.md ./
|
|
10
|
+
COPY src ./src
|
|
11
|
+
|
|
12
|
+
RUN pip install --no-cache-dir .
|
|
13
|
+
|
|
14
|
+
WORKDIR /workspace
|
|
15
|
+
|
|
16
|
+
ENTRYPOINT ["mcplint"]
|
|
17
|
+
CMD ["--help"]
|