A thin MCP client for humans and agents — test the server you're building without touching a config file.
Change your Model Context Protocol server's code and test it
immediately — no editing mcp.json, no restarting your agent, no publishing to npm first.
run-mcp spawns the server, calls its tools, shows you its stderr when it crashes, and
restarts it on demand.
run-mcp provides three interfaces for interacting with MCP servers:
- Agent MCP Server (
run-mcp) — An MCP server that exposes tools (connect_to_mcp,call_mcp_primitive,reconnect_to_mcp) so AI agents can dynamically connect to and test local MCP projects without hardcoding them in configuration files. This is the default mode when you runnpx -y run-mcp. - Interactive REPL (
run-mcp -- node server.js) — A human-friendly CLI for developers to manually test and explore MCP servers using short, memorable commands (tools/call,status, etc.). - Headless CLI (
run-mcp call,run-mcp list-tools, etc.) — Subcommands that print clean JSON to stdout. This is the loop an agent uses from a shell when it can't (or doesn't want to) add run-mcp to its own MCP config: add--session <name>and the server stays up between commands, withreconnect,stderr, andvalidateavailable against the running instance. Also works one-shot for CI, shell scripts, andjq.
To protect the CLI and parent agents from large payloads, run-mcp automatically applies the following rules:
- Saving images to disk instead of passing multi-MB base64 strings through
- Enforcing timeouts so a hung tool call doesn't block forever
- Spilling huge text to disk: oversized responses are saved in full, and the truncated reply carries a result id — page through the rest with the
read_resulttool (or just open the file)
For humans, the REPL mode provides a quick way to test any MCP server without writing client code.
npm install
npm run buildTo install globally (makes run-mcp available system-wide):
npm install -g .# Start a REPL session with any MCP server
run-mcp -- node path/to/my-mcp-server.js
# Or use npx without installing globally
npx . -- node path/to/my-mcp-server.js
# Or start it without arguments to run the Agent Server mode!
run-mcpYou'll see an interactive prompt:
⟳ Connecting to target MCP server...
Command: node path/to/my-mcp-server.js
✓ Connected (PID: 12345)
5 tool(s) available. Type help for commands.
>
run-mcp [options] [target_command...]
| Option | Description |
|---|---|
-V, --version |
output the version number |
-o, --out-dir <path> |
Directory to save intercepted images and audio |
-t, --timeout <ms> |
Default tool call timeout in milliseconds (default: 300000) (Agent Mode only) |
--max-text <chars> |
Max text response length before truncation (default: 50000) (Agent Mode only) |
-m, --media-threshold <kb> |
Media size threshold in KB to save to disk (0 to always save, -1 to keep inline) |
--mcp |
Force start Agent Server mode even if run interactively without arguments |
-s, --script <file> |
Read commands from a file instead of stdin (REPL Mode only) |
--color <mode> |
Color output mode: always, never, auto (default: auto) |
--open-media |
Automatically open intercepted images and audio files using the host OS viewer |
--scan |
Scan the current workspace and parent directories for any JSON files containing mcpServers |
--transport <mode> |
Transport for http(s) targets: auto (default), http (Streamable HTTP), sse |
-w, --watch |
Watch the current directory for file changes and auto-reconnect (REPL Mode only) |
-h, --help |
display help for command |
Examples: $ run-mcp # Test harness (agent mode) $ run-mcp -- node my-server.js # Interactive testing (human REPL mode) $ run-mcp -s test.txt -- node my-server.js # Run a script in REPL mode $ run-mcp -- npx -y some-mcp-server # Test an npx server $ run-mcp --out-dir ./test-output # Agent mode with options $ run-mcp --out-dir ./screenshots -- node srv.js # REPL mode with options
When developing an MCP server, use --watch (or -w) to automatically reconnect whenever your source files change. This eliminates the manual reconnect step from your edit-test loop:
run-mcp -w -- node my-server.jsOn each file change, run-mcp will:
- Detect the changed files (debounced to 500ms to batch rapid saves)
- Disconnect from the current server process
- Reconnect to a fresh instance
- Show a diff of what primitives changed (tools added/removed/modified, resources, prompts)
Common directories like node_modules, .git, dist, and build are automatically ignored.
run-mcp exposes a suite of headless subcommands that print clean JSON to stdout and keep status messages on stderr. Without --session each command spawns the server fresh — fine for CI and jq one-liners. For a dev loop, use sessions: the server stays up, and reconnect/stderr/validate work against the running instance.
If you're an agent driving run-mcp through a shell tool that merges stdout and stderr: use
--session. A sessioned call prints nothing but the JSON result, and the server's stderr is reachable as data (run-mcp stderr --session <name>, or thestderrfield ofcall --raw) instead of interleaving with your output.
To prevent argument parsing conflicts between run-mcp and the target server, you should separate the target command with a double-dash -- when the target command itself contains flags or options.
- Required when the target command has options/flags:
(Must use
run-mcp list-tools -- node my-server.js --verbose
--so--verboseis passed to your server, not parsed as an option forrun-mcp.) - Optional when the target command has no options/flags:
(Runs successfully without
run-mcp list-tools node my-server.js
--.)
Instead of escaping complex JSON strings on the command line, you can provide arguments using simple key-value shorthand notation:
key=value-> evaluated as a stringkey:=json_val-> parsed as a JSON primitive (boolean, number, array, object, null)
Example:
# Call a tool using shorthand arguments
run-mcp call greet name=Alice count:=5 -- node my-server.jsWithout a session, every headless command spawns a fresh process of the target server — slow (a server that launches a browser pays that cost on every call) and stateless. Pass --session <name> and the first call spawns a background daemon that keeps the server running; every later command with the same name attaches to it, needs no target command, and prints nothing but the result:
# First call spawns the session (and, say, launches the browser)
run-mcp call browser_launch headless:=true --session main -- node browser-server.js
# Later calls reuse the running server — no cold start, no progress lines
run-mcp call browser_navigate url=https://google.com --session main
run-mcp list-tools --session main
# The server's stderr, as a JSON array of lines (everything since it started, or the last N)
run-mcp stderr --session main
run-mcp stderr 20 --session main
# Edit your server's code, then restart it and see what your edit changed
run-mcp reconnect --session main
# { "reconnected": true, "pid": 4242, "command": "node browser-server.js",
# "changes": ["Changes since last connection:", " Tools: +1 added (browser_pdf)"] }
# If the new code fails to start, the result carries the crash output inline
# ({ "reconnected": false, "error": ..., "stderr": [...] }); the old process is
# gone, `stderr --session` still shows why, and `reconnect` again once it's fixed.
# Spec-compliance checks against the running instance
run-mcp validate --deep --session main
# Stop the server and the daemon
run-mcp close-session main--show-stderr on a sessioned call replays the stderr the server wrote during that call (the daemon holds the pipe, so it can't stream live). --out-dir, --timeout, and --media-threshold apply per call, exactly as without a session. --transport is fixed when the session is created.
Keeping track of sessions. run-mcp sessions lists what's running — name, pid, command, working directory, uptime, idle timeout — as JSON. A session remembers the command and directory it was started from: if you pass a different command (or the same relative command from a different directory) with an existing session name, the call is refused with both commands shown, rather than quietly answered by the wrong server. Omit the command to attach, close-session to replace.
Nothing leaks. A session lives until you close-session it — which means a forgotten one keeps its server (and whatever the server holds, like a browser) alive until reboot. Pass --idle-timeout <minutes> on any sessioned call to have it close itself after that long without a command; the value shows up in sessions. If the server fails to start on the first sessioned call, the call exits 69 with the server's stderr, and no session is left behind.
The server's stderr is the main evidence when something goes wrong, so headless mode makes it available without you having to untangle it from stdout:
run-mcp call <tool> --rawincludes astderrarray in the result envelope: the lines written during that call (in one-shot mode: everything since the server was spawned, startup output included).run-mcp stderr -- node server.jsprints what a fresh spawn writes at startup.- A server that dies during connect has its stderr printed under
--- Target server stderr ---, instead of justConnection closed.
call [options] <tool> [json_args] [target_command...]list-tools [options] [target_command...]list-resources [options] [target_command...]list-prompts [options] [target_command...]read [options] <uri> [target_command...]describe [options] <tool> [target_command...]get-prompt [options] <name> [json_args] [target_command...]stderr [options] [count] [target_command...]reconnect [options] [target_command...]daemon [options] <session_name> [target_command...]sessionsclose-session <session_name>validate [options] [target_command...]
Use run-mcp <subcommand> --help for specific command options.
When an AI agent is actively developing an MCP server, it needs to test it. Standard MCP clients require updating a configuration file (mcp.json) and restarting the agent session entirely.
run-mcp solves this by giving the agent a suite of tools to dynamically spawn, inspect, and test local MCP servers on the fly.
How to use:
Add run-mcp to your agent's MCP configuration using npx:
{
"mcpServers": {
"run-mcp": {
"command": "npx",
"args": ["-y", "run-mcp"]
}
}
}Then use these tools from your agent:
| Tool | Description |
|---|---|
connect_to_mcp |
Spawn and connect (use include to get tools/resources/prompts) |
call_mcp_primitive |
Call a tool, read a resource, or get a prompt (auto-connects) |
list_mcp_primitives |
List tools, resources, and/or prompts |
get_server_notifications |
Inspect notifications the target emitted (list_changed, updates, logs) |
subscribe_to_resource |
Exercise a server's resource-subscription support |
reconnect_to_mcp |
Restart the target after a code edit and diff what changed |
read_result |
Page through an oversized result spilled to disk |
disconnect_from_mcp |
Tear down and reconnect after changes |
mcp_server_status |
Check connection status |
get_mcp_server_stderr |
View target server stderr output |
validate_mcp_server |
Validate an MCP server command and collect diagnostics |
list_available_mcp_servers |
List local MCP servers found in config files |
Once connected via run-mcp <command>, the following shorthand commands are available:
| Command | Description |
|---|---|
tools/list |
List all available tools |
tools/describe <name> |
Show a tool's input schema |
tools/call <name> [json] [opts] |
Call a tool (interactive if no json) |
tools/scaffold <name> |
Generate argument template for a tool |
resources/list |
List all available resources |
resources/read <uri> |
Read a resource by URI |
resources/templates |
List resource templates |
resources/subscribe <uri> |
Subscribe to resource changes |
resources/unsubscribe <uri> |
Unsubscribe from resource changes |
prompts/list |
List all available prompts |
prompts/get <name> [json_args] |
Get a prompt with arguments |
ping |
Verify connection, show round-trip time |
log-level <level> |
Set server logging verbosity |
| `history [count | clear]` |
| `notifications [count | clear]` |
roots/list |
Show configured client roots |
roots/add <uri> [name] |
Add a root directory |
roots/remove <uri> |
Remove a root directory |
!! / last |
Re-run the last command |
reconnect |
Disconnect and reconnect |
timing |
Show tool call performance stats |
status |
Show target server status |
# List available tools
> tools/list
# Inspect a tool's schema
> tools/describe screenshot
# Call a tool with arguments
> tools/call screenshot {"target": "#loginBtn"}
# Call with a custom timeout (5 seconds)
> tools/call long_running_tool {} --timeout 5000
# Arguments with spaces work fine
> tools/call send_message {"text": "hello world", "channel": "general"}Instead of prefixing every tool call with tools/call, you can invoke any target server tool directly by name, and provide arguments in shorthand key-value form:
# Direct inline tool execution with HTTPie shorthand parameters
> greet name=Bob count:=3If you invoke a tool without JSON arguments, run-mcp will guide you through an interactive scaffolding wizard:
> tools/call send_message
✔ text (string) Message text to send: Hello World!
✔ Select optional arguments to provide: channel
✔ channel (string) The Slack channel: general
✔ Execute? Yes
Calling send_message...run-mcp actively remembers your inputs across identical interactive calls, scaffolding defaults based on your last execution! Use tools/forget or --clear if you need a clean slate.
You can automate REPL commands by writing them to a file:
# commands.txt
tools/list
tools/call get_status {}
tools/call screenshot {"save_path": "/tmp/test.png"}run-mcp -s commands.txt -- node my-server.js- Lines starting with
#are treated as comments - Exits with code
0on success,1on first error
Some MCP features depend on what the client provides. run-mcp exposes these so
an agent can exercise them:
- Roots — pass
rootstoconnect_to_mcp(orreconnect_to_mcp) and your server'sroots/listcalls get a real answer.run-mcpadvertises the roots capability, so without this your server correctly sees an empty list. Roots persist across reconnects. - Log level — pass
log_levelto raise your server's logging verbosity. - Notifications —
get_server_notificationsshows what your server emitted (tools/list_changed,resources/updated, log messages). These travel outside the request/response flow, so a tool result will never reveal them. - Subscriptions —
subscribe_to_resource, then trigger a change and confirm withget_server_notifications(method='resources/updated').
Run with no target command (or --mcp), run-mcp is itself an MCP server that
exposes tools (connect_to_mcp, call_mcp_primitive, reconnect_to_mcp, …) so an
agent can dynamically spawn and test local MCP servers. Tool-call responses are
processed through the interceptor pipeline:
| Feature | Behavior |
|---|---|
| Image extraction | type: "image" responses with base64 data are saved to disk. Replaced with [Image saved to /path/to/img.png (24KB)] |
| Audio extraction | type: "audio" responses with base64 data are saved to disk. Replaced with [Audio saved to /path/to/audio.wav (12KB)] |
| Base64 detection | Text responses that are entirely base64-encoded (1000+ chars) are also saved as images |
| Timeouts | Tool calls are wrapped in a configurable timeout (default 5 minutes, use --timeout to change) |
| Truncation | Text exceeding the limit (default 50K chars, --max-text to change) is saved in full to disk; the reply keeps the head plus a result id, navigable via the read_result tool |
For the detailed system architecture diagram and source module directory map, please refer to AGENTS.md.
# Install dependencies
npm install
# Build (one-time)
npm run build
# Watch mode (rebuild on changes)
npm run dev
# Run directly
node dist/index.js -- <target_command...>MIT