<img height="1" width="1" style="display:none" src="https://www.facebook.com/tr?id=1063935717132479&amp;ev=PageView&amp;noscript=1 https://www.facebook.com/tr?id=1063935717132479&amp;ev=PageView&amp;noscript=1 "> Debugging with MCP Inspector

Debugging with MCP Inspector

MCP Inspector is a client you can point at any MCP server to list its capabilities and call them by hand. You've used its --cli mode for single calls in earlier lessons. The web client is the fuller surface, and it's what this lesson uses: you'll work through its panels, then use them to find and fix a bug that reports itself as a success.

Connecting on the modern protocol

Inspector connects on the legacy protocol era by default. To get the modern era (2026-07-28), give your server an entry in an Inspector catalog file with protocolEra set, then launch against that file:

{
  "mcpServers": {
    "demo-server": {
      "type": "stdio",
      "command": "npx",
      "args": ["tsx", "src/stdio-server.ts"],
      "protocolEra": "modern"
    }
  }
}
npx @modelcontextprotocol/inspector --catalog inspector-catalog.json

Keep that file in your project root, not .vscode/. It belongs to Inspector alone, unrelated to .vscode/mcp.json or Claude Code's .mcp.json.

Launch it

  1. Create inspector-catalog.json in your project root with the entry above.
  2. Run the launch command. Open the URL it prints exactly as printed: the token in it authenticates your browser to Inspector's backend, which can spawn processes on your machine.
  3. From the Servers tab, connect to demo-server.
  4. Open Connection Info and confirm Protocol reads 2026-07-28 and Era reads Modern.

The panels

Once connected, the tab bar across the top carries one panel per capability your server declared:

PanelWhat you see in it
ServersYour server list, connection state, and per-server settings
ToolsEvery registered tool, its arguments as a generated form, and the result of a call
PromptsEvery registered prompt, its arguments, and the messages it renders
ResourcesEvery registered resource by URI, and the content a read returns

The monitoring panels sit in a separate column down the right-hand side rather than in that tab bar:

PanelWhat you see in it
ProtocolThe JSON-RPC transcript: each request paired with its response
ConsoleYour server process's stderr, which is where its own logging goes

Keeping them in their own column is the point: you can watch the traffic while you work in Tools or Resources, instead of switching away from what you're doing.

Note: a server with a logging capability, task support, or an HTTP transport also gets Logs, Tasks, or Network panels alongside those two. Yours has none of those, so they don't appear.

Where to look when a call goes wrong

  1. Is content populated? Empty content on a successful call means the result shape is wrong, almost always a misspelled or missing key.
  2. Is isError set? If so, the message text is your error, and the fix is usually in your handler.
  3. Did the request carry the arguments you expected? Check the request half of the Protocol entry. A tool behaving as though it got nothing may have received nothing.
  4. Is there a matching response at all? A request with no paired response means the server didn't answer, so look for a crash in the Console panel rather than a logic bug.

Break it and debug it

Introduce a bug in add-task and track it down. Restart the server entry in Inspector after each edit so it picks up the change.

Bug 1. Change your handler's return to misspell the result key:

return { message: [{ type: "text", text: `Added: ${title}` }] };
  1. Call add-task from the Tools panel. Does it report an error?
  2. Open Protocol and read the result. Where did your text go, and what is content?
  3. Why did neither the SDK nor Inspector reject this?

Bug 2. Now fix the key but return content as a single object instead of an array:

return { content: { type: "text", text: `Added: ${title}` } };
  1. Call it again. How does this failure differ from Bug 1, and where does Inspector show it?
  2. Fix the tool. Which of the two bugs would you rather ship, and what does that tell you about which failures to look for?

What you should see

Bug 1 reports success, and the Tools panel shows an empty result:

{
  "content": [],
  "message": [{ "type": "text", "text": "Added: Buy milk" }],
  "resultType": "complete"
}

Nothing is invalid. content is absent so the SDK defaults it to empty, and message is an unrecognized key that gets passed through untouched. Both halves are individually acceptable, so there's nothing to reject. A model calling this tool gets an empty result with no explanation.

Bug 2 fails loudly, and the Tools panel shows the error directly:

Invalid tools/call result: expected array, received object

TypeScript also catches it before you ever run the server. Bug 2 is the one you'd rather ship every time: it names the field and what was wrong with it, and it fails the moment you call the tool. Bug 1 is the dangerous class, a successful-looking call that delivers nothing, invisible until someone notices the model has no idea what your tool returned. That's why the first question in the list above is whether content is populated, and why calling every tool once and asserting content is non-empty catches this whole family of bugs.

Check your understanding

Question 1 of 3

Why wasn't the misspelled key rejected?

Your handler returns `{ message: [...] }` instead of `{ content: [...] }`. The call reports success and the result shows `"content": []`. Why did neither the SDK nor Inspector catch it?