Skip to main content
A transport is how a client reaches a server. You pick one when you start the server, and clients connect over it. The runtime speaks HTTP by default and can also serve over SSE, STDIO, or a fast same-machine Unix-domain-socket path.

Choosing a transport

tai serve takes --transport. HTTP is the default; the MCP endpoint is then served under /mcp.
  • HTTP — the default network transport, for MCP clients over the network.
  • SSE — a streaming HTTP transport.
  • STDIO — for a client that launches the server as a subprocess and talks over its standard streams.

The UDS path

For same-machine communication, --uds <path> binds the server to a Unix-domain socket instead of a host and port — a fast local path that avoids the network stack. tai42-kit supplies the client-side UDS transports and a selector, get_mcp_transport, that picks the right transport for a target: a UDS transport for a socket, or a plain config for a networked server. A mounted external MCP server can be reached over any of these — the manifest’s mcp entry sets url, uds, or command per server.

Statefulness and workers

The HTTP transports are stateful per process: each MCP session is pinned to the worker that created it. Under multiple workers a session opened on one worker breaks when another worker handles its next request, so tai serve refuses --workers greater than one on any stateful transport (http, streamable-http, sse) with an error that names the fix. The fix is --stateless-http, which turns on the underlying stateless HTTP mode and lifts the refusal — for the HTTP transports only. It has no meaning for sse or stdio (SSE has no stateless mode and STDIO has no HTTP sessions), so combining it with either is itself refused. More than one worker also needs the worker bus, or sibling workers would serve stale config after a reload with no channel to converge on, and shared sub-MCP and hooks stores (SUB_MCP_REDIS_URL and HOOKS_REDIS_URL; TAI_DEFAULT_REDIS_URL stands in for them and the bus), or a sub-MCP app or a hook registered through one worker would be unknown to its siblings. tai serve hands its worker count to each worker, and each worker refuses to boot without them, after it applies the stored env; tai serve then exits non-zero. So a multi-worker HTTP server needs --stateless-http, TAI_BUS_REDIS_URL, and both stores. Embedding the runtime in your own process applies the session-pinning rule the same way: pass create_app(manifest_path="manifest.yml", stateless_http=True) to run several workers. The bus and the stores, though, are a documented limitation when you embed — the worker count reaches the workers rules only from tai serve, and in-process code cannot count sibling processes, so a create_app(..., stateless_http=True) factory run under an external --workers counts each process as one worker and passes the workers rules even with no bus and per-process stores. Set TAI_BUS_REDIS_URL, SUB_MCP_REDIS_URL and HOOKS_REDIS_URL yourself in any multi-process embed. See Multi-worker and fleet sync.
Before binding a UDS path, tai serve cleans up a stale socket left by an unclean shutdown, but refuses to unlink a socket that answers a connect probe (a live server) or a path that is not a socket. This keeps one command from clobbering another server’s socket.
See the deploy guide for choosing a transport in production and the CLI reference for the full tai serve options.