Skip to main content

Testing and Debugging

8.1 MCP Inspector

The official interactive debugging tool that lets you test every capability of your Server in a browser.
npx @modelcontextprotocol/inspector node dist/index.js
The Inspector opens a web interface where you can:
  • View all tools, resources, and prompts exposed by the Server
  • Manually enter parameters and execute tools
  • Inspect the raw JSON-RPC requests and responses
  • Validate inputSchema and outputSchema
  • Test resource reading and subscriptions
  • Test prompt retrieval (with argument filling)
  • View the Server’s declared capabilities

Testing Remote Servers with Inspector

The Inspector also supports testing Streamable HTTP Servers:
npx @modelcontextprotocol/inspector --url https://my-server.example.com/mcp
You can attach authentication headers:
npx @modelcontextprotocol/inspector --url https://my-server.example.com/mcp --header "Authorization: Bearer YOUR_TOKEN"

8.2 Unit Testing

Test each tool’s execution logic (without involving the MCP protocol layer):
import { describe, it, expect } from "vitest";

describe("search_products tool", () => {
  it("returns matching products", async () => {
    const result = await searchProducts("running shoes", { limit: 5 });
    expect(result.results).toHaveLength(5);
    expect(result.results[0]).toHaveProperty("name");
    expect(result.results[0]).toHaveProperty("price");
  });

  it("returns empty array for no matches", async () => {
    const result = await searchProducts("nonexistent_product_xyz", { limit: 5 });
    expect(result.results).toHaveLength(0);
  });

  it("price range filtering works", async () => {
    const result = await searchProducts("shoes", {
      minPrice: 50,
      maxPrice: 100,
      limit: 10
    });
    result.results.forEach(p => {
      expect(p.price).toBeGreaterThanOrEqual(50);
      expect(p.price).toBeLessThanOrEqual(100);
    });
  });
});

Testing Tool Error Handling

describe("get_order tool", () => {
  it("returns isError when order does not exist", async () => {
    const result = await getOrderTool({ orderId: "INVALID-001" });
    expect(result.isError).toBe(true);
    expect(result.content[0].text).toContain("not found");
  });
});

8.3 Integration Testing

Test the complete MCP protocol interaction, including initialization, capability negotiation, and tool calls:
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";

describe("MCP Server Integration Tests", () => {
  let client: Client;

  beforeAll(async () => {
    const transport = new StdioClientTransport({
      command: "node",
      args: ["dist/index.js"]
    });
    client = new Client({
      name: "test-client",
      version: "1.0.0"
    });
    await client.connect(transport);
  });

  afterAll(async () => {
    await client.close();
  });

  it("initializes successfully and returns Server info", () => {
    const info = client.getServerInfo();
    expect(info.name).toBe("my-commerce-server");
  });

  it("tool list is not empty", async () => {
    const tools = await client.listTools();
    expect(tools.tools.length).toBeGreaterThan(0);
  });

  it("tool execution returns correct format", async () => {
    const result = await client.callTool("search_products", {
      query: "shoes"
    });
    expect(result.content[0].type).toBe("text");
    const data = JSON.parse(result.content[0].text);
    expect(data).toHaveProperty("results");
  });

  it("resource list is accessible", async () => {
    const resources = await client.listResources();
    expect(resources.resources.length).toBeGreaterThan(0);
  });

  it("resources are readable", async () => {
    const result = await client.readResource(
      "commerce://catalog/categories"
    );
    expect(result.contents[0].mimeType).toBe("application/json");
  });

  it("prompts are accessible", async () => {
    const prompts = await client.listPrompts();
    expect(prompts.prompts.length).toBeGreaterThan(0);
  });
});

8.4 Protocol Compliance Verification

Key checkpoints to ensure your Server conforms to the MCP specification:
CheckpointVerification Method
initialize responds correctlyInspect initialization handshake in Inspector
Capability declarations are completeCheck that capabilities declare all provided primitives
tools/list pagination is correctTest cursor pagination with large tool sets
isError is used correctlyBusiness errors use isError, not JSON-RPC error
Notification format is correctNotification messages have no id field
Content types are correcttext/image/audio type fields are complete
structuredContent matches outputSchemaWhen outputSchema exists, return structuredContent

8.5 Common Debugging Techniques

ProblemDiagnosisSolution
Server not respondingCheck stderr outputConfirm the process started successfully
Tools missing from listCheck tools/list in InspectorReview tool registration code
Parameter type errorsCheck inputSchema in InspectorCorrect the JSON Schema
Incorrect return data formatCheck raw response in InspectorEnsure content array format is correct
Claude cannot see the ServerCheck claude_desktop_config.jsonConfirm the path and command are correct
Initialization timeoutCheck Server startup timeReduce blocking operations during startup
Streamable HTTP 401Check OAuth configurationConfirm the token is valid and not expired
Session lostCheck Mcp-Session-Id headerEnsure Client includes it in every request

stderr Logging

Logs from a stdio Server must be output to stderr:
// Correct -- output to stderr
console.error("[INFO] Server started");
console.error(`[DEBUG] Processing request: ${method}`);

// Wrong -- will break protocol communication
console.log("Server started");  // stdout is the protocol channel

Log Levels

The MCP specification defines standard log levels. Servers can send log messages to the Client via notifications/message:
// Server -> Client sends log notification
server.sendLoggingMessage({
  level: "warning",
  logger: "inventory",
  data: "SKU SHOE-001 inventory below safety threshold"
});
The Client can set the minimum log level it wants to receive via logging/setLevel.

Debugging Streamable HTTP

For remote Servers, you can use curl to manually send JSON-RPC requests:
curl -X POST https://my-server.example.com/mcp \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "MCP-Protocol-Version: 2025-11-25" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'

Next Chapter: Deployment Guide — Local deployment vs remote deployment