My agent kept calling the wrong tool. The LLM was correct — I could see it reasoning about which tool to use, and its logic was sound. The MCP server was correct — I had tested the tool function in isolation and it worked. But something between the two was wrong, and I had absolutely no way to see what it was.
No logs. No request trace. No response body. Just a failed agent run and a vague error message that told me nothing about which layer had broken. If this had been a REST API, I would have had Postman, Swagger, middleware logging, and an OpenTelemetry trace within the hour. With an MCP server, I was effectively debugging blind.
This is the problem that most engineers hit within their first week of building with the Model Context Protocol. MCP is a powerful standard — it gives AI agents a clean, structured way to interact with tools, data sources, and services. But the debugging story has been, until recently, far behind what we expect from modern backend infrastructure.
That is changing. This article is a complete walkthrough of every tool and approach that exists today for debugging MCP servers — from your first local test all the way to production observability.
Why MCP Servers Are Harder to Debug Than REST APIs
To understand the debugging gap, you need to understand what MCP actually does under the hood. When your AI agent talks to an MCP server, it is exchanging JSON-RPC messages — typically over stdio (standard input/output) or HTTP/SSE. This is invisible by default. Unlike an HTTP request that you can intercept with a proxy or inspect in your browser's network tab, stdio communication happens between processes, in memory, with no built-in way to observe it.
For example, when Claude calls a tool on your MCP server, the actual exchange looks like this at the protocol level:
// What Claude sends to your MCP server (JSON-RPC over stdio)
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_service_status",
"arguments": { "service_name": "auth-service" }
}
}
// What your server responds
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [{ "type": "text", "text": "auth-service is healthy" }]
}
}
When this works, you never see it. When it breaks, you also never see it — unless you have deliberately set up the tooling to intercept and surface these messages. That is the debugging gap.
| Capability | REST API debugging | MCP server debugging (today) |
|---|---|---|
| Interactive test client | Postman / Insomnia | MCP Inspector, MCPJam |
| Schema browser | Swagger / OpenAPI UI | MCP Inspector tool list |
| Terminal quick-test | curl | MCP Inspector CLI (limited) |
| Request/response logging | Middleware (Express, FastAPI) | FastMCP logging, custom middleware |
| Distributed tracing | OpenTelemetry (mature) | FastMCP OTel, early stage |
| Observe real client traffic | Proxy / network tab | Requires proxy layer (mcps-logger) |
| CI contract testing | Newman / Dredd / Pact | Bellwether, MCPSpec (early) |
| Schema drift detection | openapi-diff, Optic | Bellwether (growing) |
The gap is real, but it is closing faster than you might think. Let us walk through every tool that exists today, what it actually does, and when to reach for it.
The Tools That Exist Today
1. MCP Inspector — Your Postman for MCP
Getting started is one command — no installation needed:
# Launch against a Node.js MCP server
npx @modelcontextprotocol/inspector node my-mcp-server.js
# Launch against a Python MCP server
npx @modelcontextprotocol/inspector python server.py
# Launch against a running HTTP server
npx @modelcontextprotocol/inspector --url http://localhost:3000/mcp
# Custom ports if 6274/6277 are in use
PORT=8080 PROXY_PORT=8081 npx @modelcontextprotocol/inspector node server.js
The UI runs at http://localhost:6274. The proxy that connects to your server runs at port 6277. You will immediately see your server's full capability list — every tool, its input schema, its description, and whether it is responding correctly to tools/list.
CLI mode for CI pipelines: This is the part most people miss. MCP Inspector also has a headless CLI mode that you can use in automated pipelines without a browser:
# List all tools (CI-friendly, returns JSON)
npx @modelcontextprotocol/inspector --cli node server.js \
--method tools/list
# Call a specific tool and assert on the response
npx @modelcontextprotocol/inspector --cli node server.js \
--method tools/call \
--tool-name get_service_status \
--tool-arg service_name=auth-service
npx @modelcontextprotocol/inspector@latest to ensure you are on the patched version. If you are testing untrusted third-party MCP servers, run Inspector inside a container or VM.
One important limitation to understand: MCP Inspector is not a traffic proxy. It acts as its own client — it shows you Inspector-to-server traffic, not Claude-to-server traffic or Cursor-to-server traffic. If your agent behaves differently than the Inspector does, you are seeing a different code path. To observe real client traffic, you need a proxy layer — more on that below.
2. MCPJam Inspector — When You Need an LLM Playground
mcpjam server doctor for one-shot health checks and OAuth validation.# Install MCPJam CLI
npm i -g @mcpjam/cli
# One-shot health check — connectivity, auth, tool sweep
mcpjam server doctor --url https://your-mcp-server.com/mcp
# List tools
mcpjam tools list --url https://your-mcp-server.com/mcp
# Call a specific tool with a JSON params file
mcpjam tools call \
--url https://your-mcp-server.com/mcp \
--tool-name get_service_status \
--tool-args @params.json \
--format json
# Run protocol conformance validation
mcpjam server conformance --url https://your-mcp-server.com/mcp
The conformance command is particularly valuable before going to production — it validates your server against the MCP protocol specification across all protocol domains, including tool discovery, resource management, and session lifecycle. Think of it as an automated contract test that verifies your server is actually spec-compliant, not just "works on my machine."
Choose MCP Inspector for protocol-level debugging and CI pipelines. Choose MCPJam when you want to simulate how an LLM agent will actually behave — tool selection, multi-turn conversation flow, and edge cases in how your tools get called in context.
3. FastMCP — The Framework That Brings Observability In
# Install FastMCP
pip install fastmcp
# A FastMCP server with built-in observability
import logging
from fastmcp import FastMCP, Context
logger = logging.getLogger(__name__)
mcp = FastMCP("platform-tools-server")
@mcp.tool()
async def get_service_status(service_name: str, ctx: Context) -> str:
"""Check the operational status of a platform service"""
logger.info(
"Tool called: get_service_status",
extra={"service": service_name, "trace_id": ctx.request_id}
)
try:
result = check_service(service_name)
logger.info("Tool succeeded", extra={"service": service_name, "status": result})
return result
except Exception as e:
logger.error("Tool failed", extra={"service": service_name}, exc_info=True)
raise
The key observability insight with FastMCP is correlation. Every tool invocation has a request_id available via the context object. Log that ID on every line and you can trace the full lifecycle of a single agent call through your logs — which tool was called, with what parameters, how long it took, whether it succeeded, and what it returned. Without this correlation, your logs are a flat list of events with no way to connect them.
For example, when you add OpenTelemetry spans to your FastMCP tools, you can see in Jaeger or Grafana Tempo exactly where time is being spent: is the latency in the MCP protocol layer, in your business logic, or in the downstream API call? That distinction is impossible to make without distributed tracing.
4. OpenTelemetry — Production-Grade Observability
// Node.js MCP server with OpenTelemetry (SDK setup)
import { NodeSDK } from "@opentelemetry/sdk-node";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
import { resourceFromAttributes } from "@opentelemetry/resources";
const sdk = new NodeSDK({
resource: resourceFromAttributes({
"service.name": "platform-tools-mcp-server",
"mcp.server.name": "platform-tools",
"deployment.env": process.env.ENV,
}),
traceExporter: new OTLPTraceExporter({
url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT + "/v1/traces",
}),
});
sdk.start();
// Instrument your tool calls with spans
import { trace } from "@opentelemetry/api";
const tracer = trace.getTracer("mcp-tools");
async function getServiceStatus(serviceName: string) {
return tracer.startActiveSpan("tool.get_service_status", async (span) => {
span.setAttributes({
"mcp.tool.name": "get_service_status",
"mcp.tool.input.service_name": serviceName,
});
try {
const result = await checkService(serviceName);
span.setStatus({ code: SpanStatusCode.OK });
return result;
} catch (err) {
span.recordException(err);
span.setStatus({ code: SpanStatusCode.ERROR });
throw err;
} finally {
span.end();
}
});
}
The key metrics to instrument on every MCP server in production:
- Tool call latency — p50, p95, p99 per tool. Which tools are slow and how consistently.
- Tool error rate — broken down by tool name and error type. Are certain tools failing more than others?
- Tool call volume — which tools are actually being used by agents. Often reveals that half your tools are never called.
- Downstream dependency latency — the time your tool spends waiting for external APIs. This is usually where most latency lives.
5. mcps-logger / Proxy Approach — Seeing Real Agent Traffic
This matters more than it sounds. In practice, an LLM client calls your tools differently than you do manually in Inspector. The LLM may call tools in a different sequence, pass different argument shapes, or trigger edge cases in your tool schema that you never thought to test manually. A proxy logger surfaces exactly what is happening in the real call path.
CI/CD for MCP Servers — The Part Most Teams Skip
Manual testing is how you build confidence in development. Automated testing in CI is how you stop regressions from shipping to production on a Friday afternoon. The MCP tooling for CI is early but growing fast.
Schema drift is the most underrated failure mode. As one engineering team found out: "So many MCP servers silently change tool signatures between versions, and you never know if something broke until a user complains." Your tools are a contract with every agent that calls them. If you rename a parameter, change a return type, or remove a tool — anything that depends on your MCP server breaks silently.
Here is the three-gate testing approach that is emerging as a best practice for MCP servers in CI:
initialize handshake, tools/list response format, error handling. This catches protocol-level bugs before anything else.tools/list and asserts on the exact JSON output.#!/bin/bash
# Example CI script using MCP Inspector CLI
# Gate 1: Protocol compliance
npx @modelcontextprotocol/inspector@latest --cli node server.js \
--method tools/list > tools_output.json
# Gate 2: Schema snapshot comparison
if ! diff tools_output.json tools_snapshot.json; then
echo "❌ Tool schema changed — review diff before merging"
exit 1
fi
# Gate 3: Tool call assertions
npx @modelcontextprotocol/inspector@latest --cli node server.js \
--method tools/call \
--tool-name get_service_status \
--tool-arg service_name=test-service \
| jq -e '.result.content[0].text | length > 0' \
|| (echo "❌ Tool returned empty response" && exit 1)
echo "✅ All MCP gates passed"
The Right Approach for Engineers Building Agents That Connect to MCP
If you are not just building an MCP server but also building an agent that consumes MCP servers — or using an existing MCP server as a tool source — the debugging approach shifts. You are no longer just validating that your server implements the protocol correctly. You are debugging why your agent is selecting or not selecting certain tools, why it is passing arguments in unexpected ways, and why a multi-step tool chain is failing partway through.
Here is the recommended debugging workflow from day one to production:
Going back to that agent that kept calling the wrong tool — the fix was not in the LLM and it was not in the tool function. It was in the tool description. The MCP tool schema I had written for get_service_health was ambiguous — two tools had overlapping descriptions, and the LLM was consistently picking the wrong one. MCP Inspector's schema browser made this instantly visible the moment I looked at both tool descriptions side by side. Thirty seconds of debugging with the right tool versus hours of guessing without it.
The MCP debugging ecosystem is maturing fast. Six months ago, MCP Inspector was the only real option. Today you have a layered stack — Inspector for protocol testing, MCPJam for agent simulation, FastMCP for framework-level observability, OpenTelemetry for production traces, and a growing set of CI tools for schema governance. Use each layer at the right stage of your development lifecycle.
The engineers who will build reliable AI agents are not the ones who get lucky — they are the ones who instrument first, assume nothing, and treat their MCP servers with the same operational rigour they give to the rest of their stack.
Happy coding!! If you are building MCP servers or AI agents and hitting debugging walls, drop a comment or reach me on LinkedIn — happy to dig into specific scenarios. And if you are using a tool or approach I have not covered here, I would genuinely like to hear about it.
In my next article, I will go into sandboxing AI agents — how to ensure an agent with MCP tool access cannot do more than it should, and what the current isolation primitives look like across different deployment environments. Stay connected.