tofu

Docs

How Tofu works

The path from code on your machine to an app answering on the internet, and the parts that are deliberately not built.

One delivery, end to end

The dashboard and your coding agent drive the same work: check the code, build it, and publish only a build that passed a health check.

  1. Import. A folder or ZIP upload, a GitHub import, or ingest from your agent; an update replaces the same project's source and keeps its settings.
  2. Detect. Deterministic — no model call, no network — returning a preset (nextjs, vite, astro, sveltekit, nuxtjs, remix, gatsby, static, unknown) to confirm.
  3. Check. Fail-closed rules over the exact snapshot about to be uploaded: a committed provider key (a Supabase service_role JWT), a credential form posting off-origin, or seed-phrase collection.
  4. Build and publish. The snapshot goes to the provider, and static skips the build only for a genuinely prebuilt site. A READY build must then answer an anonymous HTTP request before the address moves, and a failure leaves the previous version serving.

deploy waits, bounded (about twenty seconds), for a settled answer; status continues from there. The answer is machine-readable:

Result fieldWhat it tells you
publiclyReachabletrue only when an anonymous request was served.
verification.accesspublic (served), protection (refused anonymously: access protection, not a failed build) or unreachable (retried once).
servingWhether this deployment answers the app's address; ready with serving: false was not published.

The address to report is managedUrl; a row with no verdict carries a note, never a failed deployment, and an agent must not open that address in a browser to confirm a build. Detection and the check are rules; the build and network are the provider's.

Give the work to your coding agent

Tofu's client is a local stdio MCP bridge: your agent runs it, and it reaches the same authenticated control plane as the dashboard. Node.js 22 or newer is required.

Start on the setup page, choose Codex, Claude Code, Cursor or another stdio client, and paste the prompt into your agent; the client and skill are pinned by SHA-256, and a mismatch stops the install.

# Private install (0700/0600), then the registration it emits for this client:
TOFU_URL=https://app.trytofu.ai node tofu-agent.mjs setup codex
codex mcp add tofu --env TOFU_URL=https://app.trytofu.ai --env TOFU_CLIENT=codex -- <absolute-node> <absolute-client> mcp

TOFU_URL selects the exact trusted origin (HTTPS, or loopback in development) and TOFU_CLIENT names the connection and grants nothing; setup emits argv and configuration without touching your agent settings, and installs the client and skill privately.

Sign-in is a device flow: login returns a code and a same-origin approval URL, you approve the request you started, and finishLogin (at most once every five seconds) completes it. The connection lasts seven days and is revoked in the dashboard's Agent connections; only a hash of the credential is stored.

The bridge exposes 22 tools:

GroupTools
ConnectionconnectionStatus, login, finishLogin
Sourceprojects, ingest, detect, setFramework, scan
Deploydeploy, status, listDeployments, getBuildLogs, getRuntimeLogs, repairContext, rollback
SettingslistEnv, manageEnvironment
DatabasedatabaseStatus, databaseCreate, databaseReconcile
DomainsdomainConnect, domainVerify

listEnv returns names and scopes and no values; environment values are entered in the dashboard. Nothing here can change billing, buy or transfer a domain, write DNS, or run SQL, and databaseCreate needs a literal confirmed: true for that exact project because it spends Tofu's money.

Managed databases

A paid app can have one managed Supabase project (Next.js, Vite or Astro), created from Database in the dashboard or by an agent's databaseCreate. The app must already have been deployed once, because Tofu wires the database into its existing hosting project. The connection variables then go into that project's sensitive environment and reach the app on the next deploy: DATABASE_URL, SUPABASE_URL and the public URL and key under the framework's prefix. The control database keeps names and flags only, and Tofu writes no connection string to a log.

The database is empty until your migration runs inside the build: prisma migrate deploy, drizzle-kit migrate, or supabase db push --db-url "$DATABASE_URL". A failed migration fails the build and blocks publication — the previous version keeps serving — but statements already accepted stay applied.

Tofu never runs migrations and has no SQL console. Its read is read-only — recorded migrations and the base-table count in public — reported as empty, populated, unavailable or unknown: a failed read is never an empty schema.

The dashboard action Show the connection string is the one owner-only exception: it rebuilds the value from the recorded attempt and writes one audit line per call. No agent can reach it, and rotation is not implemented.

Domains and DNS

  1. Claim a hostname you already own in Domains, or with domainConnect: Tofu returns a _tofu.<hostname> TXT challenge for you to create — Tofu writes no DNS record.
  2. Tofu re-reads the challenge over DNS-over-HTTPS, verifies the hostname with the hosting provider and prepares the certificate.
  3. The hostname is advertised as connected only after Tofu reads it back and sees the deployment that passed its health check; until then the row reports ownership_pending, dns_pending, certificate_pending, connected, disconnecting or error.

Each proven hostname is unique across all Tofu projects, and a project may claim at most ten; a connected domain does not replace the app's managed address. The Domains page also reads the DNS you are about to change and writes nothing.

Tofu connects a hostname; it does not sell registrations. Buying a domain inside Tofu is deliberately not enabled: the path exists in code with no enable flag and no merchant integration, so no card data is accepted.

Analytics

Every hosted app gets one first-party counter, and the numbers belong to the owner. The key is public — tfsk_ plus 32 hex characters — and you paste one line:

<script defer src="https://trytofu.ai/collect.js" data-site="tfsk_…"></script>

The key names the project and the origins allowed to report for it. There is no vendor script, and nothing about a visitor leaves Tofu's infrastructure: no cookie, no stored IP (a day-salted digest only when a salt is configured, otherwise unique visitors show as —).

Retention defaults to 90 days, is capped at 365 and is enforced by a daily prune; deleting a project deletes its key and its traffic. The collector rate-limits — a per-site-and-address brake at 600 requests a minute and one shared platform budget — and under-counts by design: one view per page load, crawlers counted like people, no export yet.

Limits, and what Tofu does not do

Tofu is in alpha: paid plans are rolling out to invited users, and there is no uptime guarantee or SLA.

  • Quotas are enforced before the work. Free is one prebuilt static site — a root index.html, up to 10 MB and 500 files — with 3 uploads and 3 deployments a day; a paid plan publishes up to five apps with 20 deployments a day, and uploads at the plan's stated allowance.
  • Not built yet. App auth, email and payments for hosted apps, notifications, the itemized deep-scan report, domain purchase, credential rotation, and analytics export.
  • Not done to your app. Tofu does not edit your code on its own (built-in repair proposes a change you review and apply), run your migrations, write DNS, or ask for hosting-provider credentials.
  • Leaving. Your code and data stay yours: cancel and delete your projects. The uploaded source archive stays in private storage until you ask us to erase it, and a managed database is kept for you, with its transfer arranged on request.

Everything above describes what is switched on today; a preview area holds nothing you can rely on yet.

Early release · some dashboard services remain previews