Transports
mcp2cli supports three transport modes for connecting to MCP servers. Each transport handles the JSON-RPC communication layer differently.
Overview
graph TB
subgraph "mcp2cli"
ENGINE["Protocol Engine"]
end
ENGINE -->|"JSON-RPC over HTTP"| HTTP["Streamable HTTP<br/>Remote servers"]
ENGINE -->|"JSON-RPC over stdin/stdout"| STDIO["Stdio<br/>Local subprocesses"]
ENGINE -->|"File-backed state"| DEMO["Demo<br/>Offline testing"]
| Transport | Config Value | When to Use |
|---|---|---|
| Streamable HTTP | streamable_http | Remote servers, production, shared infrastructure |
| Stdio | stdio | Local development, testing, subprocess servers |
| Demo | streamable_http + demo.invalid endpoint | Learning, offline testing, CI fixtures |
Streamable HTTP
Connects to a remote MCP server over HTTP with JSON-RPC request/response cycles and SSE (Server-Sent Events) for streaming. Both http (handy for local development) and https (TLS via rustls, required for real or authenticated servers) endpoints are supported.
Configuration
server: transport: streamable_http endpoint: http://127.0.0.1:3001/mcp # plain HTTP for local/devFor a real or authenticated server, use an https endpoint so the connection is encrypted with TLS (rustls):
server: transport: streamable_http endpoint: https://mcp.example.com/email # TLS via rustlsHow It Works
Behavior depends on the negotiated MCP protocol revision (see Protocol version selection):
MCP 2026-07-28 (stateless):
- Era probe: POSTs
server/discover; aDiscoverResultselects the stateless revision - No sessions: the
Mcp-Session-Idheader is never sent - Request metadata headers: every POST carries
MCP-Protocol-Version,Mcp-Method, and (fortools/call/resources/read/prompts/get)Mcp-Name; values outside the header-safe ASCII range use the spec’s=?base64?…?=encoding - Custom param headers: tool parameters annotated with
x-mcp-headerin the input schema are mirrored asMcp-Param-*headers; tools with invalid annotations are excluded from discovery - SSE responses: the server may stream request-scoped notifications followed by the final response
- Server input requests: delivered as
resultType: "input_required"results (MRTR) and resolved by retrying the request — never as server-initiated JSON-RPC requests - Change notifications: delivered on a
subscriptions/listenPOST response stream - Cancellation: closing the response stream cancels the request
MCP 2025-11-25 (legacy):
- Initialization: sends
initializerequest with client capabilities - Session negotiation: server may return a session ID via
Mcp-Session-Idheader - Operations: each MCP operation is a POST with JSON-RPC body
- SSE responses: the server can stream responses as SSE events
- Notifications: server notifications arrive via the SSE stream
Protocol version selection
By default (server.protocol_version: auto) mcp2cli probes with
server/discover and falls back to the 2025-11-25 initialize handshake
when the server does not answer with a recognised modern response — the
exact backward-compatibility algorithm from the 2026-07-28 spec. Pin a
revision to skip detection:
server: transport: streamable_http endpoint: https://mcp.example.com/email protocol_version: "2026-07-28" # or "2025-11-25", or auto (default)Authentication
Add an Authorization header automatically via auth login:
email auth login# Prompted for token → stored in instances/<name>/tokens.json# All subsequent requests include the Authorization headerRunning a Test Server
npx @modelcontextprotocol/server-everything streamableHttpCustom CA certificates (SSL_CERT_FILE)
By default, HTTPS connections trust the bundled Mozilla root set
(webpki-roots) — no system trust store is needed. In corporate
environments that run a TLS-inspection proxy (mitmproxy, ZScaler,
Cloudflare WARP, Fortinet, …), outbound HTTPS traffic is re-signed with a
private root CA that isn’t in that bundle, and connections fail with:
Error: streamable HTTP request failed: client error (Connect)Set SSL_CERT_FILE to a PEM file containing the extra CA certificate(s)
to trust — the same convention curl, Python requests, Go’s net/http,
and Ruby’s OpenSSL bindings use:
export SSL_CERT_FILE=/opt/corp/mitmproxy-ca-cert.pememail lsThe bundled roots are never removed — SSL_CERT_FILE only adds to the
trust store, and unsetting it restores the default behavior exactly. This
applies to every outbound HTTPS connection mcp2cli makes: the MCP
transport, telemetry shipping, and OAuth login. An unset or unreadable
SSL_CERT_FILE degrades to the bundled roots with a warning rather than
failing outright.
Stdio
Spawns a local subprocess and communicates via line-delimited JSON-RPC over stdin/stdout.
Configuration
server: transport: stdio stdio: command: npx args: ['@modelcontextprotocol/server-everything'] cwd: /path/to/project # Optional working directory env: # Optional environment variables API_KEY: sk-abc123 NODE_ENV: developmentHow It Works
- Spawn: mcp2cli starts the subprocess with the given command/args
- Initialize: Sends JSON-RPC
initializeover stdin - Operations: Writes JSON-RPC requests to stdin, reads responses from stdout
- Notifications: Server notifications arrive on stdout between responses
- Shutdown: Sends stdin EOF when done; subprocess exits
Key Behaviors
- Process lifetime: The subprocess lives for the duration of the CLI command. Use Daemon Mode to keep it alive.
- stderr passthrough: The subprocess stderr is inherited for debugging (server log messages appear in terminal)
- Environment variables: The
envmap is merged with the current environment
Running a Test Server
# This is what mcp2cli spawns internally:npx @modelcontextprotocol/server-everythingDemo Mode
A file-backed backend that simulates an MCP server without network or subprocess. Ideal for learning and testing.
Configuration
server: transport: streamable_http endpoint: https://demo.invalid/mcp # Magic demo endpointHow It Works
When the endpoint is demo.invalid, mcp2cli activates the demo backend:
- Discovery: Returns a static set of tools, resources, and prompts
- Tool calls: Echo back arguments with mock responses
- State: Persisted to
~/.local/share/mcp2cli/demo-remote-state.json - No network: Everything runs locally in-process
Use Cases
- Learning mcp2cli without setting up a real server
- CI/CD tests that need deterministic MCP behavior
- Documentation examples
mcp2cli config init --name demo --app bridge \ --transport streamable_http --endpoint https://demo.invalid/mcp
mcp2cli use demomcp2cli ls # Shows demo capabilitiesmcp2cli echo --message "hello"Ad-Hoc Transport Selection
Skip config files entirely with --url or --stdio:
# HTTP — creates ephemeral streamable_http configmcp2cli --url http://127.0.0.1:3001/mcp ls
# Stdio — creates ephemeral stdio configmcp2cli --stdio "npx @modelcontextprotocol/server-everything" ls
# Stdio with environmentmcp2cli --stdio "python server.py" --env API_KEY=secret echo --message testSee Ad-Hoc Connections for full details.
Transport Comparison
| Feature | Streamable HTTP | Stdio | Demo |
|---|---|---|---|
| Remote servers | ✅ | ❌ | ❌ |
| Local subprocess | ❌ | ✅ | ❌ |
| Offline operation | ❌ | ❌ | ✅ |
| Session persistence | Via Mcp-Session-Id | Process lifetime | File-backed |
| Auth support | ✅ Bearer tokens | ❌ | ❌ |
| SSE streaming | ✅ | N/A | N/A |
| Daemon compatible | ✅ | ✅ | ✅ |
| Startup latency | Network RTT | Process spawn | ~0ms |
See Also
- Ad-Hoc Connections —
--urland--stdiowithout config - Daemon Mode — keep connections warm
- Configuration Reference — full server config options