Connect five MCP servers to an agent and the model is choosing between forty tools on every message. Each server's author tested their own tools, but nobody tested the mix you ended up with.
tooltangle tests that mix. It writes realistic user messages for every tool, runs your model on them with all the tools attached, and shows which tools get confused with which. It can then rewrite the confusing descriptions, keeping a rewrite only if the model does measurably better on messages the rewrite was never shown.
Here is part of a real run against the reference time server, which has just two tools:
ERROR time.convert_time → time.get_current_time 44% (7 of 16)
e.g. "Our CEO wants to host an all-hands at 9:00 AM Eastern Time next Tuesday. What would that time correspond to in Dubai?"
accuracy 0.82 [0.67, 0.91] on 38 messages with gemini-3.5-flash-lite
Install
You need Python 3.12 or newer.
uvx tooltangle --help # run it without installing anything
pip install tooltangle # or install it into a project
Set GOOGLE_API_KEY in your environment or in a .env file in the directory you run from.
Gemini is the default because its free tier is enough to try the tool.
To test OpenAI models, install the extra, set OPENAI_API_KEY and name the model:
pip install "tooltangle[openai]"
tooltangle check mcp.json --model openai:gpt-5.4-mini
The test messages are still written by Gemini unless you also pass
--generator openai:<model name>. OpenAI support is new and not battle-tested yet. Other
providers that LangChain supports can be tried the same way after installing their LangChain
package, but they are untested.
Usage
tooltangle tools claude-desktop # list your tools and run the checks that need no model
tooltangle check claude-desktop # measure which tools the model confuses
tooltangle fix claude-desktop # rewrite confusing descriptions, keep the ones that hold up
tooltangle check claude-desktop # measure again with the rewrites applied
The argument says where the tools come from: claude-desktop, claude-code, cursor or
vscode, a path to an MCP config file, or a Python target such as my_agent/tools.py:TOOLS
with LangChain tools or plain functions. MCP servers are started only to list their tools.
tooltangle never calls a tool.
The repository has two examples to try first:
tooltangle tools examples/demo.mcp.json # five reference MCP servers, 38 tools
tooltangle check examples/support_tools.py:TOOLS # six LangChain tools of a support agent
To pick a model, compare runs the same messages through several and names the cheapest one
that a paired test can't show to be worse than the best:
tooltangle compare mcp.json -m google_genai:gemini-3.8-flash -m google_genai:gemini-3.5-flash-lite
How it works
- It loads the tools from all servers and runs the checks that need no model: duplicate names, missing or very short descriptions, deprecated tools, the token cost of the definitions.
- A generator model writes messages a real user might send: some for each tool, tricky ones for pairs that are easy to mix up, and some that need no tool. Messages that name the tool or repeat its description are dropped. A second pass labels every message with all the tools that would be a reasonable first call, so a message with two valid answers never counts as a mistake.
- The messages are split into dev and holdout halves and saved to
.tooltangle/queries.jsonl, which you can read, edit and commit. - The model under test gets each message with all the tools attached, and the first tool it calls is recorded.
- The report lists directed confusions with a rate and an example, needless tool calls, and
accuracy with a 95% Wilson interval, in the terminal and in
.tooltangle/report.html.
Looking before acting is not a confusion. Calling a read-only tool from the same server first,
such as git_status before git_commit, is reported as INFO. This uses the MCP
readOnlyHint annotation.
How fix decides what to keep
fix asks the generator to rewrite the descriptions of the most confused pairs, then tests
each rewrite:
- The generator sees mistakes only from the dev half. The rewrite is scored on the holdout half.
- It is kept only if a one-sided exact McNemar test gives p < 0.05 and, among related tools, it broke at most one more message than it fixed.
- The 0.05 is split across attempts, and a retry is told only the counts, never the messages.
- If a pair has too few mistakes to prove anything (fewer than six on holdout by default), it first writes more messages for that pair, and says so if that still isn't enough.
Accepted rewrites go to tooltangle.overrides.yaml, and the next check applies them. To use
them in your own agent:
from tooltangle import apply_overrides
tools = apply_overrides(tools) # LangChain tools; reads tooltangle.overrides.yaml
Apps like Claude Desktop or Cursor take descriptions straight from the server, so there a rewrite is a suggestion to pass on to the server's author.
CI
tooltangle check mcp.json --yes works as a CI step. Its exit codes:
| Code | Meaning |
|---|---|
| 0 | nothing at ERROR level |
| 1 | a tool loses at least 20% of its messages (3 or more) to one wrong choice, two tools share a name, or a tool has no description |
| 2 | the run failed: a bad config, a model that can't be created, or every request failing |
| 3 | the daily quota or the account's credit ran out |
Commit .tooltangle/queries.jsonl so every run scores the same messages, and cache
.tooltangle/cache.sqlite so a run with no changes makes no model calls. See
examples/github-workflow.yml.
Configuration
Settings are optional and go in tooltangle.toml in the directory you run from. The ones you
are most likely to change, with their defaults:
model = "google_genai:gemini-3.5-flash-lite" # the model under test
generator = "google_genai:gemini-3.8-flash" # writes and labels the test messages
language = "English" # language of the test messages
queries_per_tool = 10
requests_per_minute = 120
There are also generator_fallbacks, contrast_pairs, queries_per_contrast, concurrency,
system_prompt, model_options and a [prices] table; see
settings.py.
Cost
- Every model answer is cached, so running again without changes is free and an interrupted run picks up where it stopped.
- Before making more than 25 model calls it prints an estimate and asks.
--yesskips the question. Prices come from the public list in thegenai-pricespackage; a[prices]table intooltangle.tomloverrides them. - Requests are paced and retried on rate limits. When the generator's quota runs out, the next
model in
generator_fallbackstakes over.
License
Metadata
Release files for tooltangle 0.2.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 | Provenance | |
|---|---|---|---|---|
| tooltangle-0.2.0.tar.gz | 39.3 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| tooltangle-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 87.1 kB
Release files / tooltangle-0.2.0.tar.gz
| Download URL | tooltangle-0.2.0.tar.gz |
|---|---|
| Size | 39.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
82b6684bc590c5eacd2ab31556fbd8063d60342aa4e20da98abc0ba7e0be4323
|
|
BLAKE2b-256 checksum How to use checksums |
15664d28039d3b50112136ec302c991da7d3f82a76d913e5536391e9049ac508
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.7 {"installer":{"name":"uv","version":"0.12.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / tooltangle-0.2.0-py3-none-any.whl
| Download URL | tooltangle-0.2.0-py3-none-any.whl |
|---|---|
| Size | 47.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
bbe17176a2e276acb2186e2bae222e4eaf0fb25c35817a37c877a381a10f8a5f
|
|
BLAKE2b-256 checksum How to use checksums |
cef33ac5c4ab7f9729e6be7603675355ea770eee94ff9b71135b621ea00fa723
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.7 {"installer":{"name":"uv","version":"0.12.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Security for tooltangle 0.2.0
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.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.