Fast static linter for MCP servers — catch vague, colliding, or misleading tool descriptions before agents pick the wrong tool.
Project description
mcpolish
A static linter for Model Context Protocol servers
Catch vague, colliding, or misleading tool descriptions before agents pick the wrong tool.
| Documentation | Quickstart | Rules | Glossary | Design doc | Test report |
About
When you build an MCP server, every tool you expose carries an English description. The AI agent (Claude, GPT, Gemini, Cursor, and so on) reads that description to decide which tool to call. If the description is vague, generic, or misleading, the agent picks the wrong tool.
In tests across 10,831 public MCP servers (Wang et al., February 2026), bad descriptions caused agents to pick the wrong tool 52 percentage points more often. That paper catalogued 18 specific problems. mcpolish detects 23 of them, before you ship.
mcpolish is to MCP server quality what ESLint is to JavaScript style: a fast static check with stable rule IDs that runs in your editor, your pre-commit hook, and your CI pipeline.
mcpolish is fast with:
- Sub-second scans on a typical MCP server. Verified 6 ms on a 3-tool file, 67 ms on a 40-file project.
- One parse, twenty-three checks. mcpolish reads each file once into an intermediate representation, then every rule runs against the same in-memory view.
- Zero external calls. The default engine runs offline.
mcpolish is flexible with:
- Five output formats: terminal, JSON, SARIF, GitLab Code Quality, GitHub PR-comment Markdown.
- Per-rule configuration in
pyproject.toml. Whitelist names, raise thresholds, retune category weights. - Two-tier auto-fix: safe fixes apply only deterministic edits; risky renames need
--unsafe-fix. - Three optional LLM-judged rules for the cases static heuristics miss. Off by default.
mcpolish covers:
- Schema problems (missing descriptions, broken JSON Schema, missing
requiredarrays). - Naming problems (generic names, redundant namespace prefixes, verb inconsistency, cross-server collisions, mixed casing).
- Description quality (too short, too long, no examples, no trigger conditions, jargon density, marketing qualifiers, ambiguity).
- Consistency between schema and description (param type mismatch, undocumented side effects, duplicate descriptions).
- Security smells (zero-width prompt injection, operator-style instructions baked into descriptions).
See the rules index for the full table.
Quickstart
pip install mcpolish
mcpolish lint .
mcpolish score . --json
mcpolish explain MP010
Sample run on a real server file:
$ mcpolish lint weather_server.py
mcpolish 0.1.0
server: weather (1 tool in 1 file)
weather_server.py:6:1: MP010 [W] tool name `get` is too generic
-> consider a more specific name like `get_<noun>` or `<verb>_<thing>`
-> https://mcpolish.dev/rules/MP010
weather_server.py:6:1: MP020 [W] tool `get` description is 12 chars (minimum 50)
-> state what the tool does and when an agent should pick it
-> https://mcpolish.dev/rules/MP020
Found 2 issues (0 errors, 2 warnings, 0 notes). score: 86/100
Documentation
The full documentation lives under docs/. Common entry points:
| If you want to... | Go here |
|---|---|
| Run mcpolish for the first time | Quickstart |
| Understand the diagnostic lines | Understanding the output |
| Wire mcpolish into CI | GitHub Actions, GitLab CI, pre-commit |
| Lint a multi-file project | Multi-file server |
| Customise which rules run | Customising rules |
| Look up one specific rule | Rules index |
| Find what a technical word means | Glossary |
| Use the Python API | Python API |
The 23 rules
Each rule has a stable identifier like MP010 and a documentation page.
| Category | Rules |
|---|---|
| Schema | MP001, MP002, MP003, MP004, MP005 |
| Naming | MP010, MP011, MP012, MP013, MP014 |
| Description | MP020, MP021, MP022, MP023, MP024, MP025, MP026 (LLM) |
| Consistency | MP030, MP031 (LLM), MP032 (LLM), MP033 |
| Security | MP040, MP041 |
Print the full list in your terminal:
mcpolish explain # one line per rule
mcpolish explain MP010 # details for one rule
See the rules index for the categorised table and links to every rule's detail page.
Configuration
Settings go in pyproject.toml:
[tool.mcpolish]
target-version = "2025-11"
select = ["MP001-MP041"]
ignore = ["MP025"]
registry = "official"
[tool.mcpolish.MP010]
allow = ["search"]
[tool.mcpolish.MP020]
min_chars = 80
See Configuration for every knob.
Integrations
GitHub Actions
- uses: vtensor/mcpolish-action@v1
with:
fail-on: error
report: sarif
GitLab CI
mcpolish:
image: python:3.12
script:
- pip install mcpolish
- mcpolish lint . --format gitlab > codequality.json
artifacts:
reports:
codequality: codequality.json
Pre-commit hook
repos:
- repo: https://github.com/vtensor/mcpolish
rev: v0.1.0
hooks:
- id: mcpolish
CLI cheat sheet
mcpolish lint . # lint everything reachable
--select MP010 # keep only specific rules (range syntax: MP001-MP005)
--ignore MP020 # drop specific rules
--llm openai:gpt-4o # enable the 3 LLM-judged rules
--registry official # cross-server collision check (default)
--registry off # local rules only
--fix # apply safe autofixes
--unsafe-fix # apply risky autofixes (renames)
--format tty|json|sarif|gitlab|pr-comment
--fail-on error|warn|note|never
mcpolish score . --json --badge badge.svg
mcpolish explain MP010
mcpolish doctor
Full reference: CLI reference.
Python API
import mcpolish
report = mcpolish.lint("server.py")
print(report.score) # 0 to 100
for d in report.diagnostics:
print(d.rule_id, d.location(), d.message)
See Python API.
Why this exists
- Wang et al., arXiv:2602.18914, February 2026: 10,831 public MCP servers analysed, 18 description smells catalogued, controlled mutation experiment proving each smell causes wrong-tool selection (p < 0.001).
- Li et al., arXiv:2602.03580, February 2026: static analysis of 10,240 servers; 3,449 with parameter type mismatches, 1,326 with undocumented side effects.
- Snyk acquired Invariant Labs (mcp-scan) in June 2025. Agentic-AI security is now a real budget line.
- MCP was donated to the Linux Foundation's Agentic AI Foundation in December 2025. OpenAI, AWS, Google, Microsoft, Cloudflare, and Bloomberg are members.
The ecosystem grew to over 23,000 servers on Glama, 20,000 on MCP.so, and 12,000 on Smithery with no quality gate. mcpolish is that gate.
Project status
| Version | 0.1.0 |
| Python | 3.11 and newer |
| Rules shipped | 23 |
| Tests | 132 passing |
| Performance | 6 to 67 ms on real fixtures |
| License | Apache 2.0 |
| Status | Beta. The 23 rule IDs are stable forever. |
See MCPOLISH.md for the full design document and VERIFICATION.md for the end-to-end verification report.
Contributing
Contributions are welcome. Bug reports, rule proposals, and documentation improvements are equally valued.
See CONTRIBUTING.md for the developer setup and the workflow. See CODE_OF_CONDUCT.md for community expectations. See SECURITY.md to report a security issue privately.
License
Apache 2.0. See LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file mcpolish-0.1.0.tar.gz.
File metadata
- Download URL: mcpolish-0.1.0.tar.gz
- Upload date:
- Size: 153.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f35375f377758a6fd8cca0f6ff5e94ae05d61f4ba8b78a8a0b147549ec02bf08
|
|
| MD5 |
472ea0a863ffb5eafa209f1eafb62335
|
|
| BLAKE2b-256 |
5755b7818680e008e6fa12810621c2c81c2e5968385f38b184a952fefc93bcb6
|
Provenance
The following attestation bundles were made for mcpolish-0.1.0.tar.gz:
Publisher:
publish.yml on vtensor/mcpolish
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mcpolish-0.1.0.tar.gz -
Subject digest:
f35375f377758a6fd8cca0f6ff5e94ae05d61f4ba8b78a8a0b147549ec02bf08 - Sigstore transparency entry: 1554360741
- Sigstore integration time:
-
Permalink:
vtensor/mcpolish@6e1b1744b327f8b676a2f70cb4228c0cf9f24a65 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/vtensor
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@6e1b1744b327f8b676a2f70cb4228c0cf9f24a65 -
Trigger Event:
push
-
Statement type:
File details
Details for the file mcpolish-0.1.0-py3-none-any.whl.
File metadata
- Download URL: mcpolish-0.1.0-py3-none-any.whl
- Upload date:
- Size: 74.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
38789513803eda7bf7f12ec8a003e3169d803d05900ff6593627de15001f98c2
|
|
| MD5 |
27871a0c3ace369c39e5e5a51b137b7a
|
|
| BLAKE2b-256 |
b13191ef3fbab6d377de0948718c08cf5a25e6194c79b7b6355f77092cf0dda3
|
Provenance
The following attestation bundles were made for mcpolish-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on vtensor/mcpolish
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mcpolish-0.1.0-py3-none-any.whl -
Subject digest:
38789513803eda7bf7f12ec8a003e3169d803d05900ff6593627de15001f98c2 - Sigstore transparency entry: 1554360747
- Sigstore integration time:
-
Permalink:
vtensor/mcpolish@6e1b1744b327f8b676a2f70cb4228c0cf9f24a65 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/vtensor
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@6e1b1744b327f8b676a2f70cb4228c0cf9f24a65 -
Trigger Event:
push
-
Statement type: