MCP server inspector for the whole tools/list
Paste an MCP tools/list answer and get an exact report: two tools described the same way, names that collide once case is ignored, one enum spelled two ways, plus ten per-tool schema checks. Runs in your browser, nothing is uploaded.
Your input is processed in this browser. It is not uploaded. A file or pasted rows go nowhere. A URL is fetched by your browser directly from the address you give it. Sharing creates a link containing the data you choose to include.
This link will contain
Anyone with the link can read this. Compression is not encryption.
What it does
A tool schema can be faultless on its own and still break the server it belongs to. Two tools whose descriptions say the same thing leave a model guessing which one to call. Two names that differ only by an underscore look like one name to anything that matches loosely. An enum spelled open in one tool and OPEN in the next teaches a model one spelling and fails the calls that expected the other.
This page reads a tools/list answer, JSON-RPC envelope and all, and reports in two layers: what is wrong between tools first, then each tool on its own under the same ten rules as the tool-schema linter.
How this differs from the official MCP Inspector
The official Inspector is a runtime client. It connects to your server, lists what it exposes, and lets you call a tool and see what comes back. It answers “does this server work”.
This page never connects to anything. It reads the answer you already have and compares the tools in it with each other, which is the one thing a client looking at a server cannot do for you. It answers “can a model choose between these tools”. Use both. The Inspector is also the easiest way to get the input for this page.
How to get a tools/list answer
From the official Inspector, in one command
The Inspector ships a CLI alongside its web UI. One command connects, asks for the tool list, prints it and exits.
npx @modelcontextprotocol/inspector --cli node build/index.js \
--method tools/list --format json
For a server that is already running somewhere, point at its URL instead:
npx @modelcontextprotocol/inspector --cli https://api.example.com/mcp \
--transport http --method tools/list --format json
--format json prints a single JSON object with no banners around it. Paste the whole thing. That is the shape this page reads first.
From a remote server with curl
Three requests, because tools/list only answers inside a session. The first one hands back a session id in a response header, so keep the headers.
URL=https://api.example.com/mcp
JSON='Content-Type: application/json'
ACCEPT='Accept: application/json, text/event-stream'
curl -s -D headers.txt -o init.txt "$URL" -H "$JSON" -H "$ACCEPT" -d '{
"jsonrpc":"2.0","id":1,"method":"initialize",
"params":{"protocolVersion":"2025-06-18","capabilities":{},
"clientInfo":{"name":"curl","version":"1"}}}'
SESSION=$(awk 'tolower($1)=="mcp-session-id:"{print $2}' headers.txt | tr -d '\r')
curl -s "$URL" -H "$JSON" -H "$ACCEPT" \
-H "Mcp-Session-Id: $SESSION" -H 'MCP-Protocol-Version: 2025-06-18' \
-d '{"jsonrpc":"2.0","method":"notifications/initialized"}'
curl -s "$URL" -H "$JSON" -H "$ACCEPT" \
-H "Mcp-Session-Id: $SESSION" -H 'MCP-Protocol-Version: 2025-06-18' \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
| sed -n 's/^data: //p'
Three things bite here. The Accept header has to list both application/json and text/event-stream, or the server is entitled to refuse. The answer usually arrives as a server-sent event rather than a plain body, which is why the last line pulls the JSON off the data: line. And a session id, once the server has given you one, belongs on every request after it.
If the reply is truncated or arrives with a trailing comma, the JSON repairer will close it and say what it changed.
From the SDKs
TypeScript, where the result is already in wire shape:
import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
const client = new Client({ name: 'list-tools', version: '1.0.0' });
await client.connect(new StreamableHTTPClientTransport(new URL('http://localhost:3000/mcp')));
console.log(JSON.stringify(await client.listTools(), null, 2));
Python, where it is not:
async with streamable_http_client("http://localhost:8000/mcp") as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
tools = await session.list_tools()
print(tools.model_dump_json(indent=2, by_alias=True, exclude_none=True))
by_alias=True is not optional. Without it the Python SDK serialises its own field names, so inputSchema comes out as input_schema. This page still reads that document and still reports on it, but it says so in a note and stands down the two checks about safety annotations, because nothing in the document looks like MCP any more.
From a stdio server
A stdio server speaks newline-delimited JSON-RPC on its standard input and output, so the handshake can be piped in. It is fiddly by hand, and the Inspector CLI at the top of this section drives exactly that transport for you. Use it unless you have a reason not to.
A note on paging
A server with many tools may return one page and a cursor. The Inspector CLI and both SDK clients follow the cursor and give you every tool. A single curl gets the first page only, and this page can only compare the tools it is given.
The checks, in the order the report runs them
Eighteen in total: eight that only exist between tools, and the linter’s ten underneath. A finding is either breaks, meaning a call fails or a provider rejects the schema, or degrades, meaning the model gets it wrong more often than it should.
Across the whole server
These are the ones no single-schema check can produce, because the fault is not inside any one tool.
- breaks
name_collides_folded. Two tool names are the same string once case and separators are ignored, assearch_ordersandsearchOrdersboth fold tosearchorders. Anything that matches tool names loosely, which includes some clients and most logs, cannot tell them apart. - breaks
same_description_exact. Two tools carry exactly the same description. A model choosing between them has nothing to go on but the names. - breaks
same_description_case. The same, once case and spacing are ignored. - breaks
same_description_words. The same, once case, punctuation and small words such as “the” and “for” are ignored. The report prints what both descriptions reduce to, so you can read the pair and check the claim. - degrades
enum_differs_across_tools. One property name carries different enum values in different tools. A model that learns one spelling uses it everywhere, and the calls that expected the other spelling fail. - degrades
mixed_name_convention. The server names tools in snake_case and camelCase at once. One convention throughout is easier to remember and easier to complete. - degrades
no_annotations. This tool declares neitherreadOnlyHintnordestructiveHintwhile others on the server do, so a client cannot tell whether calling it is safe. - degrades
no_annotations_anywhere. No tool declares either. Annotations are optional in the MCP specification, so that is one decision to make rather than one fault per tool, and it is reported once.
One thing this page cannot check for you: the specification also says a client must treat annotations as untrusted unless the server is trusted. A readOnlyHint is a claim the server makes about itself. Reading it here tells you what the server says, not what it does.
Tool by tool
The same ten rules the tool-schema linter runs on a single schema, run here on every tool at once.
- breaks
tool_no_description. The tool has no description, so the model has no idea when to call it. - breaks
param_no_type. A parameter has no usable type. Providers reject it and models free-type it. - breaks
required_missing_property.requirednames something that is not a property. The provider rejects the schema or the model invents the field. - breaks
duplicate_tool_name. A second tool has the same name. Which one is called is undefined. - breaks
tool_name_invalid. The name is not^[a-zA-Z0-9_-]{1,64}$. OpenAI and Anthropic reject it. - degrades
param_no_description. A parameter has no description, so the model guesses from the name alone. - degrades
enum_case_collision. Two enum values differ only by case or spacing. The model picks one at random. - degrades
description_restates_name. The description of a parameter only restates its name. It adds nothing the model did not already have. - degrades
nested_optional_required_children. An optional object has required children. The model either omits it or sends it half-filled. - degrades
too_many_parameters. More than twenty parameters on one tool. This one is a heuristic and the report says so: accuracy falls off somewhere around there, and the fix is usually to split the tool.
A worked example
A four-tool order server. Every tool in it would pass a single-schema review, and it has a name collision, a pair of interchangeable descriptions, an enum spelled two ways, a tool that says nothing about itself and a parameter with no type. The “Load an example” button above loads this exact document.
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"tools": [
{
"name": "search_orders",
"description": "Search for orders",
"inputSchema": {
"type": "object",
"properties": {
"customer_id": {
"type": "string",
"description": "The customer id"
},
"status": {
"type": "string",
"description": "Order status",
"enum": [
"open",
"shipped"
]
}
},
"required": [
"customer_id"
]
},
"annotations": {
"readOnlyHint": true
}
},
{
"name": "findOrders",
"description": "Search orders.",
"inputSchema": {
"type": "object",
"properties": {
"customerId": {
"type": "string",
"description": "Which customer to look under"
},
"status": {
"type": "string",
"description": "Order status",
"enum": [
"OPEN",
"SHIPPED"
]
},
"limit": {
"type": "integer"
}
},
"required": [
"customerId"
]
},
"annotations": {
"readOnlyHint": true
}
},
{
"name": "searchOrders",
"description": "Look up orders by customer.",
"inputSchema": {
"type": "object",
"properties": {
"customerId": {
"type": "string",
"description": "Which customer to look under"
}
},
"required": [
"customerId"
]
},
"annotations": {
"readOnlyHint": true
}
},
{
"name": "refund_order",
"description": "",
"inputSchema": {
"type": "object",
"properties": {
"order_id": {
"description": "The order to refund"
},
"amount": {
"type": "number",
"description": "How much to refund, in the order currency"
}
},
"required": [
"order_id"
]
}
}
]
}
}
The report:
4 tools, 4 breaking, 5 degrading
Across the whole server
- breaks
search_orders, searchOrders: The names “search_orders” and “searchOrders” are the same string once case and separators are ignored, both being “searchorders”. Anything matching tool names loosely, which includes some clients and most logs, cannot tell these two apart. - breaks
search_orders, findOrders: Tools “search_orders” and “findOrders” have the same description once case, punctuation and small words such as “the” and “for” are ignored: both reduce to “search orders”. Read the two side by side. If you cannot say which one to call, neither can a model. - degrades
search_orders, findOrders: The property “status” carries different enum values in different tools on this server. A model that learns one spelling will use it everywhere, and the calls that expected the other spelling fail. - degrades
search_orders, refund_order, findOrders, searchOrders: This server names tools two ways: search_orders, refund_order in snake_case, and findOrders, searchOrders in camelCase. One convention throughout is easier to remember and easier to complete. - degrades
refund_order: This tool declares neither readOnlyHint nor destructiveHint, while others on this server do, so a client cannot tell whether calling it is safe. Annotations are optional in the MCP specification; what this reports is the inconsistency.
Tool by tool
- breaks
refund_orderatdescription: The tool has no description, so the model has no idea when to call it. - breaks
refund_orderatparameters.properties.order_id: Parameter “order_id” has no usable type; providers reject it and models free-type it. - degrades
search_ordersatparameters.properties.customer_id: The description of “customer_id” only restates its name; it adds nothing the model did not already have. - degrades
findOrdersatparameters.properties.limit: Parameter “limit” has no description; the model guesses from the name alone.
Two of those are worth reading twice. search_orders and searchOrders are different tools that a log line shows as one name. And refund_order is the tool that moves money: it says nothing about itself, and it is the one tool here that will not say whether calling it is safe.
What it does not do
It never scores how similar two descriptions are. Every description finding is an exact match after a stated amount of normalising, and the sentence says what was ignored, so you can read the pair and check the claim yourself. Safety annotations are optional in the MCP specification, so a server that declares none gets one note rather than one fault per tool. A clean run says what was checked and that nothing was found, which is a different and more honest claim than saying the server is fine.
Nothing is uploaded. The document is parsed in the page and never leaves the browser, which is also why the URL option only works for endpoints that allow cross-origin reads. If you want to share a report from a server whose tool descriptions carry anything sensitive, run it through the trace redactor first.
Who this is for
Four kinds of people, and the cross-server layer matters most to the last two.
- Developers building an MCP server, checking what it exposes before wiring it into Claude Desktop, Claude Code, Cursor or VS Code.
- Integration engineers adding somebody else’s server, who would like to read what it offers before trusting it.
- Anyone reviewing tool descriptions for prompt-injection and tool-poisoning patterns, where the descriptions are the attack surface and reading all of them together is the job.
- Platform and registry teams curating many servers at once. Per-tool checks pass on every server in a catalogue that is collectively unusable, because the collisions are between servers, not inside them.
If you are building the server itself, that is the work Rockstone does for clients. The other seven tools are on the tools index.
Built and maintained by Mats Bergsten, Rockstone AI Development. Last updated 16 September 2026.