Guide · Debugging

Debugging MCP servers in VS Code

VS Code's built-in debugger works with MCP servers but requires a specific configuration because the server runs as a child process of the MCP client (Claude Desktop, Cursor, or the MCP Inspector) rather than being launched directly from VS Code. This guide covers the launch.json configuration for both the "launch" pattern (VS Code starts the server) and the "attach" pattern (VS Code attaches to an already-running server), source maps for TypeScript, and how to use VS Code's MCP Output panel for protocol-level inspection.

TL;DR

Use the launch pattern with the MCP Inspector as the client: configure VS Code to start both the Inspector and your server with --inspect-brk. Use the attach pattern when the server is already running under Claude Desktop — start the server with --inspect=9229 in the env block, then attach from VS Code. TypeScript source maps work out of the box with ts-node; for compiled output, set "outFiles" in launch.json.

Pattern 1: Launch with the MCP Inspector

The cleanest debugging workflow is to use the MCP Inspector as the client and have VS Code launch the server with the Node debugger enabled. This gives you full control — you can set breakpoints before the first tool call and step through initialization:

// .vscode/launch.json
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Debug MCP Server (via Inspector)",
      "type": "node",
      "request": "launch",
      "program": "${workspaceFolder}/dist/index.js",
      "runtimeArgs": ["--inspect-brk"],
      "env": {
        "DATABASE_URL": "postgres://localhost/mydb",
        "NODE_ENV": "development",
        "DEBUG": "mcp:*"
      },
      "outFiles": ["${workspaceFolder}/dist/**/*.js"],
      "sourceMaps": true,
      "console": "integratedTerminal",
      // Pipe stdin from the Inspector; stdout to Inspector
      // Run Inspector separately: npx @modelcontextprotocol/inspector node dist/index.js
      "preLaunchTask": "build"
    }
  ]
}

With --inspect-brk, the server pauses at startup until a debugger attaches. VS Code attaches automatically when you hit F5. Then in a separate terminal, run the MCP Inspector to connect as the client: npx @modelcontextprotocol/inspector node dist/index.js. Set breakpoints in your tool handlers and use the Inspector's playground to trigger tool calls — VS Code breaks on the breakpoint in your handler code.

Note: use --inspect-brk (break at start) during initialization debugging. Use --inspect (don't break at start) when you only want to break at a specific handler breakpoint.

TypeScript source maps

VS Code needs to map between compiled JavaScript (in dist/) and your TypeScript source (in src/) to show breakpoints on the right lines. Two approaches:

With ts-node (no build step)

{
  "name": "Debug MCP Server (ts-node)",
  "type": "node",
  "request": "launch",
  "runtimeExecutable": "node",
  "runtimeArgs": [
    "--loader", "ts-node/esm",
    "--inspect-brk"
  ],
  "program": "${workspaceFolder}/src/index.ts",
  "env": {
    "TS_NODE_PROJECT": "${workspaceFolder}/tsconfig.json",
    "DATABASE_URL": "postgres://localhost/mydb"
  },
  "sourceMaps": true,
  "console": "integratedTerminal"
}

ts-node transpiles TypeScript on the fly and generates inline source maps, so VS Code can show breakpoints in the .ts source directly. No build step needed — faster iteration during debugging.

With compiled output (tsc)

{
  "name": "Debug MCP Server (compiled)",
  "type": "node",
  "request": "launch",
  "program": "${workspaceFolder}/dist/index.js",
  "runtimeArgs": ["--inspect-brk"],
  "outFiles": ["${workspaceFolder}/dist/**/*.js"],
  "sourceMaps": true,
  "env": { "DATABASE_URL": "postgres://localhost/mydb" },
  "preLaunchTask": "tsc: build - tsconfig.json"
}

Ensure your tsconfig.json has "sourceMap": true (not inlineSourceMap) and "outDir": "./dist". VS Code finds the .map files in dist/ and maps line numbers back to your TypeScript source.

Pattern 2: Attach to a running server

If the server is already running under Claude Desktop and you want to debug a live issue, use the attach pattern. Add the --inspect flag to your Claude Desktop config and then attach VS Code:

// claude_desktop_config.json — add --inspect to the server's node args
{
  "mcpServers": {
    "my-server": {
      "command": "node",
      "args": ["--inspect=9229", "/absolute/path/to/dist/index.js"],
      "env": {
        "DATABASE_URL": "postgres://localhost/mydb"
      }
    }
  }
}
// .vscode/launch.json — attach to the running process
{
  "name": "Attach to MCP Server",
  "type": "node",
  "request": "attach",
  "port": 9229,
  "localRoot": "${workspaceFolder}",
  "remoteRoot": "${workspaceFolder}",
  "outFiles": ["${workspaceFolder}/dist/**/*.js"],
  "sourceMaps": true,
  "restart": true  // re-attach if server restarts
}

Restart Claude Desktop after changing claude_desktop_config.json. The server starts with the debugger listening on port 9229. Hit F5 in VS Code to attach — you'll see "Debugger attached" in the VS Code debug console. Now set a breakpoint in a tool handler and ask Claude to call that tool — VS Code breaks on the breakpoint with the full call stack.

Setting effective breakpoints

MCP tool handlers are async functions called from the MCP SDK's request dispatcher. Breakpoints work the same as in any async Node.js code, but a few patterns are worth knowing:

Using the VS Code MCP Output panel

VS Code (with the MCP extension or as a built-in MCP client) shows raw protocol messages in an Output panel. To access it: open the Command Palette (Cmd+Shift+P), type "MCP", and look for "MCP: Show Output" or check the Output dropdown for an "MCP" channel.

The MCP Output panel shows:

The Output panel shows client-side events — what VS Code sent and received. Your server's stderr output appears in the server process's terminal or in the VS Code launch console, not in the MCP Output panel.

Common VS Code MCP debugging issues

SymptomCauseFix
Breakpoints are grayed out ("Unverified")VS Code can't find the source map for that fileCheck outFiles glob matches your compiled output; verify sourceMap: true in tsconfig.json; rebuild
Breakpoint hits but wrong line numberStale source map (source changed since last build)Rebuild (npm run build) before debugging; use ts-node to avoid the build step
Debugger never attachesPort 9229 already in use by another Node processChange port: --inspect=9230 and update attach config; or kill $(lsof -ti:9229)
Variables panel shows compiled JS variable names (e.g., a, b)Minification — tsconfig has "minify": true or bundler is stripping namesDisable minification in development builds; use ts-node instead of compiled output for debugging
Tool call doesn't trigger breakpointHandler is registered in a different module than where the breakpoint is setCheck that the breakpoint file path matches the actual handler file; MCP SDK may wrap handlers — break one level deeper

Related pages

FAQ

Can I debug an MCP server and a Claude Desktop session simultaneously?

Yes — use the attach pattern. Add --inspect=9229 to the server's args in claude_desktop_config.json, restart Claude Desktop, then attach VS Code with the attach launch configuration. Claude Desktop and the VS Code debugger both connect to the same server process. When you pause at a breakpoint, Claude Desktop's tool call will be suspended — Claude will wait for the response. Don't leave breakpoints paused too long or Claude will time out the request.

Why does --inspect-brk break before my TypeScript code runs?

--inspect-brk breaks at the very first line of JavaScript execution — which is the Node.js module loader, not your TypeScript source. VS Code shows this as a break in an internal file. Hit "Continue" (F5) once to skip past the Node internals; the next break will be the first line of your compiled entry point. If you're using ts-node, the first meaningful break is in src/index.ts. Set a breakpoint in your server setup code to catch it at the right place.

My TypeScript breakpoints work in tests but not in the live MCP server. Why?

Tests typically use the InMemoryTransport which runs in the same process as the test runner — VS Code debugs that single process. The live MCP server is a separate process (child of Claude Desktop or Inspector). You need to debug the child process separately using either the launch or attach configuration. Make sure the server process has --inspect or --inspect-brk in its Node.js args — without it, no debugger can connect.

How do I debug an MCP server that connects to a remote database?

The debug configuration doesn't change — you're debugging the Node.js process locally. The remote database connection happens from your local server as usual. Set the DATABASE_URL environment variable in the launch.json env block (or inherit it from the system environment with "envFile": "${workspaceFolder}/.env"). If the database requires SSH tunneling, set up the tunnel separately before launching the debugger — the Node process connects through the tunnel as it would in any other context.

How does debugging relate to AliveMCP monitoring?

Debugging in VS Code is a development activity — you run it manually when diagnosing a specific problem. AliveMCP is a production activity — it probes your deployed server's MCP protocol every few minutes to detect outages automatically. The two are complementary: use VS Code to find and fix a bug, then verify in the MCP Inspector that the fix works, then deploy and confirm AliveMCP's next probe shows the server as healthy. If AliveMCP alerts on a production outage, the stack trace and argument logs from your structured debug logging point you to the breakpoint to set.