mcp-explorer
mcp-explorer
A controllable, observable MCP server. It speaks the Model Context Protocol to real MCP clients (Claude, MCP Inspector, your own client), but its behavior is driven entirely at runtime by you over a REST API + SSE stream:
Dynamically set the tools exposed to a given MCP client — per session, with a global default. Changing a session's tools pushes
tools/list_changedso the client re-fetches.Observe tool calls in real time and set the response before it returns — either from a preset (canned response, optional simulated latency/errors) or a live human-in-the-loop reply while the call blocks.
Useful as a mock / MITM harness for testing how MCP clients behave against arbitrary tool sets and arbitrary responses, without writing a bespoke server.
Architecture
┌──────────────────────── FastAPI app ────────────────────────┐
MCP client ──┤ /mcp Streamable HTTP (stateful) → low-level Server │
(Inspector) │ /api/* REST control plane → SessionRegistry │
│ /api/events SSE event stream → EventBus │
└──────────────────────────────────────────────────────────────┘
controller ──(REST: set tools, respond)──┘ controller ──(SSE: watch)──┘Sessions are distinguished by the
Mcp-Session-Idthe server assigns oninitialize(stateful Streamable HTTP). All state is in-memory in one process.Built on the MCP Python SDK v2 low-level
Server(arbitrary JSON-schema tools, per-session tool lists) — seesrc/mcp_explorer/mcp_server.py.
Run
uv run mcp-explorer # serves on http://127.0.0.1:8000Config via env vars: MCP_EXPLORER_HOST, MCP_EXPLORER_PORT,
MCP_EXPLORER_TIMEOUT_S (live-response timeout, default 300s),
MCP_EXPLORER_MODE (manual | auto), MCP_EXPLORER_API_TOKEN (optional
bearer token guarding /api/*).
MCP endpoint:
http://127.0.0.1:8000/mcpREST + SSE:
http://127.0.0.1:8000/api/...(OpenAPI docs at/docs)Web dashboard:
http://127.0.0.1:8000/live
Web dashboard
Open http://127.0.0.1:8000/live for a dependency-free (vanilla HTML/CSS/JS)
dashboard that uses the REST + SSE APIs: watch the live event stream, see
connected sessions (each gets a friendly auto-assigned adjective-animal name,
which you can override), edit the tool set (global or per-session), toggle a
session's mode, set presets, and answer blocked tool calls inline.
The UI is fully stream-driven — it does not poll. It fetches state once on
load, then updates reactively from the SSE stream: pending-call counts come
straight from tool-call events, and structural changes (a session connecting, or
tools/mode/preset changes — the latter emit a session_updated event) trigger a
single authoritative resync. It also resyncs whenever the EventSource
reconnects. Because browsers can't attach headers to an EventSource, the live
stream assumes MCP_EXPLORER_API_TOKEN is unset (the default).
Quick start
# 1. start the server
uv run mcp-explorer
# 2. define a default tool set
uv run python examples/set_tools.py
# 3. connect a client, e.g. the MCP Inspector
npx @modelcontextprotocol/inspector # point it at http://127.0.0.1:8000/mcp
# 4. watch calls and answer them live
uv run python examples/respond_live.pyInterception modes
Each session has a mode (default from MCP_EXPLORER_MODE):
manual — a tool call blocks until either a matching preset exists or you POST a live response; if neither arrives within the timeout it returns an error.
auto — a call with a preset returns it immediately; a call without a preset returns a fast "no preset configured" error instead of blocking.
Presets always take precedence over blocking, in either mode.
REST API
Method & path | Purpose |
| Liveness. |
| List active sessions. |
| Session detail. |
| Rename a session (human-readable label). |
| Set the global default tool set. |
| Set tools for one session (→ |
| Revert a session to the default tool set. |
| Set |
| Set/replace a preset response. |
| Remove a preset. |
| Calls currently awaiting a response. |
| Resolve a blocked call with a live payload. |
| SSE stream (optional |
Tool definitions (ToolDef) carry a raw JSON Schema in input_schema, so you can
expose any tool shape. Responses (PresetResponse / live RespondPayload) accept
text (single text block) or content (list of MCP content blocks),
structured_content, and is_error; presets also accept delay_s.
Events
SSE emits: session_created, session_closed, tools_updated,
tool_call_started, tool_call_awaiting_response, tool_call_completed,
tool_call_timeout — each with type, session_id, timestamp, and data.
Tests
uv run pytesttests/test_end_to_end.py boots a real server and drives it with a real MCP
client to exercise dynamic tools, live interception, presets, and timeouts.
Notes / limitations
Single-process, in-memory state. Horizontal scaling would need shared state + sticky sessions (out of scope).
tools/list_changedrequires the client to hold its stream open; modern protocol clients (e.g. the Inspector) do.