MCP-Spy Docs
A complete guide to inspecting, debugging, and understanding everything that happens between your AI assistant and its tools β whether you built those tools or not.
What is MCP-Spy?
The Model Context Protocol (MCP) is the standard that lets AI assistants like Claude call external tools β reading files, querying databases, searching the web, running code, and much more. Every time Claude uses a tool, it sends a structured message behind the scenes. MCP-Spy lets you see those messages.
Think of it like browser DevTools, but for AI tool calls. You see the exact request the AI made, the exact response it got back, how long it took, and whether it succeeded or failed. No guessing, no black boxes.
Why does this matter?
When an AI tool call fails or gives wrong results, it is nearly impossible to diagnose without seeing the raw data. Was the AI's request malformed? Did the tool return bad data? Did it time out? MCP-Spy answers all of these questions instantly.
Who is this for?
MCP-Spy is useful in three different situations, even if you have never written a line of code in your life.
AI Power Users
You use Claude Desktop or Cursor with MCP servers like the filesystem MCP, GitHub MCP, or Slack MCP β but you don't write code. MCP-Spy lets you see exactly what your AI assistant is doing with those tools in real time.
MCP Developers
You are building an MCP server and need to inspect payloads, reproduce bugs, test edge cases, and iterate fast. MCP-Spy is your primary debugging tool during development.
Teams & Auditors
You need a log of every AI tool call for compliance, security review, or team debugging. Cloud Sync gives you a persistent, shareable record of all MCP activity.
How it works
MCP-Spy is a transparent proxy. It sits between your AI client (Claude, Cursor, etc.) and the MCP server, intercepting every message in both directions without changing anything about how either side works.
You do not need to modify the MCP server. You do not need to change the AI client. You just insert MCP-Spy in the middle by telling your AI client to talk to MCP-Spy's port instead of the server's port directly.
AI Client
Claude, Cursor, Windsurf, or any MCP-compatible app sends a tool call.
MCP-Spy
Intercepts the message, logs it, then forwards it unchanged to the real server.
MCP Server
Any MCP server β filesystem, GitHub, Slack, or one you built yourself. It never knows MCP-Spy is there.
MCP-Spy is invisible to both sides. It only observes.
Quick Start
No installation needed. Run MCP-Spy with npx directly:
npx mcp-spy --target 3000This starts MCP-Spy on port 4000 (its default listen port) and forwards traffic to port 3000 where your MCP server is running. Then tell your AI client to connect to http://localhost:4000 instead.
- 1Your MCP server is already running on some port (e.g.
3000). - 2Run
npx mcp-spy --target 3000in your terminal. MCP-Spy starts listening on4000. - 3Update your AI client config to point to
http://localhost:4000instead of3000. - 4The TUI opens in your terminal. Every tool call appears in real time β request, response, duration, status code.
Using with any MCP server
You do not need to build an MCP server to use MCP-Spy. It works with any existing MCP server β including all the popular ones that the community and Anthropic provide out of the box.
Common examples include the filesystem MCP (lets Claude read and write your local files), the GitHub MCP (lets Claude interact with your repos), the Slack MCP (lets Claude read and send messages), and many others.
The core idea
Instead of telling Claude Desktop to run npx @modelcontextprotocol/server-filesystem /Users/youdirectly, you tell it to run MCP-Spy, and MCP-Spy wraps that command for you. You don't change the MCP server at all β you just add MCP-Spy in front of it.
Example: Wrapping the Filesystem MCP
Normally your claude_desktop_config.json might look like this:
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/you/Documents"]
}
}
}To add MCP-Spy, you wrap that command with mcp-spy --target, but since this MCP uses stdio (not HTTP), you use the --wrap mode by passing the original command as the target:
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": [
"mcp-spy",
"--target", "3000",
"--",
"npx", "-y", "@modelcontextprotocol/server-filesystem", "/Users/you/Documents"
]
}
}
}MCP-Spy starts the real MCP server as a child process and intercepts all stdio communication. Nothing changes for Claude β it still gets the same responses, but now you can see every call.
Other popular MCP servers you can wrap
Any MCP server that follows the standard protocol can be wrapped with MCP-Spy β these are just the most common ones.
Debugging common errors
MCP failures can be confusing because the AI assistant usually gives a vague error message. Here is how to use MCP-Spy to diagnose the most common problems.
Open MCP-Spy TUI and look at the status code column for the failed call.
- 500 β The server crashed. Check the response payload for a stack trace.
- 404 β Claude called a tool that doesn't exist (wrong tool name or version mismatch).
- 401 / 403 β Missing or expired API key in the server's environment.
- No entry at all β MCP-Spy never received the request. Your client config is still pointing to the old port.
Click on the log entry in the TUI to expand the full request and response payloads. Check:
- Did Claude send the right arguments? (Check Request Payload)
- What did the server actually return? (Check Response Payload)
- Is the data truncated, malformed, or missing fields the AI expected?
With Cloud Pro you can use Edit & Replay to modify the request and resend it to isolate exactly what causes the problem.
The duration column in the TUI shows how long each call takes. If specific tool calls are slow (e.g. >2 seconds), inspect the response to see if the server is returning large payloads that could be paginated or filtered.
Building your own MCP server
If you are developing an MCP server, MCP-Spy is even more valuable. You get live feedback on every tool call during development without adding any logging code to your server.
The setup is the same β run your server, start MCP-Spy pointing at it, and connect your AI client to MCP-Spy's port. As you make changes to your server, MCP-Spy keeps logging. No restarts needed.
The development loop
- Start your MCP server (e.g.
node server.json port 3000) - Start MCP-Spy (
npx mcp-spy --target 3000) - Ask Claude to call your tool
- See the exact JSON-RPC in the TUI
- Fix the bug, restart your server, repeat
Language-specific examples are below in the Server Examples section.
Claude Desktop
Claude Desktop reads its MCP server config from claude_desktop_config.json. The file lives at:
- macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
- Windows: %APPDATA%\Claude\claude_desktop_config.json
To spy on any MCP server in Claude Desktop, change its command and args to go through MCP-Spy. Here is a complete example wrapping an HTTP-based custom server:
{
"mcpServers": {
"my-server": {
"command": "npx",
"args": ["mcp-spy", "--target", "3000"],
"env": {}
}
}
}Claude Desktop launches this command when it starts. MCP-Spy listens on port 4000, forwards to your server on 3000, and logs everything. Restart Claude Desktop after editing the config file.
Cursor
Go to Cursor Settings β Features β MCP and add a new server entry. Set the command to launch MCP-Spy instead of your server directly.
{
"mcpServers": {
"my-server": {
"command": "npx",
"args": ["mcp-spy", "--target", "3000"]
}
}
}Cursor will launch MCP-Spy on startup and route all tool calls through it.
Windsurf
Windsurf (by Codeium) also supports MCP servers. Edit the ~/.codeium/windsurf/mcp_config.json file and add MCP-Spy as a wrapper the same way as Claude Desktop.
{
"mcpServers": {
"my-server": {
"command": "npx",
"args": ["mcp-spy", "--target", "3000"]
}
}
}Other MCP clients
Any MCP-compatible client works with MCP-Spy as long as you can configure it to use a custom server address. The pattern is always the same:
- Find where the client defines its MCP server (usually a JSON config file)
- Replace the direct server command or URL with MCP-Spy's address (
http://localhost:4000) - Make sure MCP-Spy is running with
--targetpointing at the original server
Compatible clients include: Claude Desktop, Cursor, Windsurf, VS Code (Continue extension), Zed, and any custom application using the official MCP SDK.
Cloud Sync Dashboard
By default, MCP-Spy stores logs locally in a SQLite database on your machine. With Cloud Sync (Pro), every intercepted call is also uploaded to your personal dashboard at mcpspy.dev in real time.
Persistent history
All your logs are saved in the cloud. Close your terminal and come back later β everything is still there.
Share with your team
Generate a public link for any trace. Share it with a teammate or paste it in a GitHub issue.
Edit & Replay
Modify any request payload in the browser and resend it to your local MCP server instantly.
Visual inspector
Syntax-highlighted JSON diff view, filter by status, method, or duration β all in the browser.
Activate in CLI:
npx mcp-spy --target 3000 --sync mcp_live_XXXXXXXXGet your API key from the dashboard after subscribing at mcpspy.dev/pricing.
Edit & Replay Pro
Edit & Replay lets you take any logged request, modify its JSON payload in the browser, and resend it to your locally running MCP server β without triggering Claude again.
This is useful for:
- Testing how your server handles edge cases (empty strings, large numbers, missing fields)
- Reproducing a specific failure with slightly different inputs
- Confirming that a bug is in the request (Claude's side) vs. the response (server side)
How to use it
- Open the dashboard (Pro required)
- Click any log entry to select it
- Click Edit & Replay β a modal opens with the original JSON request
- Modify the payload as needed
- Click Send β the response appears immediately below
Requires the CLI to be running locally (npx mcp-spy --target ...) since Edit & Replay sends the request through your machine's local proxy.
Auto-Redaction
ProAdd --redact-pii to automatically mask secrets from all payloads before they are saved locally or synced to the cloud. Nothing sensitive ever touches the database.
npx mcp-spy --target 3000 --redact-pii --sync YOUR_KEY
The following patterns are automatically redacted:
Redacted logs are tagged with a π badge in both the TUI and dashboard. The original bytes are forwarded to the MCP server unmodified β redaction only affects storage.
Token Profiling
ProEvery intercepted request and response is automatically profiled for token count. This tells you how much of an LLM's context window each tool call consumes β and gives you a rough cost estimate.
Token counts appear as ~1.2k badges on every log row in the TUI and dashboard. The TUI stats bar shows the session total. No extra flags needed β it's always on.
Estimation method
Uses a blended heuristic (word count Γ 1.3 vs. char count Γ· 4, take the higher). Accurate to Β±15% for JSON/code payloads β adequate for spotting expensive calls without any heavy dependencies.
Export (cURL / Postman)
ProTurn any saved log into a runnable shell command or a Postman collection with one click.
Copy as cURL
In the dashboard: select a log β Copy cURL button.
In the TUI: select a log β press c.
curl -s -X POST http://localhost:4000 \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","method":"tools/call",...}'Postman Collection
In the dashboard: click the Postman button in the top-right. Downloads a .json file you can import directly into Postman.
The collection includes all currently visible logs as individual requests, respecting any active server filter.
Auto-Mock Mode
ProAdd --mock to make MCP-Spy intercept all requests and return saved responses from your local database β without forwarding anything to the real server.
npx mcp-spy --target 3000 --mock
For each incoming method (e.g. tools/call), MCP-Spy looks up the most recent successful response for that method in SQLite and returns it immediately. If no saved response exists for a method, it returns a 404 JSON-RPC error.
When is this useful?
- Testing AI agent behaviour without hitting real APIs or incurring costs
- Reproducing a specific scenario deterministically
- Running the agent while the real MCP server is offline
- Frontend development where you don't need live data
CI/CD Test Runner
ProUse the test subcommand to replay saved requests against your MCP server and assert valid JSON-RPC responses. Exits 0 on all pass, 1 on any failure β works natively in GitHub Actions, GitLab CI, and any shell pipeline.
# Replay last 10 saved requests against the server on port 3000 npx mcp-spy test --target 3000 # Only replay a specific method npx mcp-spy test --target 3000 --method tools/call # Only replay from a specific server label npx mcp-spy test --target 3000 --name filesystem --count 20
name: MCP Server Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Start MCP server
run: node server.js &
- name: Run MCP-Spy tests
run: npx mcp-spy test --target 3000 --count 20| Flag | Description | Default |
|---|---|---|
| --target | Port of the MCP server to test | required |
| --method | Filter to only replay this JSON-RPC method | all |
| --name | Filter to only replay from this server label | all |
| --count | Max requests to replay | 10 |
| --timeout | Per-request timeout in ms | 5000 |
TUI β Invoking & Keyboard Shortcuts
How to launch the TUI
The Terminal UI (TUI) is a full-screen interface that shows a live list of intercepted MCP calls on the left and the selected payload on the right. It opens automatically whenever you start the proxy with a --target port.
# Basic β proxy + TUI in one command
npx mcp-spy -t 3001
# With a label and cloud sync (Pro)
npx mcp-spy -t 3001 --name filesystem --sync mcp_live_XXXX...
# Disable TUI, keep plain console output (useful in CI)
npx mcp-spy -t 3001 --no-tuiStandalone welcome & guided setup
Running npx mcp-spy without --target opens an interactive welcome screen instead of the proxy. You will see:
- Subscription prompt β a summary of what Pro unlocks and the price. Press Y to open
mcpspy.dev/pricingin your browser, or N to skip. - Guided setup wizard β a 4-step walkthrough that shows you how to connect the official
@modelcontextprotocol/server-filesystemserver to MCP-SPY and your MCP client. Navigate with β / N and go back with β / P.
Quick demo with the official filesystem server:
# Terminal 1 β start the MCP server on port 3001
npx -y @modelcontextprotocol/server-filesystem \
--transport sse --port 3001 ~/Documents
# Terminal 2 β start MCP-SPY proxy (TUI opens automatically)
npx mcp-spy -t 3001 --name filesystem
# Claude Desktop config β point at the PROXY (4000), not the server (3001)
# Edit: ~/Library/Application Support/Claude/claude_desktop_config.json
{
"mcpServers": {
"filesystem": {
"url": "http://localhost:4000"
}
}
}
# Use "url" β not "command"/"args" β otherwise Claude Desktop
# spawns the server directly and bypasses the proxy entirely.Keyboard shortcuts
These shortcuts work once the TUI is running with an active proxy.
| Key | Action |
|---|---|
| β / β | Navigate between log entries |
| s | Cycle server filter β shows all, then each server label in turn |
| c | Toggle cURL export view for the selected log |
| q | Quit MCP-Spy |
| Ctrl+C | Force quit |
If you prefer plain terminal output without the TUI (useful in CI pipelines), add the --no-tui flag when starting MCP-Spy.
Node.js / TypeScript
MCP-Spy works with any MCP server. You do not need to change your server code at all. If you are building a Node.js MCP server using the official @modelcontextprotocol/sdk:
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
const server = new Server({ name: "my-tool", version: "1.0.0" });
// Your tool definitions...
const transport = new StdioServerTransport();
await server.connect(transport);Run your server: node server.js on port 3000. Then start MCP-Spy: npx mcp-spy --target 3000.
Python
Using the official mcp Python package. MCP-Spy intercepts the stdio stream transparently.
import asyncio
from mcp.server.stdio import stdio_server
from mcp.server import Server
app = Server("my-tool")
@app.tool()
async def get_weather(location: str):
"""Returns current weather for a location."""
return f"It is 72Β°F and sunny in {location}"
async def main():
async with stdio_server() as (read_stream, write_stream):
await app.run(
read_stream,
write_stream,
app.create_initialization_options()
)
if __name__ == "__main__":
asyncio.run(main())Go
Using the mark3labs/mcp-go package. Compile your server, run it on a port, and point MCP-Spy at it.
package main
import (
"context"
"fmt"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
func main() {
s := server.NewMCPServer("my-tool", "1.0.0",
server.WithToolCapabilities(true),
)
tool := mcp.NewTool("get_weather",
mcp.WithDescription("Get weather for a location"),
mcp.WithString("location", mcp.Required()),
)
s.AddTool(tool, func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
loc := req.Params.Arguments["location"].(string)
return mcp.NewToolResultText(fmt.Sprintf("Sunny in %s", loc)), nil
})
if err := server.ServeStdio(s); err != nil {
panic(err)
}
}Rust
Using the mcp-rs crate. Compile your binary and point MCP-Spy at the running process.
use mcp_rs::server::Server;
use mcp_rs::transport::stdio::StdioTransport;
use tokio;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let transport = StdioTransport::new();
let mut server = Server::new(transport);
// Add tools here...
server.listen().await?;
Ok(())
}CLI Reference
| Flag | Description | Default |
|---|---|---|
| --target <port> | Required. The local port where your actual MCP server is running. | β |
| --name <label> | Human-readable label for this server. Shows in TUI and dashboard for multi-MCP setups. E.g. --name filesystem. | port-{n} |
| --sync <token> | Your Pro API key. Uploads logs to your cloud dashboard in real time. | β |
| --redact-pii | Auto-redact secrets (AWS keys, tokens, emails, passwords) from payloads before saving or syncing. Proxy traffic is unaffected. | off |
| --mock | Mock mode β return saved responses from the local database instead of forwarding to the real server. | off |
| --no-tui | Disables the interactive TUI. Useful in CI, scripts, or piped output. | off |
mcp-spy test --target <port> β see the CI/CD Test Runner section for full options.Running multiple MCP servers simultaneously
Use --name to label each instance. Run each in a separate terminal:
# Terminal 1 β filesystem MCP npx mcp-spy --target 3000 --name filesystem --sync YOUR_KEY # Terminal 2 β GitHub MCP npx mcp-spy --target 3001 --name github --sync YOUR_KEY
In the dashboard, each log shows its server label. Click a label to filter the list to that server only.
Where your data is stored
Local (free tier)
Every intercepted request is saved to a SQLite database on your machine immediately β no network needed.
~/.mcp-spy/mcp_logs.db- β Persists across terminal restarts
- β No size limit beyond disk space
- β Read by the TUI in real time
- β Lost if you wipe your machine
Cloud (Pro β --sync)
Each log is also sent to your personal Convex database in real time. Available from any device via the dashboard.
mcpspy.dev/dashboard- β Survives machine wipe
- β Accessible from browser anywhere
- β Shareable trace links
- β Real-time live updates