Debugging tool calls
Tool problems look alike from the outside and have very different causes. Work through them in order rather than guessing.
First: is the tool even there
Before anything else, confirm the harness loaded it.
claude mcp list
Most harnesses have an equivalent listing. If your server is missing, nothing downstream matters. Check for invalid JSON in the config, a wrong path, or a server that crashed on startup. A single trailing comma removes every server at once.
Second: does it work without the agent
Take the model out of the loop:
npx @modelcontextprotocol/inspector
The Inspector calls your tools directly. If the tool fails here, the problem is your server and the agent is innocent. This step saves the most time and is the one most often skipped.
Third: is it being chosen
If the tool loads and works but never gets called, the description is the problem, not the model.
Test it by describing your situation in your own words without naming the tool. If it is not chosen, rewrite the description to include the trigger: "Use when the user asks about X."
A quick check is to ask the agent directly which tools it has and when it would use each. Its answer tells you how the descriptions actually read.
Fourth: are the arguments right
If it is called with wrong arguments, the schema is underspecified. Add .describe() to every parameter, replace free strings with enums where the domain is fixed, and give defaults for anything optional.
Do not fix this in the prompt. The schema travels with the tool; the prompt does not.
Fifth: is it looping
Same call, same failure, repeatedly, means your error message does not say what to do differently.
Rewrite the error to name the fix. Invalid date format, use YYYY-MM-DD ends the loop. Error: 400 guarantees it continues.
Common specifics
Server starts then dies. Something is writing to stdout. Stdio servers use stdout for the protocol itself, so any stray console.log corrupts it. Log to stderr.
Works alone, fails in the harness. Usually environment. The harness launches the server with a different working directory and a different environment than your shell. Use absolute paths and pass variables explicitly in the config.
Worked yesterday, not today. An @latest install pulled a new version. Pin the version while you investigate.
Authentication fails silently. The variable name in your config does not match what the server reads. API_KEY when it wants FIRECRAWL_API_KEY produces a config that looks correct and never authenticates.
The order matters
Loaded, then works standalone, then chosen, then called correctly. Each step assumes the previous one passed. Jumping to the description when the server never started wastes an afternoon.