# How to Debug an MCP Server: Inspector, Logs and Common Errors

> Debug an MCP server: MCP Inspector web and CLI, client logs in Claude Code, VS Code and Cursor, a curl server/discover probe, and what each error code means.

Published 2026-10-29 by mcp.tc. Canonical: https://mcp.tc/blog/debug-mcp-server

**TL;DR.** To debug an MCP server, test it outside your AI client first. Run MCP Inspector (`npx @modelcontextprotocol/inspector`) against the same command or URL your client uses, read `tools/list` from the command line, and only then open the client's own status view and logs. Most failures are one of a few things: a stdio server that writes to stdout, a wrong path or missing environment variable, an unfinished sign-in, a protocol version the server does not support, or a page link pasted where a server URL belongs. This guide gives the exact command, log path or error code for each.

## Where does an MCP connection fail?

An MCP connection fails at one of five points: the client cannot read its configuration, the server process or endpoint cannot be reached, the first request is rejected, the tool list comes back empty or different from what you expected, or a single tool call returns an error. To debug an MCP server, find which of the five it is before changing anything.

The order matters because each layer hides the ones below it. A client that skips a config entry never starts the process, so there is no log to read. A process that starts and then prints a banner to stdout looks, from the client's side, like a server that never answered. A remote endpoint that rejects the protocol version looks like a dead URL unless you read the response body.

So the working rule is: reproduce the failure with the smallest tool that reaches that layer. For configuration, that is the client's own list command. For the process and the handshake, it is MCP Inspector or `curl`. For the tool list, it is Inspector's CLI mode or `npx mcp-tc check`, and for tool calls, Inspector's CLI mode. The sections below go through them in that order.

## How do I test a server with MCP Inspector?

MCP Inspector is the reference tool for testing an MCP server with no AI client in the way. The [Inspector docs](https://modelcontextprotocol.io/docs/tools/inspector) describe one npm package, `@modelcontextprotocol/inspector`, with three clients behind one binary: a web UI (the default), a `--cli` mode for scripts and CI, and a `--tui` terminal UI. It needs Node 22.19.0 or newer and runs through `npx` with no installation.

Pass it exactly what your client would run or connect to:

```bash
# A local stdio server: the same command your client config uses
npx @modelcontextprotocol/inspector node path/to/server/index.js

# A remote server over Streamable HTTP
npx @modelcontextprotocol/inspector --server-url https://mcp.example.com/mcp --transport http
```

The launcher prints a URL that carries a per-launch API token and opens it in your browser. The [web client page](https://modelcontextprotocol.io/docs/2026-07-28/tools/inspector/web) asks you to open that printed URL rather than typing `localhost:6274` from memory, because the Node backend behind the UI can spawn processes on your machine and guards its API with that token. Set `MCP_AUTO_OPEN_ENABLED=false` if you do not want the browser to open.

Once connected, the tab bar tells you something before you click anything: which tabs appear depends on the capabilities the server reported. No Tools tab means the server did not declare `tools`. The three monitoring tabs are where the debugging happens:

- Protocol is the JSON-RPC transcript, with requests paired to responses and spec errors rendered by class.
- Network is the HTTP layer for Streamable HTTP and SSE servers: status codes, request and response headers, bodies.
- Console is the stdio server process's `stderr`, which is where most local servers put their own diagnostics.

Network and Console never appear together, since a server is one or the other. If the Tools tab lists a tool but calling it fails, the Protocol tab shows the exact `-32602` or other error the server returned.

One documented behavior worth knowing before you paste a token: the Inspector stores OAuth tokens, OAuth client secrets and stdio `env:` values in the OS keychain when one is available. On a machine without a keychain (Linux without libsecret, headless or SSH sessions, containers with a mounted volume) they go to `~/.mcp-inspector/secrets.json`, unencrypted unless you supply a key. The [configuration page](https://modelcontextprotocol.io/docs/2026-07-28/tools/inspector/configuration) documents `MCP_INSPECTOR_SECRET_STORE=memory` for keeping them in memory only.

## How do I read a server's tool list from the command line?

The fastest check is the Inspector's CLI mode: one command connects, sends the single request you name with `--method`, prints the result and exits with a code that says why it failed. The [CLI client page](https://modelcontextprotocol.io/docs/2026-07-28/tools/inspector/cli) documents the methods, the output formats and the exit codes.

```bash
# List a stdio server's tools and exit
npx @modelcontextprotocol/inspector --cli node path/to/server/index.js --method tools/list

# Same for a remote server, as JSON, piped to jq
npx @modelcontextprotocol/inspector --cli https://mcp.example.com/mcp --transport http \
  --method tools/list --format json | jq '.result.tools[].name'
```

If your stdio server takes its own flags, put `--` between the server command and the Inspector's options. In CLI mode everything before `--` is the target and everything after it belongs to the Inspector. The exit code tells you which layer failed:

| Exit code | Meaning (CLI docs) |
| --- | --- |
| `0` | Success |
| `1` | Usage or unexpected error |
| `3` | Server requires authentication (HTTP 401 or 403) |
| `4` | Server unreachable (DNS, connection refused, timeout) |
| `5` | Tool error: `tools/call` returned `isError: true`, or the tool was not found |
| `6` | `--strict` found an error-severity tool-schema portability problem |

On any non-zero exit the CLI also writes one JSON line to stderr with a stable `code` such as `auth_required`, `unreachable` or `tool_not_found`, plus the HTTP status and URL when known. In CI, add `--stored-auth-only` so a server that needs sign-in fails at once instead of waiting on a browser callback.

For a server on the 2026-07-28 revision you can go one level lower with plain `curl` and `server/discover`. The [Streamable HTTP transport](https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/streamable-http) requires an `Accept` header listing both `application/json` and `text/event-stream`, an `MCP-Protocol-Version` header and an `Mcp-Method` header on every POST, and the [discovery page](https://modelcontextprotocol.io/specification/2026-07-28/server/discover) shows the request carrying only `_meta`:

```bash
curl -sS https://mcp.example.com/mcp \
  -H 'Accept: application/json, text/event-stream' \
  -H 'Content-Type: application/json' \
  -H 'MCP-Protocol-Version: 2026-07-28' \
  -H 'Mcp-Method: server/discover' \
  -d '{"jsonrpc":"2.0","id":1,"method":"server/discover","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientInfo":{"name":"curl","version":"0"},"io.modelcontextprotocol/clientCapabilities":{}}}}'
```

A modern server answers with `supportedVersions`, `capabilities`, optional `instructions`, and its name and version under `_meta["io.modelcontextprotocol/serverInfo"]`. The spec notes that `serverInfo` is self-reported and not verified, so treat it as a label for logs, not as proof of anything.

A third option reads the server the way mcp.tc does. `npx mcp-tc check https://mcp.example.com/mcp` sends `server/discover` and, for older versions, `initialize`, then reads `serverInfo`, `tools/list` with each tool's `readOnlyHint` and `destructiveHint`, the 401 response and its `WWW-Authenticate` header, the protected resource metadata, and the server card, and it flags keys that appear in the URL. It prints findings marked `ok`, `warn`, `error` or `tip` and exits `6` on errors or `5` when the server cannot be read at all. For a project folder, `--stdio` or `--command "<command>"` starts the server on your computer; the [mcp-tc README](https://github.com/mcptc/mcp-tc) documents these options and the exit codes. The [mcp-tc page](https://mcp.tc/cli) is clear that `check` does not predict whether a server will be listed; it tells you what a reader of your endpoint sees.

## Where are the MCP logs in Claude Code, VS Code, Cursor and Claude Desktop?

Each MCP client keeps its own status view and its own log, and that is where the error text lives when a server shows as failed. The table lists the command or menu from each client's documentation that shows a server's state, and the place its output goes.

| Client | Server status | Logs and failure detail |
| --- | --- | --- |
| Claude Code | `/mcp` in a session, or `claude mcp list` in a shell | `claude mcp get <name>` adds an `Issue:` line on failures; `claude --debug-file /tmp/claude-debug.log` writes a debug log |
| Claude Desktop | The plus icon in the chat input, then the Connectors menu | `~/Library/Logs/Claude` on macOS, `%APPDATA%\Claude\logs` on Windows |
| VS Code | MCP: List Servers in the Command Palette | Show Output opens the Output view on that server's channel |
| Cursor | Customize in the sidebar | Output panel (Cmd+Shift+U), then "MCP Logs" in the dropdown |

The [Claude Code MCP docs](https://code.claude.com/docs/en/mcp) list the status labels `/mcp` shows, including `✔ Connected`, `! Needs authentication`, `✘ Failed to connect` and `⏸ Pending approval`. `claude mcp list` appends failure detail to a failed line, with credential-like text redacted. Two settings matter when a server is slow rather than broken: `MCP_TIMEOUT` is the startup timeout in milliseconds (for example `MCP_TIMEOUT=10000 claude`), and `MCP_TOOL_TIMEOUT` is the tool-call timeout across servers. A stdio server that fails on its first connection is not retried automatically; select Reconnect in `/mcp`.

The [MCP debugging guide](https://modelcontextprotocol.io/docs/2026-07-28/tools/debugging) gives the command for following Claude Desktop's logs on macOS:

```bash
tail -n 20 -F ~/Library/Logs/Claude/mcp*.log
```

It also says that after a server code change you must fully quit and reopen Claude Desktop; closing the window is not enough. In VS Code, the [manage servers page](https://code.visualstudio.com/docs/agents/run/manage-mcp-servers) offers Start Server, Stop Server and Restart Server from the same list, and MCP: Reset Trust clears MCP trust decisions. In Cursor, the [MCP docs](https://cursor.com/docs/context/mcp) say the MCP Logs channel shows initialization, tool calls and error messages, and that config lives in `.cursor/mcp.json` for a project or `~/.cursor/mcp.json` globally.

## What do the most common MCP errors mean?

Most MCP errors can be read straight off the HTTP status and the JSON-RPC error code, because the [2026-07-28 base protocol](https://modelcontextprotocol.io/specification/2026-07-28/basic/index) assigns each protocol-level failure its own code in the `-32020` to `-32099` range, and the transport pairs each with a specific status. The table maps what you see to what to check.

| What you see | What it usually means | What to check |
| --- | --- | --- |
| Client reports a failed connection to a stdio server | The process exited, or wrote something to stdout that is not an MCP message | Run the exact command in a terminal; open the Inspector's Console tab |
| HTTP 401 or 403 | Sign-in required, or a token the server rejected | Finish the OAuth flow: `claude mcp login <name>` or Authenticate in `/mcp`; `codex mcp login` in Codex; `/mcp auth` in Gemini CLI |
| HTTP 400, code `-32022` | The server does not support the protocol version you sent | `data` lists the versions it supports |
| HTTP 400, code `-32602` | A required `_meta` field is missing, or the params are invalid | `io.modelcontextprotocol/protocolVersion` and `clientCapabilities` must be on every request; for `tools/call`, the arguments |
| HTTP 400, code `-32021` | The server needs a client capability the request did not declare | `data.requiredCapabilities` |
| HTTP 400, code `-32020` | An `Mcp-*` header does not match the body, or is missing | `MCP-Protocol-Version`, `Mcp-Method`, `Mcp-Name` |
| HTTP 404 with a `-32601` body | A modern server does not implement that method | The method name |
| HTTP 404 or 405 with no JSON-RPC body | Wrong path, or an older-era flow (GET or DELETE) sent to a 2026-07-28-only endpoint | The URL on the listing; the server's protocol era |
| HTTP 403 on a local server with no auth challenge | The server rejected the `Origin` header | The spec requires 403 for an invalid `Origin` to stop DNS rebinding |
| HTTP 409, code `-32001` | An mcp.tc listing link was pasted as the server URL | Use the server's own URL, which the message names |

Two of these need a note. The Claude Code docs say a server is marked as needing authentication on 401 or 403, but if you set `headers.Authorization` yourself and the server rejects it, that shows as a failed connection rather than an authentication prompt; check the token or remove the header to use OAuth. And the Streamable HTTP page says a client that gets a 400 should inspect the body before falling back to the legacy `initialize` flow, because a modern server also uses 400 for the version, capability and header errors above.

Claude Code also reports configuration problems in words. A JSON entry with a `url` but no `type` is skipped with the message `MCP server "<name>" has a "url" but no "type"; add "type": "http" (or "sse" / "ws") to this entry`. Leading or trailing whitespace in `command`, `url`, `args`, `env` or `headers` is flagged, which the docs attribute to a pasted token with a trailing newline. A `${VAR}` reference with no value and no default produces a missing-variable warning and the literal text is sent.

The last row in the table is specific to mcp.tc. A listing page such as `https://mcp.tc/i/example-server` is a web page, and the [connect docs](https://mcp.tc/docs/connect) explain that an MCP client pointed at it gets HTTP 409 with a JSON-RPC error whose message names the server's own URL, or the install command for a local server. Copy the URL from the box at the top of the listing, or use the client's setup step on that page.

## Why won't a local stdio server start?

A local stdio server fails to start for one of three documented reasons: the client launched it from an undefined working directory, it did not inherit the environment variables it needs, or the runtime it depends on (Node.js for `npx`, uv for `uvx`, Docker for `docker`) is missing on the machine. All three show up as a generic connection failure in the client.

The [debugging guide](https://modelcontextprotocol.io/docs/2026-07-28/tools/debugging) states the first two plainly. The working directory for servers launched via the client's config "may be undefined (like `/` on macOS)", so use absolute paths in the configuration and in any `.env` file. And stdio servers "inherit only a limited subset of environment variables automatically", with the exact set depending on the platform, so pass what the server needs with the `env` key:

```json
{
  "mcpServers": {
    "example-server": {
      "command": "/absolute/path/to/example-server",
      "args": ["--data", "/absolute/path/to/data"],
      "env": {
        "EXAMPLE_API_KEY": "replace-me"
      }
    }
  }
}
```

The third reason is the first check in mcp.tc's [troubleshooting list](https://mcp.tc/docs/connect): run the install command from the listing in a terminal before adding it to a client. If it fails there, the usual cause is a missing runtime, or a placeholder such as `<YOUR_API_KEY>` that was never replaced.

Once the process starts, the only remaining stdio rule is about stdout. The [stdio transport](https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/stdio) says the server "MUST NOT write anything to its stdout that is not a valid MCP message" and "MAY write UTF-8 strings to stderr for any logging purposes". A startup banner, a progress bar or a `console.log` call on stdout corrupts the stream, and the client sees a server that never completed the handshake. In the Inspector, pass `-e KEY=VALUE` to set environment variables and `--cwd <path>` to set the working directory, then read the Console tab, which shows exactly what the process wrote to stderr.

## Legacy or 2026-07-28: which protocol era is the server speaking?

A server speaks one of two eras: the legacy era, where an `initialize` handshake opens a session, or the modern 2026-07-28 era, where every request carries its own `_meta` and there are no sessions. A client that assumes the wrong era gets errors that look like the server is down, so settle the era before reading anything else.

The Inspector treats this as a per-server setting. The [protocol eras page](https://modelcontextprotocol.io/docs/2026-07-28/tools/inspector/protocol-eras) documents three values for `--protocol-era` (or `protocolEra` in a config file): `legacy`, the default, sends a plain `initialize` with no probing; `auto` probes `server/discover` first and falls back to `initialize` on any non-modern outcome; `modern` pins exactly `2026-07-28` with no fallback, "so a non-modern server fails loudly". The docs explain the default: a debugging tool must not auto-probe, because a probe stalls against silent legacy stdio servers and clutters the transcript you came to read. Once connected, the negotiated era appears as a badge on the Protocol tab.

The probe itself has three outcomes, spelled out on the [stdio transport page](https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/stdio). A `DiscoverResult` means the server is modern. A recognized modern error such as `UnsupportedProtocolVersionError` means the server is modern and you should use one of the versions it advertises, without falling back. Any other error, or no answer within a reasonable timeout, means the server is legacy. The spec adds that the fallback must not be keyed to one specific error code, since legacy servers answer an unknown pre-`initialize` request with `-32601`, `-32602`, or nothing at all.

For remote servers, the revision also changed the transport. The Streamable HTTP page lists what 2026-07-28 removed: the GET stream endpoint and protocol-level sessions. A server that supports only this revision answers a GET or DELETE with `405 Method Not Allowed` and ignores an `Mcp-Session-Id` header. If you are moving your own server across that line, the [stateless migration guide](https://mcp.tc/blog/mcp-stateless-migration) walks through `server/discover`, per-request `_meta` and the `Mcp-Method` and `Mcp-Name` headers.

## What you can do next on mcp.tc

A listing on mcp.tc gives you the inputs this guide keeps asking for: the server's own URL or install command, whether it needs sign-in, and setup steps for Claude Code, claude.ai, Claude Desktop, ChatGPT, Cursor, VS Code, Devin Desktop, Codex and Gemini CLI. Search the [directory](https://mcp.tc/directory) by name, or filter to servers that need no sign-in when you want a quick connection test. Every setup step uses the vendor's own URL or package, so the client connects straight to the server and nothing on mcp.tc is in between.

From a terminal, `npx mcp-tc scan` reads the MCP configs of Claude Code, Claude Desktop, Cursor, VS Code, Devin Desktop, Codex and Gemini CLI on your computer and shows which servers are on mcp.tc, sending one address per server and no keys, headers or environment values. `npx mcp-tc add <slug> --client cursor` shows the command or config change for a listing and asks before writing. Both are on the [CLI page](https://mcp.tc/cli), along with `check` for your own server.

If a server connected fine last week and a tool is now missing or renamed, open the listing's History tab. mcp.tc reads the tool list of each listed server whose tools it can read without signing in every 3 hours, and records added and removed tools, changed descriptions, schemas and annotations after two reads agree. On 28 October 2026, 351 of the 1,125 listed servers had a watched tool list, and the history began on 6 October 2026. The newest changes across the directory are at [recent changes](https://mcp.tc/changes), and the [change history docs](https://mcp.tc/docs/changes) explain what counts as a change and what the history cannot show: tools a server lists only after sign-in or only to some clients, and a change undone between two checks. Sign in and press Follow on a listing to get an email when its tools or version change.

If the server you are debugging is public and not listed, [suggest it](https://mcp.tc/submit) with its URL, or run `npx mcp-tc submit <url>`.

## FAQ

### What is MCP Inspector and do I need to install it?

MCP Inspector is the reference developer tool for testing and debugging MCP servers, published as the npm package `@modelcontextprotocol/inspector`. It bundles a web UI, a scriptable CLI (`--cli`) and a terminal UI (`--tui`). You run it with `npx` and no install step; it needs Node 22.19.0 or newer. Pass it the same stdio command or `--server-url` your client uses, and it shows the JSON-RPC transcript, the HTTP layer and the server's stderr.

### Why does my MCP server work in Inspector but not in my client?

The Inspector runs the server from your shell, with your working directory, your environment variables and your `PATH`. A client launches it from an undefined working directory with a limited set of environment variables, which the MCP debugging guide calls out as platform-dependent. Use absolute paths and an `env` block in the client config, confirm the runtime (`npx`, `uvx`, `docker`) is on the client's path, and compare the client's log with the Inspector's Console tab.

### How do I see which protocol versions a server supports?

Send `server/discover`. The 2026-07-28 specification says servers must implement it, and the result lists `supportedVersions` along with capabilities and the server's self-reported name and version. If you ask for a version the server does not support, it answers HTTP 400 with error code `-32022` and the supported versions in the error's `data`. A server that returns any other error, or nothing, is on the legacy era and expects an `initialize` handshake instead.

### What does "Failed to connect" mean in Claude Code?

It means the connection attempt failed for a reason other than authentication, which has its own label; the Claude Code docs describe the detail shown next to it as the HTTP status or error code, plus any error text the server returned. Run `claude mcp get <name>` to read the `Issue:` line, or `claude mcp list` for the failure detail with credentials redacted. A rejected `headers.Authorization` you set yourself also shows as a failed connection. For a stdio server, select Reconnect in `/mcp`, since the first connection is not retried.

### Can mcp.tc tell me why my server fails to connect?

Partly. `npx mcp-tc check <url>` reads your endpoint the way mcp.tc and clients do, reports the handshake, the sign-in challenge, the tool list and the server card, and exits `6` on errors or `5` when the server cannot be read at all. It does not predict whether the server will be listed. For a server already listed, the listing page shows whether its daily health check is failing, and the History tab shows tool changes mcp.tc's reads found.

### Where should a stdio MCP server write its logs?

To stderr, and nowhere else. The stdio transport specification says the server must not write anything to stdout that is not a valid MCP message and may write UTF-8 log lines to stderr. Clients may capture stderr; the Inspector shows it in its Console tab, and the debugging guide says the host application captures it for stdio servers. Logging over the protocol with `notifications/message` is deprecated as of 2026-07-28, and a remote server's stderr is not captured by the client at all.

## Sources

- <https://modelcontextprotocol.io/docs/tools/inspector>
- <https://modelcontextprotocol.io/docs/2026-07-28/tools/inspector/cli>
- <https://modelcontextprotocol.io/docs/2026-07-28/tools/inspector/configuration>
- <https://modelcontextprotocol.io/docs/2026-07-28/tools/inspector/web>
- <https://modelcontextprotocol.io/docs/2026-07-28/tools/inspector/protocol-eras>
- <https://modelcontextprotocol.io/docs/2026-07-28/tools/debugging>
- <https://modelcontextprotocol.io/specification/2026-07-28/basic/index>
- <https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/streamable-http>
- <https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/stdio>
- <https://modelcontextprotocol.io/specification/2026-07-28/server/discover>
- <https://code.claude.com/docs/en/mcp>
- <https://code.visualstudio.com/docs/agents/run/manage-mcp-servers>
- <https://cursor.com/docs/context/mcp>
- <https://github.com/mcptc/mcp-tc>
