Test and debug an MCP server with MCP Inspector
Debug an MCP server before you connect it to ChatGPT: MCP Inspector web UI and CLI, tunnels, logs, schema and annotation checks, and OAuth debugging.
When an MCP server misbehaves in ChatGPT, the chat window tells you very little. Test it outside ChatGPT first: confirm it initializes, lists valid tools, returns the right results and handles auth. Then add a tunnel and ChatGPT, one layer at a time, so you know which layer broke.
The main tool for this is MCP Inspector, the official MCP debugging client. It comes in one package with a web UI, a CLI and a terminal UI.
Before you start#
- Node.js 22.19 or newer for the current Inspector (v2). The older v1 line is still available as
npx @modelcontextprotocol/inspector@v1-latest, but its CLI flags differ from the examples here. - Your server running locally, for example on
http://localhost:8787/mcp. If you don't have one, follow Build your first ChatGPT app. jq, if you want to script assertions.
Tip: To check that your client setup works before blaming your server, point the Inspector at a known-good server. The reference "Everything" server implements every MCP feature, and DeepWiki is a public remote server with no auth (
https://mcp.deepwiki.com/mcp).
1. Inspect it in the web UI#
npx @modelcontextprotocol/inspector@latestThis opens the Inspector in your browser. Choose Streamable HTTP, enter your server URL, and connect. Then work through this list:
- Initialization succeeds. Note the protocol version and capabilities the server reports.
- Server instructions read well. ChatGPT and Codex use the MCP
instructionsfield as server-wide guidance, and Codex recommends keeping the first 512 characters self-contained. - The tool list is what you expect, with no leftovers from earlier experiments.
- Every tool works with normal inputs, edge cases, missing IDs and empty results.
- Errors come back as readable messages, not stack traces.
- Auth is enforced for private data and write actions.
For a stdio server, the Inspector can start it for you. Pass the command after the package name:
npx @modelcontextprotocol/inspector node build/index.jsNote: The Inspector stores OAuth tokens, client secrets and stdio environment values in your OS keychain when one is available, and otherwise in
~/.mcp-inspector/secrets.json. Keep that in mind on shared machines.
2. Script checks with the Inspector CLI#
The CLI runs one MCP call per command and exits with a status code, which makes it good for repeatable checks and CI.
# Connect-only probe: handshake, print server info, disconnect
npx @modelcontextprotocol/inspector --cli --transport http \
--server-url http://localhost:8787/mcp --method initialize
# List tools as JSON
npx @modelcontextprotocol/inspector --cli --transport http \
--server-url http://localhost:8787/mcp --method tools/list --format json
# Call one tool with typed arguments
npx @modelcontextprotocol/inspector --cli --transport http \
--server-url http://localhost:8787/mcp \
--method tools/call --tool-name roll_dice \
--tool-args-json '{"count":2,"sides":6}' --format jsonPrefer --tool-args-json over --tool-arg key=value. The key=value form parses values as JSON when it can, so an ID like 10001 arrives as a number, and a schema expecting a string will reject it.
Turn checks into assertions with jq -e, which fails when the result is null or false:
npx @modelcontextprotocol/inspector --cli --transport http \
--server-url http://localhost:8787/mcp --method tools/list --format json \
| jq -e '.result.tools | map(.name) | index("roll_dice")' > /dev/nullTwo more flags worth knowing:
--app-inforeports whether a tool has an MCP Apps UI and whichui://resource it points to, without calling the tool. It exits2when there is no UI.--strictontools/listaddsschemaFindingsfor schema portability problems.
In CI, pin an exact Inspector version (npx --yes @modelcontextprotocol/inspector@<version> --cli ...) so results don't change under you.
3. Check tool schemas and annotations#
Most "the model won't call my tool" problems are metadata problems. For each tool, check:
- Name: specific and verb-first (
get_order_status), unique within the server. - Description: says what it does, when to use it ("Use this when…"), and its limits.
- Input schema: every parameter has a type and a description; use enums for fixed choices; mark required fields. Ask only for what the tool needs.
- Output schema: if you declare
outputSchema,structuredContentmust match it on every call, including error cases. - Annotations: set
readOnlyHint,destructiveHintandopenWorldHintexplicitly. ChatGPT treats a tool with noreadOnlyHintas a write and asks for confirmation every time. OpenAI's plugin review rejects annotations that don't match behavior. - UI link: for app tools,
_meta.ui.resourceUripoints at a resource that exists, served with MIME typetext/html;profile=mcp-app.
4. Watch the raw traffic and logs#
When the Inspector and your expectations disagree, go one level lower. A Streamable HTTP server is just HTTP, so you can call it with curl:
curl -s -X POST http://localhost:8787/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}'A healthy server returns a JSON-RPC result with serverInfo and capabilities. A 406 Not Acceptable from a server built on the official TypeScript SDK means the Accept header doesn't list both application/json and text/event-stream.
On the server side:
- Log every incoming method name, tool name and duration, plus the full error for failures.
- For stdio servers, never write logs to stdout. Stdout carries the protocol; a stray
console.logcorrupts it. Log to stderr instead. - Don't log access tokens or sensitive tool results.
5. Test through a tunnel, then ChatGPT#
Once it works locally, expose it and repeat the same checks against the public URL:
ngrok http 8787
# or
cloudflared tunnel --url http://localhost:8787npx @modelcontextprotocol/inspector --cli --transport http \
--server-url https://abc123.ngrok.app/mcp --method tools/list --format jsonIf it passes here but fails in ChatGPT, the problem is in the ChatGPT setup (URL, auth mode, stale metadata), not the server. Connect it with developer mode, and remember to select Refresh on the connection after every server change.
For raw request and response logs from OpenAI's side, use the API Playground: Tools > Add > MCP Server, enter the HTTPS endpoint, and run test prompts.
6. Debug OAuth#
ChatGPT follows the MCP authorization spec. Check each link in the chain:
Protected resource metadata. Your server publishes it at
/.well-known/oauth-protected-resource, or points to it in aWWW-Authenticateheader on401responses:bashcurl -i https://mcp.example.com/.well-known/oauth-protected-resource curl -i -X POST https://mcp.example.com/mcp -H 'Content-Type: application/json' -d '{}'The second call should return
401withWWW-Authenticate: Bearer resource_metadata="...".Authorization server metadata is reachable at
/.well-known/oauth-authorization-serveror/.well-known/openid-configurationon your identity provider.Client registration works: CIMD (
client_id_metadata_document_supported: true), DCR, or static credentials entered in ChatGPT.Redirect URI matches exactly. Copy it from the connection's page in ChatGPT; it is either
https://chatgpt.com/connector/oauth/{callback_id}or the stablehttps://chatgpt.com/connector_platform_oauth_redirect, depending on whether your authorization server supports issuer identification.Tokens are checked for issuer, audience, expiry and scopes. Reject bad tokens with a
401and theWWW-Authenticatechallenge, so the client re-authorizes.
To test with the Inspector, register its callback URL with your provider (http://localhost:6274/oauth/callback for the web UI, http://127.0.0.1:6276/oauth/callback for the CLI). Then:
# skip OAuth and pass a token you already have
npx @modelcontextprotocol/inspector --cli --transport http \
--server-url https://mcp.example.com/mcp \
--header "Authorization: Bearer $TOKEN" --method tools/list
# throw away stored tokens and log in again
npx @modelcontextprotocol/inspector --cli --transport http \
--server-url https://mcp.example.com/mcp --relogin --method tools/listCommon protocol mistakes#
- The URL in ChatGPT is missing the
/mcppath. - OAuth discovery routes return an HTML error page or
500on a no-auth server. Return404. - CORS preflight (
OPTIONS) isn't handled, orMcp-Session-Idisn't exposed. - A stateful server loses sessions behind a load balancer. Use stateless mode or sticky sessions.
- Tool results include only
contentwhen the widget needsstructuredContent. - Tool results leak internal IDs, timestamps or debug payloads.
- Tool names or schemas change between deploys, breaking chats that already loaded the old list.
Next steps#
- Connect the working server: How to add a custom MCP server to ChatGPT.
- Prepare it for review: Publish a ChatGPT plugin.
- Compare your server with others in the MCP directory.