Transports
Choose between stdio (local child process) and HTTP + SSE (remote / shared process).
Last updated
Transports
Two transports ship in @plainwork/mcp. Pick based on where the agent runs
and how you want to scale.
Stdio (default)
Stdio spawns one @plainwork/mcp child process per host. The host pipes
JSON-RPC frames over stdin/stdout; the child terminates when the host
closes.
Use stdio when:
- The agent runs in a local editor/desktop app: Claude Desktop, Claude Code, Cursor.
- Each agent needs its own API key.
- You do not want a long-running server process.
Stdio is selected automatically. No flags needed — the NPM guides use it by default. To be explicit:
PLAINWORK_API_KEY=pw_agent_... \
MCP_TRANSPORT=stdio \
npx -y @plainwork/mcp
HTTP + SSE
The HTTP transport runs one long-lived server process that multiple clients (or one client with multiple tabs/sessions) can connect to over HTTP with Server-Sent Events.
Use HTTP when:
- The agent or orchestrator lives in a browser or a workflow engine (n8n, Zapier, a custom dispatcher).
- You want a shared MCP endpoint for a team's tooling.
- You want to front the server with TLS, load-balancing, or logging middleware you already operate.
Start it with either flag:
PLAINWORK_API_KEY=pw_agent_... MCP_TRANSPORT=http node apps/mcp/dist/index.js
# or
PLAINWORK_API_KEY=pw_agent_... node apps/mcp/dist/index.js --http
Default port 9090, override with MCP_HTTP_PORT. Binds 0.0.0.0.
Endpoints
All endpoints are mounted at /mcp.
| Method | Path | Purpose |
|---|---|---|
POST | /mcp | Send a JSON-RPC request. Body is the JSON-RPC envelope. |
GET | /mcp | Open an SSE stream to receive server-initiated messages. |
DELETE | /mcp | Terminate the current session. |
The server returns 404 Not Found for any other path.
Sessions
The transport is session-aware. The first request (a POST with no
session header) creates a fresh session and the response includes the
mcp-session-id header. Echo that header on every subsequent request to
stay on the same session — sessions hold the tool capability map and the
SSE channel. Omitting the header starts a new session, which works but is
usually not what you want.
curl -i -X POST http://localhost:9090/mcp \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize", ...}'
# Response contains: mcp-session-id: abc123...
curl -i -X POST http://localhost:9090/mcp \
-H 'Content-Type: application/json' \
-H 'mcp-session-id: abc123...' \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'
The server drops sessions when the underlying transport closes (client
disconnect, network timeout, explicit DELETE /mcp). A stale
mcp-session-id is ignored and a new session is minted.
Multi-tenant isolation
The HTTP transport today binds one PLAINWORK_API_KEY to the entire
process. Every session shares that key. If you need to isolate tenants or
agents, run one process per tenant/agent.
A public multi-tenant endpoint where each session authenticates with its
own Authorization: Bearer token is planned but not shipped. This page
will document it when it lands.
Health check
Any endpoint other than /mcp returns 404. There is no dedicated /health
endpoint; use GET /mcp for liveness — a healthy server responds with a
200 and the SSE Content-Type:
curl -sI http://localhost:9090/mcp | head -2
# HTTP/1.1 200 OK
# Content-Type: text/event-stream
Choosing at a glance
- Local editor on your laptop → stdio.
- One-off script or curl testing → HTTP (easier to observe).
- n8n, custom web UI, or browser-side agent → HTTP.
- CI job that runs once and exits → stdio.
Related: Custom HTTP client has a full runnable curl example against the HTTP transport.