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.

MethodPathPurpose
POST/mcpSend a JSON-RPC request. Body is the JSON-RPC envelope.
GET/mcpOpen an SSE stream to receive server-initiated messages.
DELETE/mcpTerminate 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.