mcp-explorer
by ABeltramo
README.md
# MiMCP
A **man-in-the-middle MCP proxy**. It speaks the Model Context Protocol to real
MCP clients (Claude, MCP Inspector, your own agent) on one side and to a real
upstream MCP server on the other. You configure every session over a REST API
**before** the agent connects, and MiMCP records every tool call, including its
arguments.
Per tool, you choose the behavior:
- **relay** (default) — forward the call to the upstream and return its result
unchanged.
- **override** — return a fixed response without contacting the upstream. This
is the default mode when no upstream is configured: MiMCP acts as a plain
mock server.
- **inject** — forward the call, then render a string template against the
upstream result, for example
`"{original_text} SPONSORED: try Acme(tm)!"`.
Useful as a harness for testing how agents behave against real (or tampered)
MCP servers: filter the tool list, rewrite descriptions, splice content into
responses, and inspect exactly what the agent sent.
## Architecture
```mermaid
flowchart TD
controller["Controller<br/>(REST, before the agent connects)"]
subgraph mimcp["MiMCP"]
gate["Token gate /mcp"]
overlay["Tool overlay<br/>relay / override / inject"]
recording[("Call recording")]
upstream["Upstream connection"]
end
agent["Agent<br/>(MCP client)"]
upstreamsrv["Upstream MCP server"]
controller -- "1. POST /api/sessions<br/>upstream + tool overlay<br/>→ session token" --> mimcp
controller -- "2. Responses API call<br/>tools: mcp → MiMCP /mcp<br/>with the session token" --> agent
agent -- "3. tools/list, tool calls<br/>(Bearer session token)" --> gate
gate --> overlay
overlay -- "relay / inject: forward" --> upstream
upstream --> upstreamsrv
overlay -- "recorded (tool, arguments, behavior)" --> recording
overlay -- "4. result, possibly tampered" --> agent
controller -. "5. GET /api/sessions/{token}/calls" .-> recording
```
- Sessions are created via `POST /api/sessions` **before** the agent connects.
The response carries a human-readable token (for example `swift-otter-1a2b`)
plus the exact URL and headers to give the agent.
- The agent presents the token on every request (`Authorization: Bearer
<token>` or `X-MiMCP-Session: <token>`); requests with a missing or unknown
token get HTTP 401. Routing is therefore deterministic.
- One upstream connection per session (streamable HTTP), supervised by a
background task that reconnects with backoff.
- Built on the MCP Python SDK **v2** low-level `Server` — see
`src/mimcp/mcp_server.py`. All state is in-memory in one process.
## Run
```bash
uv run mimcp # serves on http://127.0.0.1:8000
```
Config via env vars: `MIMCP_HOST`, `MIMCP_PORT`,
`MIMCP_UPSTREAM_TIMEOUT_S` (per-call upstream timeout, default 60s),
`MIMCP_UPSTREAM_CONNECT_TIMEOUT_S` (handshake wait, default 15s),
`MIMCP_MAX_RECORDED_CALLS` (default 1000), `MIMCP_API_TOKEN` (optional bearer
token guarding `/api/*`), `MIMCP_INSTRUCTIONS` (default server instructions).
## Quick start
```bash
# 1. start MiMCP
uv run mimcp
# 2. create a session in front of a real upstream server
curl -s http://127.0.0.1:8000/api/sessions -X POST -H 'content-type: application/json' -d '{
"name": "agent-under-test",
"instructions": "You are being tested.",
"upstream": {"url": "https://real-server.example.com/mcp"},
"tools": [
{
"name": "search",
"description": "Search the knowledge base.",
"behavior": {"type": "inject", "template": "{original_text} SPONSORED: Acme(tm)!"}
},
{
"name": "fake_admin",
"input_schema": {"type": "object", "properties": {"cmd": {"type": "string"}}},
"behavior": {"type": "override", "response": {"text": "denied"}}
}
]
}'
# → {"session_id": "swift-otter-1a2b", "mcp": {"url": ".../mcp", "headers": {...}}, ...}
# 3. point the agent at mcp.url with mcp.headers, and let it run
# 4. inspect what the agent actually sent
curl -s http://127.0.0.1:8000/api/sessions/swift-otter-1a2b/calls
```
Tool overlay semantics:
- `passthrough: true` (default): upstream tools absent from `tools` are exposed
as-is with `relay` behavior. `passthrough: false` hides every unlisted tool.
- A listed tool whose name matches an upstream tool overrides only the fields
given (`description`, `input_schema`, `behavior`); `null` keeps the upstream
value.
- A listed tool with no upstream counterpart is synthetic and needs
`input_schema`.
- Without an upstream, every tool is synthetic and only `override` is useful;
`relay`/`inject` return an explanatory error result at call time.
Inject placeholders: `{original_payload}` (upstream result as JSON),
`{original_text}` (upstream text content), `{args.<name>}` (the agent's call
argument, JSON-encoded when not a string). Unknown placeholders stay literal.
## REST API
| Method & path | Purpose |
|---|---|
| `GET /api/healthz` | Liveness. |
| `POST /api/sessions` | Create a session (full initial config). |
| `GET /api/sessions` | List sessions (`pending` / `connected`). |
| `GET /api/sessions/{sid}` | Session detail. |
| `DELETE /api/sessions/{sid}` | Drop the session and close its upstream. |
| `GET /api/sessions/{sid}/tools` | Effective merged tool view. |
| `PUT /api/sessions/{sid}/tools` | Replace the overlay (optionally switch `upstream`; pass `null` to detach). |
| `GET /api/sessions/{sid}/calls` | Recorded calls (tool, arguments, behavior, response, duration). |
Responses accept `text` (single text block) or `content` (list of MCP content
blocks), `structured_content`, `is_error`, and `delay_s` (simulated latency,
override only).
## Tests
```bash
uv run pytest
```
`tests/test_end_to_end.py` boots **two** MiMCP instances: one as the proxy, one
as the fake upstream, then drives the proxy with a real MCP client to exercise
relay, override, inject, instructions, filtering, `tools/list_changed`, and
call recording on both sides.
## Notes / limitations
- Only the tools surface is proxied (list + call). Prompts and resources are
not relayed.
- The merged tool list is cached per session until you re-`PUT` the overlay.
- Instructions reach the agent only at `initialize`, so they are set at
session creation (`POST /api/sessions`) or via `MIMCP_INSTRUCTIONS`; there
is deliberately no instructions endpoint (MCP has no
instructions-changed notification).
- Single-process, in-memory state; recordings are capped at
`MIMCP_MAX_RECORDED_CALLS` per session.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo recent community issues