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_PROVIDERS — an ordered JSON list, default ["redis"] — names the chain the verifier tries. Swap the module to change the authn backend.
examples/deploy/access_control_manifest.yaml
/api GET is served the dataless Studio SPA shell — index.html and its static assets — so a deep-link refresh reaches the login page and the app boots. This is on by default (the spa_shell_public setting), so you do not pin /, /assets, /vendor, or any SPA path public; the shell leaks nothing, because all data sits behind authenticated /api/* calls. The login API namespace /api/login is public by code default, and everything else under /api/* stays authed. A headless (no-Studio) deployment can set spa_shell_public to false to turn the fallback off.
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.
The probe routes are public by default — no manual step. With access control enabled, unknown routes are denied, but /health and /ready are served public by the app’s own route-level declaration: they ship in ACCESS_CONTROL_ACKNOWLEDGED_PUBLIC_ROUTES, so a probe carrying no key reaches them with nothing to configure. Do not also list them in ACCESS_CONTROL_ALWAYS_PUBLIC_PATH_PREFIXES — an acknowledged route that overlaps an always-public prefix is rejected at boot.
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+.
tai ships with the runtime — pip install tai42-skeleton. To run it against an unreleased change instead, install from the repository: pip install git+https://github.com/tai42ai/tai-skeleton.Self-host the whole stack
Running the platform as containers — the batteries-included image, Postgres and Redis, the Compose bundle, and the Helm chart — is covered in Operate. Datastores are never host-published there; 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.

