mispick
Find out which of your MCP tools the model mixes up — measured, not guessed.
uvx mispick run --cmd "python -m my_server"
Point it at any MCP server. It generates realistic user requests, asks a model to pick a tool for each one, and shows you a confusion matrix: which tools get chosen for which intent. Then it proposes description rewrites for the confused pairs and re-runs to prove the fix helped.
It never calls a tool. Only initialize and tools/list, so it is safe to point at a server that
deletes things.
Why this rather than a static linter
mcp-lint, mcpolish, oh-my-mcp, mcp-conform and mcp-cve-lint check tool definitions
against rules, and they are good at it — run one. But a description can pass every rule and still
be indistinguishable from the tool next to it. Rule-based linters predict problems; this measures
them, by putting your tool list in front of a real model and counting what it picks. Two other
projects do measure selection — whichtool (local
models, but no rewrites, one server per run) and toolfit
(rewrites, but cloud-only, one server per run). Neither looks across servers. The full comparison,
with what each one actually does, is in docs/research.md.
How it works
- Connect to your server and read
tools/list(nevertools/call). - For each tool, generate N realistic requests — plus hard negatives written to sit as close as possible to its nearest neighbour, and some requests no tool should answer.
- Ask the model to choose a tool for each request, K times, showing it the real tool definitions a client would send.
- Score it: accuracy, per-tool precision and recall, a confusion matrix, argument validity, stability across the K runs — every rate with a Wilson 95% interval.
- Propose rewrites for the worst pairs, re-run the same requests against them, and keep only the ones that measurably won.
Install
Not on PyPI yet.
mispickhas not been released, so the commands below do not work today. Until then, run it from a checkout:git clone https://github.com/msc35/mispick && cd mispick uv sync --all-extras uv run mispick --help
uvx mispick --help # no install
uv tool install mispick # or keep it
pip install mispick
The default backend is a local model through Ollama, so it costs nothing and needs no account:
ollama pull qwen3.5:4b
Cloud models are optional: --model openai/gpt-4.1-mini or
--model anthropic/claude-haiku-4-5-20251001, with the matching key in the environment.
Use
# measure a local server, a remote one, or a captured snapshot
mispick run --cmd "python -m my_server"
mispick run --url https://example.com/mcp
mispick run --snapshot tools.json
# capture a snapshot so later runs are offline and reproducible
mispick snapshot --cmd "python -m my_server" -o tools.json
# every server in your config at once, and what they do to each other
mispick run --config ~/Library/Application\ Support/Claude/claude_desktop_config.json
# rewrites for the worst pairs, each one re-tested
mispick fix --cmd "python -m my_server" --patch fix.diff
# reports and CI
mispick run --snapshot tools.json --format html --out report.html --badge badge.svg
mispick run --snapshot tools.json --fail-under 80 # exit 1 below the threshold
mispick compare base.json head.json --max-drop 5 # exit 1 on a regression
Generated requests are cached in .mispick/queries.yaml. Read that file and edit it — it is
your test set, and it only regenerates for a tool whose name, description or schema changed.
Commit it.
Proven fixes
mispick fix rewrites both descriptions in a confused pair, then re-runs the same requests
against the rewrite and reports before/after with an exact McNemar test — the trials are paired,
so an unpaired test would be the wrong one. Rewrites that fail are shown too.
That re-test is the whole point. Hasan et al. rewrote tool descriptions with an LLM across 231 tasks and found success rose by a median 5.85 points — but regressed in 16.67% of cases (arXiv 2602.14878). An unmeasured rewrite is a one-in-six chance of making things worse. mispick also caps rewrite length, so a rewrite cannot win by crowding out its neighbours, and it never edits your source — it prints a suggestion.
Expect rejections, especially on a small local model. In our own runs against
ollama/qwen3.5:4b, two of three proposed rewrites measurably made selection worse — one of them
took a tool from 78% to 58% accuracy, breaking 14 trials and fixing none, and it replicated when
re-run with three times the data. Its stated reasoning read perfectly sensibly. The third improved
by 10 points but over only two changed trials, which is not enough to call, so mispick reported it
as inconclusive rather than banking it. A 4B model is good enough to find confusion and often not
good enough to write its way out of it; fix mode is more productive with a larger model. Either
way the verdicts are the product — a run where nothing is accepted has told you something true.
Across servers
Point --config at a whole claude_desktop_config.json and mispick loads every server at once,
because that is how a client sees them. It reports which bare tool names collide across servers
and — the part that matters — how often a request for one server's tool is answered by another
server's. On the two-server fixture, 17% of trials cross servers and workspace:search_docs falls
to 0% recall: every request for it is answered by wiki:search_docs. The MCP spec names this
problem and warns that serverInfo.name is not unique across servers, so mispick keys on your
config label instead.
In CI
- uses: actions/checkout@v4
- uses: msc35/mispick@v0.1.0
with:
snapshot: tools.json
model: anthropic/claude-haiku-4-5-20251001
fail-under: '80'
compare-base: 'true'
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
It posts the report as a sticky PR comment, and with compare-base the comment reads
84 → 91 (▲7) and names the confusions that appeared or disappeared. With no model key it skips
with an explanation instead of failing — a check that goes red over a missing secret is a check
people learn to ignore. See examples/github-action.
Benchmark
benchmark/ runs the pipeline over servers from the official MCP registry that start without
credentials, and builds a static leaderboard. Snapshots are committed so results are reproducible,
only initialize and tools/list are ever sent, and the page lists the servers it could not
measure so the coverage is not overstated. See benchmark/README.md.
Limitations
Read these before quoting a number.
- Results depend on the model. A score is one model's reading of one tool surface on one day, not a property of your server. Every report says so, and states the model, N, K and the date.
- Small local models are noisy. That is why every rate carries a Wilson 95% interval and why
--runsexists. With K=3 the intervals are wide; read them, not the point estimate. - The requests are generated, so they are not your traffic. They are a stand-in for it. Edit
.mispick/queries.yamlto make them yours. - It measures one selection turn. Not multi-step agent behaviour, not tool output quality, not
recovery after a bad call. Arguments are checked against
inputSchemaand no further. - A low score can be correct. Two tools may genuinely overlap. mispick tells you the model cannot separate them; whether they should be separable is your call.
mispick fixoften accepts nothing, and that is working as intended. See the note above.- Token counts are estimates, and lower bounds — a real tokenizer and the provider's framing both add to them.
- Reasoning models spend heavily on hidden thinking. mispick turns thinking off by default for
Ollama (measured: 89s → 2.2s per call on
qwen3.5:4b, same answers);--thinkrestores it.
Development
uv sync
uv run pytest # offline; no model or network needed
uv run ruff check . && uv run mypy src
uv run mispick run --snapshot tests/fixtures/confusing_tools.json --model mock
--model mock is a deterministic offline backend. The test suite uses it, so pytest passes with
no Ollama, no keys and no network.
License
MIT. See LICENSE.
Metadata
Release files for mispick 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| mispick-0.1.0.tar.gz | 218.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| mispick-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 290.7 kB
Release files / mispick-0.1.0.tar.gz
| Download URL | mispick-0.1.0.tar.gz |
|---|---|
| Size | 218.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
2d1a693b923dac74b02ed011be355743c2d4f81d6cda319c82af878a0a426b30
|
|
BLAKE2b-256 checksum How to use checksums |
31bbb5d6c7ccb9f87c9bd59aaa6541198b014501ceb38d684b65b17d85095e33
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / mispick-0.1.0-py3-none-any.whl
| Download URL | mispick-0.1.0-py3-none-any.whl |
|---|---|
| Size | 72.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2fed27598f6295024654a51e525d9d1c1a656a42ce607b9c3b4be4955f7fec8c
|
|
BLAKE2b-256 checksum How to use checksums |
ed894b4986ce69c555a99faa4ed2b94f8b1b1b7269ea347301b1e929afc03ed1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency log