spanly run
Wrap an MCP server as a child process – stdio or HTTP, zero code changes.
spanly run is the default path for most users. It launches your MCP
server as a child process and captures every JSON-RPC frame on its
transport. Works identically for stdio servers, HTTP servers, and SSE
servers.
30-second demo
# 1. Set your API key (region auto-detected from the prefix)export SPANLY_API_KEY="$SPANLY_API_KEY"# 2. Wrap your MCP server. stdio:npx -y @spanly/spanly run -- node ./server.js# Or HTTP – the wrapper takes your port; the child gets a random one:npx -y @spanly/spanly run --port 3000 -- node ./server.jsThat's it. Run your MCP client (Claude Desktop, Cursor, …) as usual. Telemetry shows up in the Spanly dashboard.
stdio mode (default)
When --port is not set, the CLI runs in stdio mode. It spawns your
server, hooks stdin/stdout, and forwards JSON-RPC frames in both
directions – capturing them in the process.
spanly run -- node ./server.js
spanly run -- python -m my_mcp
spanly run -- ./my-go-binaryYour MCP client sees exactly the same stdio behavior as if it had launched the server directly. There is no protocol change.
HTTP mode
Set --port to switch to HTTP mode:
spanly run --port 3000 -- node ./server.jsWhat happens:
- The CLI binds to port
3000(the port your MCP client connects to). - The child server gets a random free port via the
PORTenvironment variable (rename with--child-port-env). - Every HTTP request to
/mcp//sse(configurable via--inspect-prefix) is captured. Everything else is forwarded untouched.
Your MCP client URL doesn't change – it still points at port
3000. The fact that there's a wrapper in between is transparent.
Pinning the child port
If your server can't pick its own port:
spanly run --port 3000 --child-port 3001 -- ./serverMulti-tenant tagging via headers
Map inbound HTTP headers to context fields with --context-header:
spanly run --port 3000 \
--context-header=X-Tenant=environmentId \
--context-header=X-Org=organisationId \
-- ./serverIn the dashboard the captured requests can then be sliced by
environmentId or organisationId – no code change in the server
required.
Examples
# Node MCP, stdio
spanly run -- node server.js
# Python MCP, HTTP on port 3000
spanly run --port 3000 -- python -m my_mcp
# Go MCP, HTTP with admin metrics
spanly run --port 3000 --admin-addr=:9090 -- ./my-mcp-server
# Multi-tenant tagging from request header
spanly run --port 3000 \
--context-header=X-Tenant=environmentId \
-- ./srvEmbedding in MCP client configs
Claude Desktop / Cursor / Windsurf
{
"mcpServers": {
"my-server": {
"command": "npx",
"args": ["-y", "@spanly/spanly", "run", "--", "node", "./server.js"],
"env": {
"SPANLY_API_KEY": "spanly_us_xxxxxxxxxxxxxxxxxxxxxx"
}
}
}
}The user-facing behavior is identical to running node ./server.js
directly – they see the same prompts and tools – but every interaction
is now visible in Spanly.
Flag reference
See the full flag reference for the complete list of
flags shared by run and proxy (buffering, retry, admin endpoints,
OTLP co-export).