tai serve with a transport, a bound address, a manifest, and the environment its tools need. This page covers those choices. Provider-specific settings (a config provider’s own environment variables) live in that provider’s repository README.
Running inside your own ASGI process instead of
tai serve — a plain uvicorn run or a mount in an existing FastAPI/Starlette app? See Embed in your own ASGI process.Choose a transport
tai serve runs the MCP server over one transport. Select it with -t/--transport:
--uds /path/to/socket instead of a host and port.
Scale workers
Run multiple worker processes with-w/--workers. The transport constrains the worker count:
stdioandsserun a single worker.http/streamable-httprun a single stateful worker by default; pass--stateless-httpto lift the single-worker restriction and run several.
tai serve --workers N (N > 1) is refused without TAI_BUS_REDIS_URL, because sibling workers would serve stale config after a reload with no channel to converge on.
examples/deploy/serve_workers_help.sh
Multiple workers need a shared sub-MCP store. Running more than one worker (A single-worker deployment may run on the default in-memory store.
-w N, N > 1) requires SUB_MCP_REDIS_URL. Sub-MCP app registrations are per-process in-memory routing state; without a shared Redis a registration made on one worker is invisible to its siblings and no worker rehydrates another’s routes. Point it at a plain Redis:Point at a manifest and environment
The server loads a manifest and reads its environment at startup. Point at the manifest with--manifest-path, or set TAI_MANIFEST_PATH as the flag’s default. Provide the environment your tools need — see the repository’s .env.example.
file provider anchors .env, manifest.yml, and templates under is /app; override it with TAI_CONFIG_DIR_PATH.
HTTP routers and headless mode
Out of the box the server serves the full Studio surface — every built-in page and its API — with no router list in the manifest. Thedefault_routers field controls this:
all(the default) — serve the whole Studio UI plus its API. A bare manifest gives you the full surface; you never enumerate the core routers, and no page is left dark for want of a listing.api— a headless deployment: mount the core JSON API (/api/*) but do not serve the browser SPA. Use it for an API-only backend with no UI.none— mount nothing by default and list every router inrouters_modulesyourself. For a fully manual or MCP-only surface.
routers_modules adds extras — your own routers, a plugin’s — on top of the selected set; the loader keeps the Studio SPA catch-all last. See the manifest for the field reference.
Run mode and logging
TAI_RUN_MODE selects how tai serve runs. Leave it unset (or empty) for the normal multi-worker server; set it to debug for a single in-process server, which ignores --workers. Any other value is rejected loudly at startup rather than silently falling through — a typo never selects a mode.
TAI_LOG_LEVEL (default INFO). A config reload that changes it re-applies logging under tai serve.
Swap the config provider
Where the server reads its env and manifest is a swappable provider. The defaultfile provider reads from disk with zero dependencies. Select a different provider — for example a Kubernetes-backed one — with TAI_CONFIG_MODE:
Access control at deploy
Enabling access control (ACCESS_CONTROL_ENABLE, on by default) adds three deploy requirements.
Postgres for the policy store. The access-control policy and route tables are Postgres-backed, with their own ACCESS_CONTROL_STORE_* connection namespace (defaulting to the same tai database as the other stores). Supply the password via ACCESS_CONTROL_STORE_PG_PASSWORD.
ACCESS_CONTROL_REDIS_URL, default redis://localhost:6379/0) and the identity records from plain Redis hashes. Any standard, module-less redis-server runs the whole ecosystem, access control included — redis:7, redis:8, or valkey. No RedisJSON, no redis-stack, no Redis modules are required anywhere.
An identity provider in the manifest. The skeleton ships no concrete identity provider, so a deployment with access control enabled must name one as a lifecycle_modules entry — its module registers the provider at import. The default is the Redis-backed api-key provider, which registers itself as the "redis" provider; ACCESS_CONTROL_AUTH_PROVIDER (default redis) selects it. Swap the module to change the authn backend.
examples/deploy/access_control_manifest.yaml
ACCESS_CONTROL_ALWAYS_PUBLIC_PATH_PREFIXES replaces the code default (/api/login) wholesale, so re-include /api/login alongside the SPA prefixes:
/api/* except /api/login stays authed. The distribution compose ships this default; a headless (no-Studio) deployment can drop the SPA prefixes.
See the access-control guide for the end-to-end walkthrough.
Liveness and readiness
The server exposes two operational routes for orchestrators and load balancers. Both areGET, unauthenticated, and served outside the OpenAPI surface — there is no CLI command for them.
/health stays green as long as the process is up. /ready lets an orchestrator hold traffic off a worker whose Redis or Postgres is unreachable — such a worker would 500 every real request but stay live.
Pin the probe routes public. With access control enabled, unknown routes are denied, so pin /health and /ready public for probes that carry no key. Use the public-routes door (the public marker); there is no "*" wildcard.
Preflight check
Run read-only health diagnostics before serving — Python version, config mode, Postgres and Redis connectivity, and whether the schema is applied. It exits non-zero when any check fails, and redacts credentials.Requires Python 3.13+. Nothing is on PyPI yet — install from source (
pip install git+https://github.com/tai42ai/tai-skeleton). This switches to pip install tai42-skeleton once published.Self-host from source
Thetai-distribution repo ships a
Docker Compose bundle that runs the batteries-included image as three processes
(serve, backend worker, metrics) over Postgres and Redis. The base
compose/docker-compose.yml pulls the released image; to run against local
changes instead, layer compose/docker-compose.local.yml, which adds a
SOURCE=local build to every app service — it builds the image from your
sibling checkouts and starts the stack against it:
siblings build context defaults to the parent of the checkouts (../..),
overridable with TAI_SIBLINGS. Because that context may hold untracked
secrets, the source build runs on a LOCAL builder only — never CI or a shared
registry. Datastores are never host-published; only serve’s port is, and access
control gates it exactly as above.
See also
- Transports — the HTTP, SSE, stdio, and UDS paths in depth.
- Config and secrets — the config-provider seam.
- Integrations — point an MCP client at the running server.
- CLI reference — the full
tai servesurface.

