FastAPI backend
Stand up a production agent-webkit server with FastAPI — auth, custom routes, and configuration.
The fastest way to expose the Claude Agent SDK over HTTP+SSE is the bundled FastAPI adapter.
pip install "agent-webkit-server[fastapi]"The minimum
from agent_webkit_server.adapters.fastapi import create_app
from agent_webkit_server.auth import AuthConfig
app = create_app(auth=AuthConfig.from_env())create_app returns a FastAPI instance with the wire protocol routes mounted at the root: POST /sessions, GET /sessions/{id}/stream, POST /sessions/{id}/input, DELETE /sessions/{id}.
Run it any way you'd run a FastAPI app:
uvicorn main:app --host 0.0.0.0 --port 8000Authentication
AuthConfig is the only auth knob you need:
# Static Bearer token (good for service-to-service)
AuthConfig(token="hunter2")
# Read from env: AGENT_WEBKIT_AUTH_TOKEN or AGENT_WEBKIT_NO_AUTH=1
AuthConfig.from_env()
# Disabled (DEV ONLY)
AuthConfig(disabled=True)For real systems you usually want JWTs / OIDC instead of a shared secret. The recommended pattern: validate at your edge (load balancer, reverse proxy, FastAPI middleware) and pass through a stable token to agent-webkit. Or, mount your own auth dependency on the routes — FastAPI's Depends machinery composes cleanly with the routes create_app registers.
Adding your own routes
app is a regular FastAPI instance. Register more routes as you would normally:
from fastapi import Depends
app = create_app(auth=AuthConfig.from_env())
@app.get("/healthz")
def healthz():
return {"ok": True}
@app.get("/me")
def me(user = Depends(get_user)):
return {"user_id": user.id}The agent-webkit routes don't conflict with anything outside /sessions.
CORS
The adapter doesn't add CORS middleware (intentionally — it's deployment-specific). For browser clients on a different origin:
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["https://app.example.com"],
allow_credentials=True,
allow_methods=["GET", "POST", "DELETE", "OPTIONS"],
allow_headers=["Authorization", "Content-Type", "Last-Event-ID"],
expose_headers=["Last-Event-ID"],
)Last-Event-ID in both allow_headers and expose_headers is required for resume to work.
Custom SDK construction
The default sdk_factory builds a standard ClaudeSDKClient. Override it to inject SDK options (custom MCP servers, hooks, system prompt, etc.):
from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions
async def my_sdk_factory(*, model=None, permission_mode=None, cwd=None):
options = ClaudeAgentOptions(
model=model,
permission_mode=permission_mode,
cwd=cwd,
system_prompt="You are a code review assistant.",
mcp_servers={"linear": {"command": "linear-mcp"}},
)
client = ClaudeSDKClient(options)
await client.connect()
return client
app = create_app(auth=AuthConfig.from_env(), sdk_factory=my_sdk_factory)Anything reachable via ClaudeAgentOptions is reachable from here.
Reverse-proxy notes
SSE has a few well-known foot-guns behind proxies:
- Buffering off. Disable response buffering. Nginx:
proxy_buffering off; proxy_request_buffering off; proxy_read_timeout 600s; - Compression off for SSE.
gzip off;in the SSE location, or setCache-Control: no-transform. - Idle timeouts. Bump them past your max session length, or rely on the 15s
:keepaliveframe to keep connections warm. ALBs default to 60s; bump to 600s+.
Observability
create_app doesn't ship its own structured logging. Plug in standard FastAPI middleware (structlog, OTel, sentry-fastapi) — they all work as expected. Useful labels:
session_id(extract from path)event_seq(the SSEidfield, for resume diagnostics)correlation_id(for permission/question races)
Persistent storage
The default registry is in-memory. For multi-host failover, swap in the Postgres adapter — see Postgres sessions.
Where to next
- Deployment guide — sizing, scaling, eviction.
- Server API reference — every Python entry point.
- Custom adapter — Starlette, Litestar, raw ASGI.