Documentation
ReferenceArchitecture

The Node API (REST + MCP)

The node is the single binary that turns your private mesh into a public surface. It joins the mesh, discovers the apps your supervisors are hosting, and proxies traffic straight to each app's address — so external clients and AI agents never touch mesh setup. It speaks two protocols at once: a REST API (OpenAPI 3 + Swagger UI) and a full MCP server over Streamable-HTTP.

The public edge lives at https://api.tabbify.io — a CloudFront HTTPS endpoint that fronts the node. That's the only address you ever use; the raw host and port are an implementation detail behind the edge.

REST surface

Every protected endpoint expects Authorization: Bearer <TABBIFY_TOKEN> — your Tabbify API token — checked in constant time. GET /health, GET /openapi.json, and GET /swagger-ui are open.

# Discover what's running across all supervisors
curl -H 'Authorization: Bearer <TABBIFY_TOKEN>' \
  https://api.tabbify.io/v1/apps

# Start, stop, and reset app instances
curl -X POST -H 'Authorization: Bearer <TABBIFY_TOKEN>' \
  https://api.tabbify.io/v1/apps/<uuid>/start

Core endpoints: /v1/supervisors (list), /v1/apps (list), /v1/topology (discovery), /v1/apps/{uuid}/start|stop|reset, and /v1/apps/{uuid}/deploy (roll an app to a new image). The async deploy pipeline is POST /v1/deploy (returns 202 {deploy_id}) with GET /v1/deploy/{id}/status to poll, plus the per-supervisor GET/PUT /v1/supervisors/{name}/version. The full schema lives at /openapi.json and is browsable at /swagger-ui.

/app/{uuid} routing

This is the data path. The node derives an app's mesh address directly from its UUID — blake3(uuid)[0:6] mapped into fd5a:1f02:...::1 — and dials it on port 8730. No supervisor query, no cache lookup; the node and the supervisor compute the same address independently. See Routing for the address scheme.

curl https://api.tabbify.io/app/0191e7c2-1111-7222-8333-444455556666/ \
  -H 'Authorization: Bearer <TABBIFY_TOKEN>'

GET /app/{uuid} proxies /; ANY /app/{uuid}/{*rest} forwards method, headers, query, and body to the subpath. Bodies stream both directions with no buffering — large uploads and downloads pass straight through, but a slow upstream holds the connection (no per-hop timeout). An unreachable app returns 502; a malformed UUID returns 400.

MCP tools

The same business logic backs an MCP endpoint at POST/GET /mcp (official rmcp SDK, Streamable-HTTP transport, Mcp-Session-Id header for sessions). Tools mirror the REST verbs — list_supervisors, list_apps, get_app, start_app, stop_app, reset_app, and deploy_pipeline — and carry the same Bearer auth. So an agent discovers, deploys, starts, and routes to apps through one session.

How apps get there

You don't talk to the node to deploy — you git push. GitHub Actions runs the tabbify-io/deploy action, which calls POST /v1/deploy; the node clones your repo, builds it on a builder supervisor (a Firecracker build sandbox → OCI image), pushes the image to the mesh registry, and runs it as a Firecracker microVM runner that joins the mesh as its own peer. You can drive the same pipeline straight from a terminal with tcli deploy --remote. See the deploy pipeline and the CLI reference.

Config and caveats

The node joins the mesh automatically — the coordinator address is baked into the joiner, so there is nothing to point it at. Requests authenticate with a Bearer token (your network token), or — for MCP clients — via the node's OAuth flow.

Connect an MCP client

The same node serves the Model Context Protocol alongside the REST API, at https://api.tabbify.io/mcp. Interactive clients authorize via the node's full OAuth 2.1 surface (/.well-known/oauth-protected-resource, /.well-known/oauth-authorization-server, dynamic client /register, /authorize, /token) — nothing to paste; a static Bearer token works for headless/CI use. For step-by-step setup of Claude, OpenAI, and other clients, see Connect an MCP client.

Honest about the current state: activation-policy and desired-version stores are in-memory only, lost on restart unless seeded via env. The mesh join needs NET_ADMIN + /dev/net/tun at runtime, so a self-hosted node needs those capabilities (Docker: cap_add: [NET_ADMIN] with the TUN device). For standing up your own edge, see Self-hosting a node.