MCP Tool Definition Linter

Paste a tools/list response. Get the duplicate names, the descriptions that never say when to call the tool, the pairs a model cannot tell apart, and the exact number of bytes your tool definitions add to every request.

Paste below, or drop a file anywhere on this panel

Or drop a file anywhere on this panel. Nothing is uploaded: the analysis runs in this tab.

The answer appears here

Paste on the left and press Lint. Nothing leaves this tab.

Examples

Real input you can load into the tool above. Each one shows a different thing going wrong, because that is what the tool is for.

Two tools a model cannot tell apart

With the same vocabulary in both descriptions there is nothing to choose on, so it picks by name similarity

{"tools": [
  {"name": "get_user", "description": "Fetch a user record from the database by identifier. Call this when you need user details.", "inputSchema": {"type": "object", "properties": {}}},
  {"name": "fetch_user", "description": "Fetch a user record from the database by identifier. Call this when you need user details.", "inputSchema": {"type": "object", "properties": {}}}
]}

A description with no trigger

Stating capability without stating when to call it under-triggers, and the lift comes from the tool's own description

{"tools": [
  {"name": "get_weather", "description": "Returns the current weather for a city as a JSON object.", "inputSchema": {"type": "object", "properties": {"city": {"type": "string", "description": "City name."}}}}
]}

Two tools with one name

Most clients keep the last and drop the first without reporting it, so a tool silently stops existing

{"tools": [
  {"name": "search", "description": "Search the docs. Call this when asked how something works.", "inputSchema": {"type": "object", "properties": {}}},
  {"name": "search", "description": "Search the issue tracker. Call this when asked about bugs.", "inputSchema": {"type": "object", "properties": {}}}
]}

Common mistakes

These are the ones that fail silently. The config is accepted, nothing raises an error, and the consequence arrives later.

  1. Writing descriptions that say what the tool does and not when to call it

    Capability alone under-triggers. Models reach for a tool far more reliably when the trigger condition is written out, and that lift comes from the tool's own description rather than the system prompt.

    Instead:Add the clause: "Call this when the user asks about current prices or recent events."

  2. Distinguishing two similar tools by name alone

    When both descriptions share the same vocabulary the model has nothing to choose on, so it falls back to name similarity or list position and is right about half the time.

    Instead:Write contrastively and name the other tool: "Use this for a single record. For more than ten, use bulk_fetch."

  3. Adding and removing tools during a conversation

    Tool definitions render at position zero, ahead of the system prompt. Changing them changes the first byte of the prefix, which discards the entire prompt cache including the conversation history, not just the tool section.

    Instead:Keep the tool set fixed for the life of a conversation, and serialize it deterministically.

Your tool list is a prompt, and you pay for it on every request

Tool definitions sit in the context before the conversation does. That makes them both the model's only guide to what your server can do, and a fixed cost on every single turn.

Say when to call it, not only what it does

A description that states capability under-triggers. Models reach for a tool far more reliably when the trigger condition is spelled out, and the lift comes from the tool's own description rather than from the system prompt. "Search the documentation index" is a description. "Call this when the user asks how to do something in the product" is the half that changes behaviour.

Two tools described alike are chosen at random

When several tools share the same vocabulary the model has nothing to discriminate on and falls back to name similarity or list position. The fix is not longer descriptions; it is contrastive ones. Name the other tool: "Use this for a single record. For more than ten, use bulk_fetch instead."

Names are prefixed before they reach the model

Clients namespace tools by the server name, so what the model sees is longer than what you wrote. A name that is legal alone can exceed the length limit once prefixed, and dots or spaces are rejected outright by the strictest consumer. Letters, digits, underscore and hyphen, and leave headroom.

Changing the tool set mid-conversation discards the whole cache

Tools render at the very front of the prompt prefix, before the system prompt and before the messages. Adding or removing one changes byte zero, which invalidates every cache breakpoint after it, not just the tool section. That is why a server that adds tools dynamically is expensive in a way that does not show up in the tool list's own size.

The byte count here is exact; the token figure is not

This page measures the serialized definitions in bytes, which it can do precisely. The token range beside it is derived from those bytes and is an estimate, because a real count needs the model's tokenizer and this page ships no tokenizer and calls no API. For an exact number, use the provider's token counting endpoint against the same JSON.

What this cannot see

Whether the descriptions are accurate, whether the tools do what they claim, and whether the model actually confuses the pair it flags. The ambiguity check compares vocabulary, which is a proxy: two tools can share most of their words and still be clearly distinguished by one of them, and two tools can share none and still be confusable. Treat a flagged pair as worth rereading, not as a defect. It also cannot know your client's name-length limit, so it applies the strictest one in common use.