Skip to main content
Glama

Tests CodeQL npm: @run402/sdk npm: run402 npm: run402-mcp npm: @run402/functions License: MIT

Run402 is open-source backend infrastructure for AI agents and coding agents — a backend-as-a-service addressed to a machine rather than to a person. An autonomous agent provisions a Postgres database, user auth, file storage, serverless functions and site hosting, ships them through one staged deploy workflow, and pays for the usage itself. Comparable in surface to Supabase, Firebase or Vercel; different in that there is no dashboard you have to sign into to get started (the console exists for people; the agent never needs it) and no human-issued API key to copy.

This is the backend Kychee's open products run on. We needed a layer an agent can drive end to end, with room for whatever each app turns out to need, and nothing off the shelf had all of it, so we built it and opened it the same way we open the apps: this repo holds the agent surfaces (MIT), run402-core holds the open self-hostable runtime slice (Apache-2.0; the managed Cloud control plane remains proprietary, see CLOUD_VS_CORE.md), and kysigned is the first product running on it.

One call to run402 gives an agent a full Postgres database, REST API, user auth, content-addressed file storage, static site hosting, serverless functions, and image generation, paid with x402 (USDC on Base) or MPP (pathUSD on Tempo, or sats over Bitcoin Lightning) — or card-funded allowance. The prototype tier is free on testnet.

Run402 is agent-first because agents are first-class participants, not because people disappear. A person or agent acts through its own Run402 principal and authenticator, and its actions remain attributable. Identity answers who acted; memberships, roles, grants, grant keys, freshness, and spend policy determine what that principal may do.

An autonomous agent may remain the legitimate owner of the org-of-one it creates. People may join through explicit co-ownership. Agents entering somebody else's organization receive bounded authority instead of borrowing a human account. Different keys. Equal standing. Explicit authority.

Use the CLI by default to provision, deploy, inspect and recover. Use the typed, opinionated SDK when writing programmatic TypeScript/JavaScript workflows; shell scripts and CI can keep using CLI. MCP serves MCP-native hosts, and direct HTTP supports deliberate lower-level integrations.

This monorepo ships these interfaces:

Surface

Use when…

run402 CLI

Terminal, scripts, CI, agent-controlled shells: JSON in, JSON out, exit code on failure

@run402/sdk

Calling run402 from TypeScript: typed kernel, isomorphic (Node 22 / Deno / Bun / V8 isolates) with a Node entry that auto-loads the local keystore + wallet + x402 / Lightning fetch

run402-mcp

Claude Desktop, Cursor, Cline, Claude Code: core run402 operations as MCP tools

OpenClaw skill

OpenClaw agents (no MCP server required)

Run402 for Buzz

Buzz people and agents: install from run402.com, preflight/link one agent's dedicated identities, deploy a contextual demo, then offer human co-ownership through a normal HTTPS/passkey handoff; Buzz remains unchanged

@run402/functions

Imported inside deployed functions (db(req?), adminDb(), auth.user(), email, ai, assets) and for TypeScript autocomplete in your editor. Source lives in the public run402-core repo under packages/functions; run402 Cloud consumes the published npm package when it bundles function zips.

@run402/astro

Astro integration for SSR, ISR cache, hosted auth components, and image variants

These interfaces share a single typed kernel where appropriate: @run402/sdk. MCP tools, CLI subcommands, and OpenClaw scripts are thin shims over SDK calls. @run402/functions is the in-function helper that runs inside deployed code; the npm package on the registry is the artifact Cloud bundles. @run402/astro layers the SDK and functions runtime into Astro's build and SSR flow. The HTTP API is the foundation; the SDK owns shared client workflows and orchestration; CLI and MCP expose them in machine-friendly forms. Native SDK/MCP references explain intentional alternatives.

Deploy summaries share the SDK workflow view. CLI writes redacted detail under .run402/diagnostics/; MCP retains it through expand_result. Typed SDK callers keep the full result. Snapshot collection excludes platform runtime files automatically.

30-second start

First create the complete run402.json and index.html from Your first deploy. Run these commands in that application directory; --name requests a new project.

npm install -g run402@latest
run402 up --name my-app -y                           # bootstrap wallet/tier/project/link, then deploy manifest
run402 up verify                                     # rerun app HTTP verification without deploying
run402 up --verify                                   # deploy, then wait for gateway/edge coherence

That's a real Postgres database + a deployed static site, paid for autonomously with testnet USDC.

Buy from any x402 seller with the same wallet and a default $0.10 ceiling:

run402 pay https://seller.example/translate --method POST \
  --body '{"text":"hello"}' --max-usd 0.05 \
  --idempotency-key translation:1 --require-receipt

The SDK equivalent is r.pay.fetch(url, init, { maxUsdMicros, idempotencyKey, requireReceipt }); MCP callers run the same SDK call as a run snippet. All three return the same x402-commerce-result.v1 settlement, movement/replay, delivery, offer, merchant-receipt, signer-relationship, policy, and raw-evidence fields and pass unpriced URLs through with payment: null. Requiring a receipt rejects before payment when no wallet-rooted offer is eligible. If a promised receipt cannot be verified after settlement, PaymentPolicyError retains the upstream response and paid result and tells the caller to reconcile—never to pay again. For a trusted Run402 PAYMENT_INTENT_PENDING, all three surfaces prescribe one recovery path: wait for Retry-After, then repeat the same request with the same payer and key. Never replace the key. The SDK and MCP can also re-present an ambiguous proof while their process remains alive; custom/arbitrary sellers remain ambiguous and require reconciliation.

Prefer run402 up when a repo has run402.deploy.json or app.json. The CLI stays a thin shim over the Node SDK action runner (r.actions.run(...) / r.up(...)): it validates the manifest first, then recursively performs only the missing prerequisites. Project resolution is --project, .run402/project.json, manifest project_id, approved creation from --name; global active state never selects a deploy target. --name is project creation/link metadata only; it is not part of the deploy manifest and never renames an existing project. Use --check for local validation and --plan for gateway-reviewed intent before applying. Local validation covers every file the manifest references (migration sql_path/sql_file, function sources and files, site paths and dir() targets, assets.put sources): a missing one fails with MANIFEST_FILE_MISSING (details.missing[] of { field_path, path, kind }, one create_file next action per file) before any gateway call, in every mode and in run402 deploy. With no manifest in the working directory, UP_MANIFEST_REQUIRED looks one directory down and names what it found (details.nearby_manifests[], a read-only run_in_directory action for a single candidate, or one unranked select_application action for multiple apps); --manifest <path> to a missing file is a typed MANIFEST_NOT_FOUND.

If an app manifest defines verify.http[], run402 up verifies those URLs after deploy. Fresh run402 edge sentinel misses are reported as propagation_pending rather than permanent failures while the binding is still converging; tune that wait with --propagation-budget-s (default 120) or return immediately with --no-propagation-wait. run402 up verify reruns the same HTTP checks without uploading, deploying, creating projects, or mutating resources.

The CLI checks for newer run402 releases opportunistically and fail-open. Success stdout stays the command result; stale-version notices are advisory JSON on stderr, or cli.update_available NDJSON events in --json-stream. run402 doctor --refresh is the explicit live npm check and reports the install context plus the safest upgrade command for local, global, or ephemeral installs. run402 doctor answers { ok, blocking[], warnings[], checks[] }: ok is true exactly when blocking[] is empty, every check carries severity: "blocking" | "advisory" | "info", advisory findings (an unbound passkey, a stale CLI, vault gaps, a tier-less own org that can still reach another org's projects: TIER_MISSING_ON_OWN_ORG) land in warnings[] without changing ok or the exit code, and the tier check's status is a fixed vocabulary (ok | inactive | frozen | past_due | dormant | purged | missing | unknown | error, never a tier name; the name and raw lifecycle are in value.tier / value.lifecycle).

Typed deploy configs use the same commands. Executable configs are trusted local code, so v1 only runs them when passed explicitly:

run402 up --manifest run402.deploy.ts --check
run402 up --manifest run402.deploy.ts --plan
run402 up --manifest run402.deploy.ts --require-plan plan_...

--check and --print-spec are local-only (both verify that every referenced file exists). --plan asks the gateway for a reviewed plan with plan_id, plan_fingerprint, warnings, diff, and one next action. --require-plan reapplies only if the normalized spec and reviewed gateway events still match.

import { defineConfig, dir, nodeFunction, sqlFile } from "@run402/sdk/config";

export default defineConfig(({ env }) => ({
  project_id: env.required("RUN402_PROJECT_ID"),
  database: { migrations: [sqlFile("db/001_init.sql")] },
  site: { replace: dir("dist"), public_paths: { mode: "implicit" } },
  functions: { replace: { api: nodeFunction("dist/functions/api.js") } },
  secrets: { require: ["OPENAI_API_KEY"] },
}));

Helpers normalize to the same ReleaseSpec as JSON manifests. dir() walks deterministically and rejects unsafe files unless explicitly allowed, sqlFile() derives the migration id from the filename unless supplied, and nodeFunction() currently expects JavaScript output; point TypeScript functions at built .js files.

Related MCP server: Database MCP Server

The patterns

Paste-and-go assets: content-addressed URLs with SRI

Upload files with the CLI. Keep the returned AssetRef, including immutable identity and image variants, when saving references in application data. Do not reconstruct content hashes or variant URLs yourself.

run402 assets put ./logo.png ./app.js ./app.css --project prj_example

Use a manifest asset slice when these files must activate with a release. See the storage guide and native SDK AssetRef helpers for HTML emitters and programmatic composition. Binary files must remain bytes; never read them as UTF-8 before uploading.

Dark-by-default tables + the expose manifest

Tables you create are unreachable via /rest/v1/* until you declare them in a manifest. That closes the "agent created a table, forgot to set RLS, data leaked" footgun. A valid anon key against an existing but undeclared table gets a structured 403 TABLE_NOT_EXPOSED (never a bare Postgres 42501) whose next_actions say exactly that: expose_table (declare it and redeploy), edit_request (the expose endpoint), or use_function (keep it dark and read it from a function with adminDb()); it is not an RLS problem. The manifest is convergent: applying it twice is a no-op; items removed between applies have their policies, grants, triggers, and views dropped.

cat > manifest.json <<'EOF'
{
  "$schema": "https://run402.com/schemas/manifest.v1.json",
  "version": "1",
  "tables": [
    { "name": "items",  "expose": true,  "policy": "user_owns_rows",
      "owner_column": "user_id", "force_owner_on_insert": true },
    { "name": "audit",  "expose": false }
  ],
  "views": [
    { "name": "leaderboard", "base": "items", "select": ["user_id", "score"], "expose": true }
  ],
  "rpcs": [
    { "name": "compute_streak", "signature": "(user_id uuid)", "grant_to": ["authenticated"] }
  ]
}
EOF

run402 projects validate-expose <project_id> --file manifest.json
run402 projects apply-expose    <project_id> --file manifest.json
run402 projects get-expose   <project_id>

Built-in policies: user_owns_rows (rows where owner_column = auth.uid(); with force_owner_on_insert: true a BEFORE INSERT trigger sets it), public_read_authenticated_write (anyone reads, any authenticated user writes), public_read_write_UNRESTRICTED (fully open; requires i_understand_this_is_unrestricted: true), and custom (escape hatch: your own CREATE POLICY SQL).

Use run402 projects validate-expose for a non-mutating feedback loop before applying. Optional migration SQL is used only to check manifest references; it is not executed as a PostgreSQL dry run, and this does not validate deploy manifests.

Auth-as-SDLC: put the same JSON under database.expose in your v2 ReleaseSpec. The gateway validates it against your migration SQL during deploy and rejects mismatches with a structured errors array listing every violation.

Directory deploy (advanced primitive)

For a standalone static directory on an existing project:

run402 sites deploy-dir ./dist --project prj_example > result.json 2> events.log

Use run402 up for a complete application with a deploy manifest. The SDK owns file hashing, upload deduplication and release orchestration; the CLI renders progress and the result.

Same-origin web routes: static site + function ingress

Apply-v1 routes and static public paths are release resources: the release pointer activates after the required deploy stages in run402 deploy. Applied migrations and external side effects are not rolled back by changing that pointer. Release static asset paths such as events.html are distinct from browser-visible public static paths such as /events. Use site.public_paths for ordinary clean static URLs; keep routes for function ingress and exact, method-aware static aliases.

{
  "project_id": "prj_...",
  "site": {
    "replace": {
      "index.html": { "data": "<!doctype html><main id='app'></main><script>fetch('/api/hello')</script>" },
      "events.html": { "data": "<!doctype html><h1>Events</h1>" }
    },
    "public_paths": {
      "mode": "explicit",
      "replace": {
        "/events": { "asset": "events.html", "cache_class": "html" }
      }
    }
  },
  "functions": {
    "replace": {
      "api": {
        "runtime": "node22",
        "source": {
          "data": "export default async function handler(req) { const url = new URL(req.url); return Response.json({ ok: true, path: url.pathname }); }"
        }
      },
      "login": {
        "runtime": "node22",
        "source": { "data": "export default async function handler(req) { return Response.json({ ok: true }); }" }
      }
    }
  },
  "routes": {
    "replace": [
      { "pattern": "/api/*", "methods": ["GET", "POST", "OPTIONS"], "target": { "type": "function", "name": "api" } },
      { "pattern": "/login", "methods": ["POST"], "target": { "type": "function", "name": "login" } }
    ]
  }
}

site.public_paths.mode: "explicit" means only the complete public_paths.replace table is directly reachable as static URLs. In the example, /events serves the release asset events.html, while /events.html is not public unless separately declared. mode: "implicit" restores filename-derived public reachability and can widen access, so review gateway warnings before confirming it.

Omit routes or pass routes: null to carry forward base routes. Use routes: { "replace": [] } to clear the route table. Route entries are an ordered replace list, not a path-keyed map. Function targets use { "type": "function", "name": "<materialized function name>" }. Static route targets use exact patterns only, methods ["GET"] or ["GET","HEAD"], and { "pattern": "/events", "methods": ["GET","HEAD"], "target": { "type": "static", "file": "events.html" } } where file is a release static asset path, not a public path, URL, CAS hash, rewrite, or redirect. Use static route targets for method-aware aliases such as static GET /login plus function POST /login; in explicit public path mode the backing asset can stay private by filename. Direct /functions/v1/:name calls remain API-key protected; browser-routed paths are public same-origin ingress.

Function routes can charge a fixed tenant x402 price before the handler runs by adding pricing: { "mode": "always", "amount_usd_micros": 250000, "pay_to": "org_default_payout" } to the route entry. 250000 is $0.25 per matching action. The portable ReleaseSpec contract also accepts receipt: "on_fulfillment" on a priced function route; a compatible host then requires the function to return payment.fulfilled(response) before it authors a receipt. Run402-hosted advertising remains gated off until the standard delegated-signer carrier is available—receipt intent never silently downgrades. Omit networks for production mainnet only; include "testnet" explicitly for testnet acceptance. Static aliases cannot be priced, direct function invocation is not monetized, and service/admin keys do not bypass a priced browser route. The owning org must have a resolvable payout wallet: set it with run402 orgs payout-wallet <org_id> <wallet_address>. Conditional credit systems should expose one fixed-price route such as POST /api/credits, then keep the rest of the app behind unpriced routes and app-local authorization.

Matching is exact or final-prefix-wildcard only. /admin and /admin/ are exact trailing-slash equivalents; /admin/* matches children but not /admin, /admin/, /admin.css, or /administrator, so deploy both /admin and /admin/* for a routed section root. Query strings are ignored for matching and preserved in the handler's full public req.url. Exact routes beat prefix routes; longest prefix wins; method-compatible dynamic routes beat static assets. A POST /login route can coexist with static GET /login HTML. Unsafe method mismatch returns 405, and matched dynamic route failures fail closed instead of falling back to static files.

Routed functions use the Node 22 Fetch Request -> Response contract: export default async function handler(req) { ... }. req.method is the browser method, and req.url is the full public URL on managed subdomains, hosts, and verified custom domains. Derive OAuth callbacks from it, for example new URL("/admin/oauth/google/callback", new URL(req.url).origin). Append multiple cookies with headers.append("Set-Cookie", value); redirects, cookies, and query strings are preserved. On priced routes, import getRoutedPaymentContext from @run402/functions, read const paymentContext = getRoutedPaymentContext(req), and key app-side idempotency by paymentContext.paymentId. For a receipt-enabled route, return payment.fulfilled(response) only after the response represents completed delivery; the helper fails closed outside a settled, current, receipt-enabled routed invocation. The context helper reads gateway-confirmed x-run402-payment-* headers and returns null for unpriced or direct calls. The raw run402.routed_http.v1 envelope is internal; do not write route handlers against it.

Recipe: static home page + SPA shell. A SPA site ships index.html as the shell serving every unmatched route (match spa_fallback), so by default GET / serves the shell too. To serve a real static home page at / while keeping the shell for app routes, ship home.html at the site root alongside index.html and add an exact root static route alias: "routes": { "replace": [ { "pattern": "/", "target": { "type": "static", "file": "home.html" } } ] }. Route matching runs before all static resolution (including the implicit / -> index.html root mapping), and SPA-fallback derivation is independent of the route table, so GET / serves home.html (route_static_alias), unmatched app routes such as /dashboard still serve the shell (spa_fallback), and named static pages keep serving unchanged (static_exact). Expect two non-blocking plan lints: STATIC_ALIAS_SHADOWS_STATIC_PATH (warn: the alias overrides what / would otherwise serve; accurate and expected here) and STATIC_ALIAS_DUPLICATE_CANONICAL_URL (info: /home.html stays directly reachable in implicit public-path mode; add <link rel="canonical"> to home.html if duplicate-content SEO matters). Omitting routes on later deploys carries the alias forward; routes.replace is total, so a pipeline that sends it must include the alias every time. Verify with run402 deploy resolve --url https://<your-site>/ --method GET (or r.project(id).apply.resolve) and confirm match: "route_static_alias" with target_file: "home.html".

Avoid routing every static file, broad method lists by default, wildcard static route targets, leading-slash static files, directory shorthand, and one-static-route-target-per-page tables that exhaust route limits. Also watch wildcard function routes that shadow direct public static paths. Warning codes to handle include STATIC_ALIAS_SHADOWS_STATIC_PATH, STATIC_ALIAS_RELATIVE_ASSET_RISK, STATIC_ALIAS_DUPLICATE_CANONICAL_URL, STATIC_ALIAS_EXTENSIONLESS_NON_HTML, and STATIC_ALIAS_TABLE_NEAR_LIMIT; inspect active routes, static_public_paths, and resolve diagnostics to distinguish the route pattern from the backing asset_path.

Resolve public URLs with the CLI or its MCP/SDK equivalents:

run402 deploy resolve https://example.com/events --project prj_123 --method GET
run402 deploy resolve --url https://example.com/events?utm=x#hero --project prj_123 --method GET
run402 deploy resolve --host example.com --path /events --project prj_123 --method GET

r.project(id).apply.resolve({ url, method: "GET" }) (from MCP, a run snippet) returns would_serve, diagnostic_status, match, normalized request data, warnings, full resolution JSON, edge_propagation, and next steps. When returned, asset_path, reachability_authority, and direct explain which release asset backs the public URL and whether reachability came from implicit file-path mode, explicit site.public_paths, or a route-only static alias. Stable-host diagnostics may also include authorization_result, cas_object (sha256, exists, expected_size, actual_size), hostname-specific response_variant, route/static fields such as allow, route_pattern, target_type, target_name, and target_file, and edge_propagation (settled, propagating, or sync_pending). Known match literals are host_missing, manifest_missing, active_release_missing, unsupported_manifest_version, path_error, none, static_exact, static_index, spa_fallback, spa_fallback_missing, route_function, route_static_alias, and route_method_miss; preserve unknown future strings. Known authorization_result values include authorized, not_public, not_applicable, manifest_missing, target_missing, active_release_missing, unsupported_manifest_version, path_error, missing_cas_object, unfinalized_or_deleting_cas_object, size_mismatch, and unauthorized_cas_object. Known fallback_state values include active_release_missing, unsupported_manifest_version, and negative_cache_hit; preserve unknown future strings. result is the diagnostic body status, not the HTTP status of the SDK call, so host misses can still be successful CLI/MCP/SDK calls with would_serve: false. Do not treat resolve/diagnose as a fetch, cache purge, or cache-policy oracle; route method misses should inspect allow, CAS authorization/health failures should inspect or redeploy the affected static asset, and fresh host misses should inspect edge_propagation or rerun run402 up verify. Branch on structured JSON fields such as cache_class and preserve unknown cache classes.

Release observability exposes stable asset identity and public reachability. Inventories include release_generation, static_manifest_sha256, nullable static_manifest_metadata (file_count, total_bytes, cache_classes, cache_class_sources, spa_fallback), and static_public_paths[] when returned. site.paths lists release static assets; static_public_paths[] lists browser-visible public paths with public_path, asset_path, reachability_authority, direct, cache class, and content type. Plan and release diffs expose static_assets counters: unchanged/changed/added/removed, newly_uploaded_cas_bytes, reused_cas_bytes, deployment_copy_bytes_eliminated, legacy_immutable_warnings, previous_immutable_failures, and cas_authorization_failures.

Runtime route failure codes to branch on: ROUTE_MANIFEST_LOAD_FAILED (manifest/propagation), ROUTED_INVOKE_WORKER_SECRET_MISSING (custom-domain Worker secret), ROUTED_INVOKE_AUTH_FAILED (internal invoke signature), ROUTED_ROUTE_STALE (selected route failed release revalidation), ROUTE_METHOD_NOT_ALLOWED (method mismatch), PAYOUT_WALLET_REQUIRED / PAYOUT_WALLET_AMBIGUOUS / PAYOUT_WALLET_UNRESOLVED (priced-route payout setup), PAYMENT_PROOF_MISMATCH (stale or wrong x402 proof), and ROUTED_RESPONSE_TOO_LARGE (body over 6 MiB).

For repo-driven deploys, run402 does not need service keys or wallet files in GitHub secrets. Run a local link command once:

run402 ci link github --project prj_... --manifest run402.deploy.json
# Optional route authority for CI route declarations:
run402 ci link github --project prj_... --manifest run402.deploy.json --route-scope /admin --route-scope /api/*

That creates a deploy-scoped /ci/v1/* binding and writes a workflow that grants id-token: write, checks out the repo, and runs the existing deploy primitive:

permissions:
  contents: read
  id-token: write

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Deploy to run402
        run: npx --yes run402@3.7.5 deploy --manifest 'run402.deploy.json' --project 'prj_...'

CI deploys are intentionally narrow: site, functions, database, absent/current base, and route declarations only when the binding has covering --route-scope patterns. Without route scopes, CI cannot ship routes. Keep secrets, domains, subdomains, checks, non-current base, and broader trust changes in a local wallet-backed deploy. If the gateway returns CI_ROUTE_SCOPE_DENIED, re-link with exact scopes like /admin or final-wildcard scopes like /api/*, or deploy locally. Manage bindings with run402 ci list and run402 ci revoke.

In-function helpers: caller-context vs BYPASSRLS

Inside a deployed function, import from @run402/functions. Two distinct DB clients keep RLS clean:

import { db, adminDb, auth, email, ai } from "@run402/functions";

export default async (req: Request) => {
  const user = await auth.requireUser();

  // Caller-context: db() mints a 60s actor JWT so run402.current_user_id() resolves in RLS.
  // No .eq("user_id", user.id) needed: RLS already binds the visitor's rows; the redundant
  // filter is a deploy-fail (R402_AUTH_REDUNDANT_USER_FILTER) under @run402/functions v3.0+.
  const mine = await db().from("items").select("*");

  // BYPASSRLS: for platform-authored writes (audit logs, cron cleanup, webhook handlers).
  await adminDb().from("audit").insert({ event: "items_read", user_id: user.id });

  // Send mail from the configured default outbound mailbox.
  if (mine.length === 0) {
    await email.send({ to: user.email, subject: "Welcome", html: "<h1>hi</h1>" });
  }

  return Response.json(mine);
};

adminDb().sql(query, params?) runs raw parameterized SQL and always bypasses RLS. The current runtime returns the gateway envelope, including rows and row_count; read result.rows, not result[0]. Older helper typings incorrectly described a bare array. See the owning runtime reference and match local helper types to the runtime version used by your deploy.

@run402/functions is auto-bundled into deployed code; install it in your editor for full TypeScript autocomplete (also works at build time for static-site generation with RUN402_SERVICE_KEY + RUN402_PROJECT_ID set).

ai.generateImage({ prompt, aspect? }) is available inside deployed functions for live app flows such as generated avatars or OG images. It calls the project runtime image endpoint with RUN402_SERVICE_KEY, so deployed functions do not need wallets or x402 signing code. Aspects are square, landscape, and portrait; the result is { image, content_type, aspect } with base64 image bytes. Runtime image generation is billed, rate-limited, and spend-capped against the project organization; public routed functions should authenticate/rate-limit their users before calling it.

assets.put(key, source, opts?) uploads bytes from inside a deployed function through the same CAS-backed apply substrate as deploy-time assets. It uses RUN402_SERVICE_KEY, accepts a string, Uint8Array, or { content | bytes }, and returns an SDK-compatible AssetRef with mutable and immutable URLs.

Operating data from outside a function: use the CLI with an explicit project. For deliberate HTTP integrations, the native HTTP reference distinguishes administrative REST from caller-scoped REST. Never expose a service key to the browser.

run402 projects sql prj_example "SELECT count(*) FROM audit"

repos: your repository history, encrypted before it leaves the machine

run402 repos is a Git remote whose contents are encrypted on your own machine and stored as a chain of signed, admitted heads. It exists so your repository history outlives the machine it was written on — without that outliving requiring you to hand Run402 the plaintext. The wire protocol is r402s/v0. One noun, seventeen verbs: KyGit is the brand, a vault is the resource, a repo is what you have — run402 repos on the command line and r.repos in the SDK. Two pairs of the seventeen mint and claim a single-use bearer key: handoff/resume hand a checked-out working tree — dirty state included — from one agent to ANOTHER, the sender stopping; invite/join bring a SECOND agent into the SAME work while the first keeps going, sharing a coordination room. See "Handoff / resume" and "Invite / join" below.

Three claims, three different strengths. These are the entire approved claims vocabulary for this feature:

  1. Run402 cannot decrypt your vault or repository history. Deployment artifacts remain a disclosed plaintext custody boundary. Cryptographic, against Run402 itself: in the vault lane, source payload and repository-history content are ciphertext-only; the substrate retains only enumerated plaintext metadata and holds zero vault keys. The deploy lane is separate and disclosed — the platform custodially holds the plaintext artifacts of every deploy. It can read what you deployed; it cannot read what you did not.

  2. Activation requires vault admission by default; an explicit, audited override can bypass it. An operational platform invariant, enforced and auditable — not cryptographic against the platform that enforces it.

  3. Retention is an operational promise of the platform, not a cryptographic guarantee against it (the host controls timestamps and bytes).

The vault-only track — three lines, muscle memory intact, nothing to pay:

run402 init                                   # once per machine
run402 repos create my-notes                  # project + vault + origin remote, one free call
git push -u origin main                       # publishes, encrypted before it leaves the machine

origin is claimed additively: when the directory has no origin yet, the scaffold names ours origin — git push origin main just works, no side-remote name to remember. An existing origin is never touched; the fallback is run402 instead, and the response says which happened and why. An app that lives inside another repository (a monorepo workspace) is never scaffolded into that repository: the skip carries a create_nested_repo next action, and run402 repos create --nested --project <project_id> (or run402 up --nested) gives the app root its own nested repository and run402 remote, appending exactly one line to the enclosing repository's local .git/info/exclude and touching nothing else there; repos create prints a git push next action only for a remote it actually added. The free path is the whole path — no slug, no fee, no ceremony. When you want pretty run402::<org-slug>/<name> addresses (clone-by-name, push-to-create), claim an org slug once — the optional named-address upgrade described below.

Named addressing. run402::<org-slug>/<name> works alongside the id-form run402::<org_id>/<project_id> in the same slot — pick an org slug once (run402 orgs slug <slug>, owner-only, a small one-time fee), and every repo under it is run402::<slug>/<name>. Pushing a name that doesn't exist yet push-to-creates it: the project and vault are allocated atomically, and a losing concurrent pusher resolves cleanly to the winner's repo instead of erroring — its work is not lost, it just wasn't the creator. The first time a named remote resolves on a checkout, the resolved id is pinned into that checkout's local git config — every later push/fetch follows the pin directly, so a later rename of the org slug or repo name never breaks an existing clone. The id-form address needs no pin (a project id never changes) and stays the cold-restart path: an agent that lost its local state but still holds authority on the project can always fall back to run402::<org_id>/<project_id>.

One thing to know up front: a vault has a writer set, not a single key. Every admitted member or handoff recipient opens the vault and pushes under its OWN keystore key (run402 repos access lists them). A vault whose only admitted principal is this keystore is exactly as safe as this keystore — see "If you lose the keystore" below, or admit a second principal.

The explicit, ceremonial form still works, and allocates the SAME way git push does lazily on first use — useful for scripts, or for the receipt to land in JSON stdout instead of stderr:

# 1. Provision. Inside a repository that already exists, this adds the origin
#    remote (run402::<org_id>/<project_id>). Not a repository yet?
#    `run402 init --git-remote` creates one first. It needs a project selected
#    (`run402 projects use <project_id>`, or RUN402_PROJECT_ID).
run402 init

# 2. Allocate the repo's vault explicitly. Separate from `run402 init` on
#    purpose: this is the step that mints key material on this machine and
#    prints a one-shot recovery receipt. Idempotent — an existing vault comes
#    back deduplicated. (Skip this step and git push / repos capture
#    against an unallocated project allocates the SAME way, lazily, on
#    first use — the two paths don't stack; this one just does it now,
#    explicitly, so the receipt lands in JSON stdout instead of stderr.)
run402 repos create --project <project_id>

# 3. Snapshot — capture the working tree, encrypt it, publish a signed head.
run402 repos capture --message "wip: refactor the parser"
git push origin main            # ...or push your own branches, via git-remote-run402

# 4. View, then fsck: walk the head chain from your authenticated pin.
run402 repos view
run402 repos fsck --budget 500

# Restore anywhere, with plain git.
git clone run402::<org_id>/<project_id> restored

Cloning needs a Run402 principal on this machine — a wallet and a keystore holding an envelope for this vault — this is encrypted git, not a shareable link.

A fresh clone installs local refs/r402/retain/<oid> refs for every retained deploy-capture tip no branch reaches, so a plain git fsck is silent — git for-each-ref refs/r402/ lists what is retained. Clones made by a client older than this one (or a checkout whose ref write degraded) may still show dangling commits under git fsck; harmless, not corruption — one run402 repos fsck run installs the missing refs. A retained ref locally pins that history against git gc until the vault prunes the capture, at which point the next fetch retracts it.

repos capture is the CAPTURE lane — the protocol deploy ref plus the HEAD target — because a dirty tree captures as a synthetic commit that sits on no branch. Your own branches and tags reach the vault through git push origin <branch>.

Allocating a vault does NOT gate the project's deploys. vault_policy stays unset until you set it — a vault created by a first git push or repos create never silently changes how you deploy. A deploy against a vaulted, ungated project proceeds ungated and its result carries a typed next_actions entry offering run402 repos policy required; every later ungated deploy carries a warnings[] entry naming the drift, until the policy is set either way — never a block, never an interactive prompt. Once vault_policy is required, a deploy must present a vaulted capture at commit — run402 deploy produces one automatically on any machine holding the keystore; un-gate with run402 repos policy grandfathered --reason "<why>" (owner + step-up, audited, reversible with run402 repos policy required). Vaulting your source is never gated on a deploy, either way. run402 doctor reports the policy, whether this machine can satisfy it, and where the keystore lives.

Before capture reports that anything landed, the client compares every finalization receipt against its local expected manifest and reads the admitted head back from storage — a 200 alone is never enough. Maintenance is one verb, run402 repos gc — git gc's own two halves (checkpoint publication + prune planning), never described as "exactly git gc" since the deletion ceremony is stricter: it plans locally, and submits only when handed both verifier receipts — one from this CLI, one from the independent r402s-verify — with --submit --intent-core <path> --verifier-receipt <path>; only the control-plane-signed completion says what was deleted.

From the SDK, with identical semantics — vault reads run anywhere, and the verbs that touch a git working tree or the on-disk keystore are Node-only:

For typed repository automation, see the native SDK KyGit reference.

The encrypted second remote — the zero-migration pattern. Keep GitHub/GitLab as the primary (collaboration, CI, reviews, unchanged) and add KyGit as the second remote: git remote add vault run402::<org_id>/<project_id> + git push vault --all, and a complete, continuously updated copy of your history exists that the storage provider itself cannot read. The reason this matters, said plainly and as capability rather than accusation: a host that can READ private repositories can — under a future policy, an acquisition, a training pipeline, a subpoena, or a breach — index them, train models on them, or hand them to someone who will. Run402 cannot decrypt your vault or repository history. Deployment artifacts remain a disclosed plaintext custody boundary.

A vault-only project is first-class. run402 init (or run402 repos create <name>), then git push origin …, then gc / fsck / access, and never a deploy — a supported shape, not a degraded one. One consequence is worth stating plainly: a vault-only project has no deploy lane, so the disclosed plaintext custody boundary is empty and there is consequently no custodial restore path.

If you lose the keystore. The vault protects source history from host-side loss while a principal keystore survives. The "while" clause is load-bearing: in V0-A, whole-machine or whole-keystore loss is terminal for vault history until human envelopes ship, and run402 repos view prints that sentence verbatim. Back up the keystore directory run402 repos view reports as keystore.root and prints under the terminal-loss statement — ~/.config/run402/vault for the default wallet, ~/.config/run402/profiles/<wallet>/vault for a named one. The recovery receipt is an integrity anchor, not a decryption key — it proves the vault you are served is the one you created, and it decrypts nothing. It is not a secret; the more copies the better. The reminder gets louder as the vault gets more valuable: quiet at genesis, a STANDING run402 doctor warning once the vault crosses any of ≥10 generations / ≥10 MB / ≥14 days since genesis — cleared only by adding a second principal, never by an attestation, because V0 cannot verify one is true.

The exit ramp: mirror your own copy. run402 repos mirror <destination> [--profile <name> | --ambient] (S3 or a plain directory) configures a second, customer-owned copy of the vault's ciphertext — the destination and credential name live in a config file beside the keystore, never in run402.config.json, never a raw secret. Once set, every capture is mirrored to it automatically, reported as a separate mirror_push field beside the vault result; a mirror failure never blocks, slows, or changes the actual publish. run402 repos mirror --backfill (idempotent, resumable) catches it up on demand; run402 repos fsck --mirror is a KEYLESS integrity probe — it reports the recoverable generation without touching key material. run402 repos recover <source> --out <dir> needs no server at all: it reads the mirror, verifies the chain, and decrypts with the local keystore alone. Named recover rather than restore, which already means something else in git. Two things to know before you rely on it: it proves validity, never freshness — an older mirror looks identical to a genuinely short history — and a mirror without the keystore or an equivalent key recovers nothing, since mirroring ciphertext does not create a second key; the V0 terminal-loss statement above applies.

The human backup path (vault-recovery-custody). A human org member who completed source enrollment at console.run402.com/account holds an equivalent key with no keystore at all: their member key lives as sealed wrappers (passkey PRF and/or a source recovery code), and run402 repos recovery-bundle downloads the versioned recovery bundle (key identity + wrapper ciphertexts — still nothing the platform can open). Kept with a vault mirror — copy it to member-recovery-bundles/<name>.json under the mirrored prefix — that bundle + the source recovery code + the vault's recovery receipt recover the repository with no run402 server and no keystore: run402 repos recover <source> --out <dir> --receipt <pin.json> (the code is prompted with hidden input; --bundle <file> if the bundle isn't in the mirror). A raw passkey PRF output is deliberately NOT a recovery input — the no-server path for a human is the recovery code. run402 doctor's recovery_posture check tells you whether each vault-owning org actually has this backstop configured.

run402 repos mirror s3://acme-vault-mirror --profile acme --region us-east-1
run402 repos mirror --backfill
run402 repos recover s3://acme-vault-mirror --out ./restored --repo src_1a2b3c
run402 repos recovery-bundle --out ./bundle.json     # the member's no-keystore recovery half
run402 repos recover ./mirror-copy --out ./restored --receipt ./recovery-receipt.json --bundle ./bundle.json

Handoff / resume — pass a working tree to another agent, dirty state and all. run402 repos handoff captures the actual working tree — staged, unstaged, and untracked changes, exactly as git stash push -u would — and mints a single-use bearer key, kgh1_…, printed to stdout exactly once (--json still keeps it off stderr; there is no second place to find it if you lose it). Hand that key to another agent — another machine, another session, no shared keystore, no shared wallet — and run402 repos resume kgh1_… claims it, clones a fresh checkout, and reapplies the exact dirty state with git stash apply --index. The resuming agent also becomes a run402 wallet of its own on the way in: with no active tier, resume folds the same cold-start chain create does (wallet → faucet → one x402 prototype payment) before the claim; --no-init opts out, and the claim never waits on it. A Handoff Note rides alongside (a short JSON summary: what's done, what's in progress, what's failing, next steps) and renders as Markdown by default on resume. The key confers real authority — by default the sender's own org role — until it is claimed or its TTL (default 1h, --ttl <seconds>) expires; the mint response says so, and the CLI echoes the warning before printing the key. Sensitive untracked files (.env, *.pem, *.key, SSH/AWS/GPG directories, and 18 more patterns) are excluded from capture by default; opt one back in with --include-sensitive <glob>.

run402 repos handoff --note-file handoff.json     # captures the working tree, mints the key, prints it ALONE to stdout
run402 repos resume kgh1_…                         # on the other machine: redeem it, clone, restore, print the note

Neither verb has an MCP tool — handoff mints a bearer secret and resume mutates org membership, the same "mutating verbs are CLI-only" reasoning as create/delete above.

Writers, plural. A vault admits heads from a SET of writer keys, each a member's own keystore identity (protocol rev 47). resume makes the recipient a writer before it returns — git push works at once, under the recipient's own key, and the sender's environment can be deleted afterwards. Any member added with run402 orgs members add (developer or above) becomes a writer the same way: the adder's client admits the new key inline when it can, and REFUSES the add (VAULT_WRITER_NOT_ADMITTED, request_writer_sync) when it cannot, so no member is ever left able to read but not push. run402 repos view lists writers[] and pending_writers[]; run402 repos access sync admits pending keys on demand; removing a member rides the next epoch rotation and that key can never be re-added. Nobody's seed is ever copied: a writer is admitted by a live writer's signature or by a sender-signed handoff grant the recipient completes with its own key.

Invite / join — bring a second agent into the exact work, dirty tree included, and talk in a shared room. A Handoff passes the work on; an Invite grows the team. run402 repos invite captures the working tree exactly like handoff does — the inviter's own worktree, index, branch, refs, and access are all untouched, and it keeps pushing throughout — registers the inviter's own presence in a coordination room (the project's default room, or --room <key> for a named org room), mints a single-use bearer key, kgi1_…, printed to stdout exactly once, and posts ONE room message naming the checkpoint and the invite id (never the key). Minting requires an ACTIVE writer key, the same as handoff (INVITE_MINT_REQUIRES_WRITER names run402 repos access sync as the fix). Hand that key to another agent and run402 repos join kgi1_… pays its own way in — the joining agent folds the SAME cold-start chain resume does (wallet → faucet → one x402 prototype payment) before the redemption, so it arrives as a paid-up run402 wallet of its own — clones a fresh checkout, becomes a writer of the vault under its OWN key before the command returns (nothing is copied from the inviter), restores the exact dirty state, pins the invite's room locally, registers its own presence, posts ONE arrival message, and reports who invited it (name, labels, whether they're still live), who else is in the room, and the last few messages. Both agents push, interleaved, each signing under its own key. From there run402 messages wait is the agent's ear: it blocks until the other side speaks (or a bounded timeout elapses) using the gateway's held read, never errors on silence, and reports who is still live either way. The minted role defaults to developer and never exceeds the inviter's own; the Invite Note (same shape as the Handoff Note) rides alongside and renders as Markdown by default on join. Taking access back is run402 orgs members rm, which rotates the vault's epoch so the removed key can no longer push while every remaining agent keeps working.

run402 repos invite --note-file invite.json       # captures the working tree, mints the key, prints it ALONE to stdout
run402 repos join kgi1_…                           # on the other machine: pay in, redeem, become a writer, clone, restore
run402 messages wait                               # then: block until the other agent speaks (or the timeout elapses)

Like handoff/resume, neither invite nor join has an MCP tool — invite mints a bearer secret and join mutates org membership and writes a working tree, the same reasoning as create/delete/handoff/resume above.

Verify it without trusting our client. r402s-verify is an independent-lineage verifier for the same protocol — a separate language, separate authorship, and a separate primitive stack, deliberately sharing no implementation code with the SDK. That non-sharing is the point: a differential verifier that reuses the code it is checking verifies nothing. It lives on the r402s-verify branch of this repository with its own workflow, ships prebuilt release binaries, and also builds with cargo build --release. The full protocol specification and threat model it verifies against are published in docs/kygit/, and the frozen conformance vectors in test-vectors/r402s-v0/.

Cost. There is no separate repos price — a vault's bytes count against the organization-pooled vault quota (sourceBytes: prototype 1 GB, hobby 10 GB, team 50 GB), a separate pool from the storage your projects share, charged once per unique object with a 4 KiB per-object accounting floor and a 1 MiB per-vault minimum.

SDK: @run402/sdk

npm install @run402/sdk

Two entry points:

  • @run402/sdk: isomorphic. Bring your own CredentialsProvider (a session-token shim, a remote vault, anything that resolves project keys + auth headers). Works in Node 22, Deno, Bun, V8 isolates.

  • @run402/sdk/node: Node-only convenience. Reads local profile state plus the project-key credential cache (credentials/project-keys.v1.json) and signs x402 payments from one deterministic source: an explicit opaque paymentSigner, explicit walletPath, the supplied provider's readWallet(), or the default active-profile wallet. Auth and payer may intentionally differ; a selected payment source never falls back to an ambient wallet. r.paymentPayer() reports only safe public payer/source provenance. Also exposes sites.deployDir(...), fileSetFromDir(...), typed deploy-manifest helpers (loadDeployManifest, normalizeDeployManifest), and resolveRun402TargetProfile() for app build scripts that need the same Core/Cloud target the CLI uses.

import { run402 } from "@run402/sdk/node";

const r = run402();
// Prepare the complete first-deploy manifest and referenced app files.
const result = await r.up({ name: "my-app", manifest: "run402.json" }, { approval: "yes" });
console.log(result);

The SDK is organised into focused namespaces: actions (Node recursive action runner), pay (bounded arbitrary-URL x402 buyer), projects, snapshots, branches, archives, assets, cache, ci, sites, functions, jobs, secrets, subdomains, domains, email (+ webhooks), auth, apps, tier, billing, contracts, ai, wallets (the local wallet: status, create, export, faucet; plus the server label), service, admin, session (a person's sign-in session: run402 login loopback and --device seams plus the browser surface), writeApproval (the passkey write approval behind run402 approve), me (account overview and status), wallets (signed server-side wallet label), orgs (org-owned control plane + r.org(id) sub-client), grants (per-project capability grants), and identityLinks (public, protocol-discriminated human/agent Nostr attribution), plus const project = await r.project(id); await project.apply(spec) for staged multi-resource writes (release slices + assets slice via /apply/v1/*). Every operation throws a typed Run402Error subclass on failure: PaymentRequired, PaymentBuyerError, ProjectNotFound, Unauthorized, ApiError, NetworkError, LocalError, Run402DeployError. apply() automatically re-plans safe current-base BASE_RELEASE_CONFLICT races and emits apply.retry progress events. See sdk/README.md.

Humans and agents can publicly attribute separately held Buzz/Nostr identities to their Run402 principal. Agent links use the EOA-plus-kind-1 protocol; human links use a normal browser, fresh passkey, and released Buzz consent ceremony at https://console.run402.com/identity-links/connect. Both produce the same public idlnk_… resource shape with a discriminating proof_protocol. One principal may have several active Nostr subjects, while one active Nostr subject belongs to only one principal. This is attribution only: Nostr identities never authenticate, authorize, pay, deploy, or receive transfers. Run402 never accepts or derives from an nsec, Nostr private key, mnemonic, seed, passkey, session credential, or derivation path.

The human-facing install is a Buzz message—no terminal required:

Please install the run402.com skill.

That is the entire human instruction. In a managed Buzz context, first-party discovery routes it to run402-buzz; the agent reads the apex install router and installs the self-contained skill into its workspace (normally the user-home .buzz directory). The request means install and connect: after verifying the inert files, the agent loads the installed skill directly and continues through preflight, setup, and identity linking in the same turn. It does not stop at “available next turn” or ask a second setup question. For a Codex runtime, prefer supplying the working directory and environment separately to the agent's command runner:

working_directory: <user-home>/.buzz
environment: { "DO_NOT_TRACK": "1" }
command: npx --yes skills@latest add https://run402.com -s run402-buzz -a codex -y

Shell-only POSIX environments use:

cd "$HOME/.buzz"
DO_NOT_TRACK=1 npx --yes skills@latest add https://run402.com -s run402-buzz -a codex -y

Windows PowerShell uses:

Set-Location (Join-Path $HOME '.buzz')
$env:DO_NOT_TRACK = '1'
npx --yes skills@latest add https://run402.com -s run402-buzz -a codex -y

Claude Code uses -a claude-code, Goose uses -a goose, a confirmed .agents/skills consumer may use -a universal, and Claude Code plus Codex uses -a claude-code codex. universal is the shared path, not all runtimes; do not use the invalid explicit target -a claude. The skill bytes come from immutable digest-verified artifacts at run402.com; first-run npx can still require npm. GitHub is the one availability-only fallback, while any integrity failure stops before setup. Success reports the observed first-party digest and exact managed-workspace path; a GitHub source or global runtime path is never mislabeled first-party.

The file installation stage is inert. Continuing onboarding publishes a durable public kind-1 Nostr event and durable Run402 proof connecting the two public identities; revocation does not erase their history, and a Buzz-managed event may also expose its owner's public NIP-OA attestation. The agent initializes only if needed, creates or reuses the link, independently verifies it, and immediately offers one context-relevant quick test or demo with Deployment: none retained in the expanded receipt. On Windows the setup helper runs npm's and Run402's JavaScript entrypoints through the exact managed Node runtime with shell: false, avoiding .cmd process-boundary failures. It waits for explicit approval before building or deploying. After independently verifying the live app, it creates an inert durable offer and posts a normal HTTPS “Become an owner” handoff. The browser owns human login/passkey and the existing Buzz six-digit consent callback; no human terminal command or Buzz change is required.

See the buzz/ guide for prerequisites, the no-secret signer model, released-client fixtures, migration guidance, and the full workflow, or inspect the exact run402-buzz listing on skills.sh. The low-level CLI commands remain available for debugging, but they are not a competing onboarding path.

The community control plane keeps four concepts separate: installing the skill is inert shared capability; installing a community associates a Buzz relay community with a Run402 organization after dual consent; human adoption records a terminal consent receipt, creates the human's public Buzz identity link, and adds an ordinary owner membership without demoting the founder agent; agent enrollment gives each later agent principal only bounded, expiring grants to named existing projects. The completed receipt, public attribution, and membership remain independent: revoking the link does not remove org authority, and removing the membership does not revoke the link or rewrite the receipt. Buzz itself remains unchanged: approval uses already-shipped browser-fragment/kind-1 behavior plus released NIP-11/NIP-43 evidence, while Run402 owns offers, organizations, descriptor discovery, and lifecycle. run402 buzz status capability-detects older gateways; MCP only renders exact HTTPS/CLI next steps. See the Fizz/Honey workflow.

Astro SSR + ISR cache. For Astro apps, use @run402/astro 1.0+: export default run402(); in astro.config.mjs returns an AstroUserConfig composing the SSR adapter (Lambda + SnapStart + ISR cache + AsyncLocalStorage request-context), image integration, and build-time detectors. Functions opt into the SSR class via FunctionSpec.class: "ssr" in ReleaseSpec; the gateway provisions SnapStart and caches HTML responses keyed by (host, path, search, method, locale, release_id). Cache is bypass-by-default (no-store unless Cache-Control explicitly allows it AND no Set-Cookie AND no auth-taint flag from auth.* helpers / payment primitives). Invalidate from in-function code or out-of-band: r.cache.invalidate(url) / r.cache.invalidatePrefix({ host, prefix }) / r.cache.invalidateAll({ host }) (SDK), run402 cache invalidate <url> (CLI). Inspect cached state with r.cache.inspect(url) / run402 cache inspect <url>. Agent DX helpers also in the CLI: run402 doctor (5 health checks), run402 dev (Astro dev with .env.local), run402 logs --request-id req_... (correlate across functions). Full reference at astro/README.md and cli/llms-cli.txt (R402_* SSR Runtime Error Codes section).

CLI: run402

npm install -g run402@latest

Every subcommand prints JSON to stdout, JSON errors to stderr, exits 0 on success and 1 on failure: designed for an agent shell, not a human. Full reference: cli/llms-cli.txt (also at https://docs.run402.com/llms-cli.txt) — an index carrying the whole first-deploy contract plus a table of fetchable topic slices (/llms-cli-deploy.txt, /llms-cli-commands.txt, /llms-cli-functions.txt, …); /llms-cli-full.txt is the whole thing in one document.

run402 up --name my-app -y                # recursive SDK action runner: init/tier/project/link/deploy
run402 up verify                          # rerun app HTTP verification without a deploy
run402 up --nested -y                     # app root inside another repo: its own nested repo + encrypted remote
run402 doctor                             # { ok, blocking[], warnings[], checks[] }: ok means this agent can ship
run402 logs --request-id req_abc123       # every function in the project; app output only (--all for the raw stream)
run402 init                              # one-shot wallet + faucet + tier check
run402 pay https://seller.example/resource --max-usd 0.05 --require-receipt
run402 status                            # organization snapshot (wallet, rail, balances, tier, projects)
run402 projects provision --name my-app
run402 projects sql <project_id> "CREATE TABLE …"
run402 projects validate-expose <project_id> --file manifest.json
run402 projects apply-expose <project_id> --file manifest.json
run402 sites deploy-dir ./dist
run402 deploy verify op_... --project <project_id> --wait  # confirm gateway/edge release coherence
run402 deploy releases active --project <project_id>  # inspect current-live release inventory
run402 deploy resolve https://example.com/events --project <project_id> --method GET
run402 deploy --manifest app.json --json     # deploy only; rehearses automatically when a live release has migrations to protect
run402 snapshots list prj_...
run402 branches create prj_... --ttl-days 7 --json
run402 functions deploy <project_id> <name> --file fn.ts
run402 functions runs create <project_id> <name> --event-type reminder.send --idempotency-key reminder:123 --delay 10m
run402 ci link github --project <project_id>       # GitHub Actions OIDC deploy binding (--route-scope for CI routes)
run402 assets put ./asset.png --immutable
run402 assets diagnose <url>             # inspect live CDN state for a public URL
run402 cdn wait-fresh <url> --sha <hex>  # poll until a mutable URL serves the new SHA

up is the only compound CLI command: it calls the SDK action runner, emits steps[], and writes .run402/project.json when it needs to remember the workspace project. Against run402 Core it skips Cloud wallet/tier prerequisites and fails closed if no Core project is selected.

Rehearsal is automatic: a migration-bearing run402 up / run402 deploy against a project with a live release is rehearsed on a contained branch and committed only on a passing report (result.deploy.rehearsal); a first deploy has nothing to protect and commits directly (reason: "no_live_release"), and a redeploy whose migrations are all already applied with identical checksums is skipped as migrations_unchanged, so a page-only redeploy that still carries its migrations ships in seconds. --no-rehearse skips it. ADVANCED: run402 deploy rehearse [<plan_id>] [--manifest <path>] rehearses without committing — from a persisted plan, or from the manifest in the current directory (plan, upload, rehearse). Manual restore points live under run402 snapshots create|list|get|restore|delete; restore is a two-step plan/confirm flow. Branch projects live under run402 branches create|list|renew|delete, default to a 7-day TTL, use sandboxed email by default, and are marked noindex; a parent with no live release yields an empty branch.

Your HTML never needs a pasted key: every Run402 host serves /_run402/config.js (window.RUN402 = { project_id, api_base, anon_key }) for the project it resolves to. run402 up also names this principal when it has none — RUN402_AGENT_NAME if your runtime declares one (it overrides an existing name), else a detected client (claude-code, codex, cursor, grok; RUN402_CLIENT=<name> declares a client with no marker of its own, checked first); when nothing is known nothing is written — so promotion credit names you; set it any time with run402 whoami --set-name <name>. result.identity always reports detected (the client seen this run) and detection: { applied, reason }, where name_already_set means a client was detected but the principal already had a name. projects provision never touches git; up scaffolds a run402 remote on the app root only, and an app root inside another repository is skipped with a create_nested_repo next action unless you pass --nested, which makes it its own nested repository (one line appended to the enclosing repository's local .git/info/exclude, nothing else touched).

Portable archives export the supported run402 Core runtime slice of a Cloud project for local Core import. This is the no-lock-in trust path, separate from allowance/spend-cap financial-risk controls.

run402 archives create <project_id> --target cloud --scope portable-runtime-v1 --auth stubs --consistency pause-writes --wait --output ./project.r402ar --json
run402 archives verify ./project.r402ar --json
run402 archives import ./project.r402ar --target core --name imported-project --env-file ./required.env --json

--target names the deployment each verb talks to: create, status, and download export from Run402 Cloud (--target cloud, the default); import loads into a local Run402 Core (--target core, the default). Archive v1 excludes secret values, auth credentials, logs, billing/allowance state, Cloud operations metadata, Cloud import, and existing-project merge import. Verify is local/offline and checks integrity plus compatibility; archives remain untrusted input until Core import verifies and stages them.

The active project is sticky: run402 projects use <project_id> server-validates <project_id> and stores it as the default for subsequent <project_id>-taking subcommands, so most commands work without it. Local key material is managed separately under run402 credentials project-keys ...; that cache is never project inventory.

MCP server: run402-mcp

npx -y run402-mcp                        # standalone test

Eight tools, a few kilobytes of schema in a host's context: up and deploy for the first deploy, status, whoami, doctor, docs, run, and expand_result. Everything else is a run snippet against r, the Node SDK client, executed in a QuickJS-in-WebAssembly sandbox with no filesystem, process, or network of its own. Needs Node.js 22.13 or later. Local, so it can actually pay: an x402 payment needs a signing key, so a wallet-less remote server cannot make one.

Remote endpoint (no install)

A hosted streamable-HTTP MCP server runs at https://mcp.run402.com/mcp with free discovery tools only: run402_quickstart, x402_price_check (decode any URL's x402 challenge, unpaid), and experiment_scoreboard. It never handles funds — paid capabilities (image generation, deploys, payments) require the local server below, which holds your wallet. Registry entry com.run402/mcp lists both (packages[] npm + remotes[]). The remote itself runs as a run402 function — the platform hosting its own MCP server.

Stdio MCP transports must keep stdout reserved for JSON-RPC. Use the package bin (npx -y run402-mcp) or node dist/index.js from a built checkout. If a host insists on npm start, set npm_config_loglevel=silent; npm's lifecycle banner is stdout and otherwise appears as non-JSON prelude. The repo .npmrc and Docker image set this for source/container hosts.

Claude Desktop

Add to ~/Library/Application Support/Claude/claude_desktop_config.json:

{
  "mcpServers": {
    "run402": { "command": "npx", "args": ["-y", "run402-mcp"] }
  }
}

Cursor

Add to .cursor/mcp.json:

{
  "mcpServers": {
    "run402": { "command": "npx", "args": ["-y", "run402-mcp"] }
  }
}

Cline

Add to your Cline MCP settings:

{
  "mcpServers": {
    "run402": { "command": "npx", "args": ["-y", "run402-mcp"] }
  }
}

Claude Code

claude mcp add run402 -- npx -y run402-mcp

OpenClaw skill

cp -r openclaw ~/.openclaw/skills/run402
cd ~/.openclaw/skills/run402/scripts && npm install

Each script re-exports from cli/lib/*.mjs: the OpenClaw command surface is identical to the CLI command surface by construction. See openclaw/README.md.

MCP tools

Tool

What it does

up

The first deploy: any missing setup (wallet, tier, project, workspace link), then the deploy. Returns the run402.up.result envelope.

deploy

Applies a ReleaseSpec to a project (r.project(id).apply): database, functions, site, site.public_paths, assets, subdomains, routes.replace. Returns the DeployResult.

status

r.status(): the wallet the server acts as (local_label, server_label, address), tier and lease, allowance, projects, active project.

whoami

r.orgs.whoami(): the remote principal, its authenticators, org memberships, and sign-in session grade.

doctor

r.doctor(): { ok, blocking[], warnings[], checks[] }.

docs

The SDK reference and the run primer, shipped in the package: topic (a namespace or section) or search.

run

Runs a TypeScript snippet against the SDK in a sandbox; returns the value, the captured logs, and the SDK calls it made.

expand_result

Pages a stored result: a run value or its logs, a docs answer, up's detail.

A snippet is the body of an async function; the value of its last expression is the result:

{ "code": "const { projects } = await r.projects.list();\nprojects.filter((p) => !p.site_url).map((p) => p.id)" }

The result is { status, value, value_ref, shown, total, logs, logs_ref, calls, duration_ms, wallet, error? }: a large value is stored whole by item (an array's elements, a result's rows) and its leading whole items arrive in value_window, including in structuredContent (expand_result pages the rest by item), calls[] lists every SDK call with its outcome, and a timeout (60 s by default, 300 s at most) still lists the calls that completed. An SDK error passes through with its own code and next_actions.

Structured results. Every tool also returns its result as structuredContent under a declared outputSchema, so a host reads fields instead of parsing text. The object has status: "ok" | "error"; the fixed tools put the SDK object under result, and every error carries error.code, error.message, and error.next_actions. The fenced JSON in the text is the same object.

One-time secrets stay in the CLI. An operation that returns or consumes a one-time secret (minting or rotating a grant key, a Handoff or Invite Key, a Room Invite Key, provisioning a project or rotating its credentials, a project token, creating, importing, or exporting a wallet, the Lightning pairing) refuses inside run with SECRET_REQUIRES_CLI and one next action, { "type": "run_cli_command", "command": "run402 …" }, naming the exact command to hand the person. The refusal is in the SDK method itself, before any request, so nothing secret reaches a result.

Full reference: llms-mcp.txt.

Configuration

Variable

Default

Purpose

RUN402_API_BASE

https://api.run402.com

API base URL (override for staging)

RUN402_CONFIG_DIR

~/.config/run402

Local credential storage base directory (named wallets live under profiles/<name>/)

RUN402_WALLET

default

Active named wallet (profile). Overridden by --wallet <name> and per-directory .run402.json; RUN402_PROFILE is an alias. See run402 wallets.

RUN402_WALLET_PATH

{config_dir}/wallet.json

Custom wallet file path

RUN402_GRANT_KEY

(unset)

A grant-key bearer from run402 grants create --key. When set it is the only credential sent, so a process with no wallet can deploy.

Local state lives at:

  • profile state.json: active project pointer and profile state

  • profile credentials/project-keys.v1.json (0600): local anon/service key cache for explicit credential-required operations

  • ~/.config/run402/wallet.json (0600): wallet for x402 / MPP signing

Legacy projects.json files are one-way migration input only. anon_key and service_key have no expiry; lease enforcement happens server-side. Inspect cache state with run402 credentials project-keys status --project <project_id> and export secrets only with run402 credentials project-keys export --project <project_id> --reveal.

Development

npm run build           # builds core/, sdk/, then the MCP server
npm test                # SKILL + sync + unit tests
npm run test:e2e        # builds generated CLI SDK mirrors, then runs CLI end-to-end tests
npm run test:sync       # checks MCP/CLI/OpenClaw/SDK stay in sync
npm run test:skill      # validates SKILL.md frontmatter + body

Architecture: every tool / subcommand / skill script is a thin shim over an @run402/sdk call. core/ holds Node-only filesystem primitives (keystore, wallet, SIWE signing) wrapped by the SDK's Node provider. See CLAUDE.md for the full layout.

License

MIT for this repo (the agent surfaces: SDK, CLI, MCP server, Astro integration, OpenClaw skill). The full backend, run402-core, is Apache-2.0.

Available Tools

8 tools
deployA

Unified apply primitive. Accepts a structured ReleaseSpec — database (migrations + expose), value-free secrets.require/delete declarations, functions, site, site.public_paths, site.embedding (framing opt-in by catalog key, e.g. { frame_ancestors: ['localhost'] }; null = deny), subdomains, and routes.replace web routes — with explicit replace vs patch semantics per resource. Migration entries use id for immutable versioned SQL or name for generated/idempotent content-tracked SQL; name compiles client-side to _<sha256(sql)[0:16]>. Use site.public_paths for clean static URLs such as /events backed by release asset events.html; explicit mode does not expose /events.html unless separately declared, while mode: 'implicit' restores filename-derived reachability and can widen access. Route entries map exact/final-wildcard browser paths like /admin and /admin/* to Node 22 Fetch Request -> Response functions, or exact GET/HEAD method-aware static aliases such as /events to { type: 'static', file: 'events.html' }; intentional read-only GET/HEAD wildcard function routes may set acknowledge_readonly: true. Direct /functions/v1/:name remains API-key protected. Secret values are set first with r.secrets.set (a run snippet) or run402 secrets set, never placed in deploy specs. All bytes ride through CAS (no inline-body cap). Returns release_id, URLs, warnings, and a structured progress-event log. Stops before upload/commit on confirmation-required warnings unless reviewed codes are passed with allow_warning_codes or allow_warnings is true.

ParametersJSON Schema
NameRequiredDescriptionDefault
baseNoDiff base. Default `{ release: 'current' }`. Use `{ release: 'empty' }` for a fresh deploy that fails if a release already exists.
i18nNoRouted-locale-context release slice. Omit to carry forward from base release; pass null to clear the slice; pass { defaultLocale, locales, detect? } to replace. Drives the negotiated locale that the gateway surfaces to routed HTTP function invocations via x-run402-locale and x-run402-default-locale request headers (omitted entirely when the active release has no i18n slice). Static-route hits do NOT receive locale negotiation.
siteNo
assetsNov1.48 unified-apply assets slice. Asset writes promote inside the same activation transaction as functions/site/secrets so a release flips atomically.
routesNoApply-v1 web routes. Omit or pass null to carry forward base routes; pass { replace: [] } to clear routes; pass { replace: [{ pattern, methods?, target: { type: 'function', name } }] } for functions or exact GET/HEAD { target: { type: 'static', file } } entries for method-aware static route aliases. Prefer site.public_paths for ordinary clean static URLs.
secretsNo
databaseNo
functionsNo
project_idYesProject ID to deploy to (from provision).
subdomainsNoAt most one subdomain per project — multi-element `set` is rejected with SUBDOMAIN_MULTI_NOT_SUPPORTED.
allow_warningsNoContinue past plan warnings that require confirmation. Default false: the tool stops before upload/commit so an agent can set missing secrets or inspect warnings.
idempotency_keyNoOptional client idempotency key. Combined with the project id and gateway-computed manifest digest to deduplicate retries.
allow_warning_codesNoContinue past specific reviewed plan warning codes. Prefer this to allow_warnings when only one known warning class is intentional.

Output Schema

ParametersJSON Schema
NameRequiredDescription
errorNo
eventsNo
resultNo
statusYes
warningsNo

TDQS

A4/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure, and it does so thoroughly: it explains CAS transport, id vs name migration semantics, public_paths explicit vs implicit reachability, acknowledge_readonly, the warning-stop behavior, and the returned fields. This is unusually transparent for a deploy tool.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long and dense, but nearly every sentence carries substantive information, and the purpose is front-loaded. It could benefit from bullet structure or shorter sentences, but the length is largely justified by the tool's complexity and the absence of annotations.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity, the output schema, and the lack of annotations, the description is quite complete: it covers CAS, warning gates, route semantics, migration semantics, and return contents. Minor gaps remain—such as i18n, base diffing, assets sync, and subdomain constraints—but those are already documented in the schema.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 69%, and the description adds real meaning beyond the schema for several key parameters: migration id/name compilation, public_paths mode implications, route target types, secret handling, and warning acknowledgements. It does not cover every one of the 13 parameters, but the schema already documents many of those.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with 'Unified apply primitive' and clearly states what it does: accepts a structured ReleaseSpec covering database, secrets, functions, site, subdomains, and routes, with replace vs patch semantics. It is specific about the resource and action, but it does not explicitly distinguish itself from sibling tools such as 'up'.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives strong operational context—how migrations, public paths, routes, secrets, and warnings behave—and implies this is the unified tool for applying a ReleaseSpec. However, it never explicitly states when to use this tool versus alternatives like 'up', and it lacks when-not-to-use guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

docsA

The SDK reference for run snippets, from the copy shipped in this package, so it matches the SDK the snippet runs against. No arguments: the run primer, the r namespace table, and the topics. topic: one namespace or section (assets, project.apply, rooms, local-state) or sdk for all of it; search: sections containing every word. Long answers are a window plus a ref for expand_result. Any other operation is a run snippet against r, the Node SDK client; docs is its reference.

ParametersJSON Schema
NameRequiredDescriptionDefault
topicNo`index` (default: the run primer, the r namespace table, the topics), `sdk` (the whole SDK reference), or one section: a namespace such as `projects`, `assets`, `project.apply`, `rooms`, or a section slug from the index.
searchNoWords to look for; returns up to 8 SDK reference sections whose heading or body contain all of them.

Output Schema

ParametersJSON Schema
NameRequiredDescription
refNo
kindNo
errorNo
linesNo
shownNo
totalNo
statusYes

TDQS

A4.6/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the behavioral burden. It discloses the default output (run primer, namespace table, topics), how topic and search alter results, and that long answers come as 'a window plus a ref for expand_result.' It does not explicitly state that the tool has no side effects, but 'SDK reference' and the contrast with run make the read-only nature reasonably clear.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is dense but efficient, with no filler and the core purpose front-loaded. It is slightly run-on with semicolons, but each clause carries meaningful information about modes, scope, or relationships to sibling tools.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a read-only reference tool with two optional parameters and an output schema, this description covers the default behavior, all parameter modes, the SDK-version guarantee, and the relationship to run and expand_result. Nothing needed to invoke it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline is 3. The description adds value beyond the schema by giving concrete topic examples (assets, project.apply, rooms, local-state), clarifying search as 'sections containing every word,' and explaining the expand_result link for long answers. This is more than the schema alone provides.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The first sentence identifies docs as 'the SDK reference for run snippets,' and the final sentence explicitly contrasts it with run ('Any other operation is a run snippet... docs is its reference'). This gives a clear verb-adjacent purpose (reference/lookup) and distinguishes it from execution tools.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly divides labor: docs is for looking up SDK reference, run is for executing operations against r, and expand_result is the companion for long answers. It also spells out the invocation modes (no args, topic, search), so an agent knows exactly when and how to use this tool.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

doctorB

Local health and configuration diagnostics (r.doctor()): { ok, blocking[], warnings[], checks[] }, the same report as run402 doctor. ok is false only on a blocking finding. Any other operation is a run snippet against r, the Node SDK client; docs is its reference.

ParametersJSON Schema
NameRequiredDescriptionDefault
project_idNoTarget this project's vault check. Omitted: the repository's own remote, else the active project.

Output Schema

ParametersJSON Schema
NameRequiredDescription
errorNo
resultNo
statusYes

TDQS

B3.2/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description must disclose behavioral traits. It explicitly defines the return object and the meaning of 'ok', which is useful. However, it does not state whether the tool is read-only, has side effects, or requires specific permissions, leaving those gaps unfilled.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is reasonably compact but includes the extraneous sentence about other operations being run snippets, which is not specific to this tool and could confuse the agent. It is not front-loaded with the key purpose; the first sentence does state it, but the trailing context dilutes focus.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple diagnostic tool with a single optional parameter and an output schema, the description plus schema cover the essentials: purpose, output structure, and parameter semantics. It lacks any mention of error cases or environment assumptions, but these are not critical for basic invocation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% and the single parameter 'project_id' is thoroughly described in the schema. The main description adds no extra parameter context, so the baseline 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly identifies the tool as local health and configuration diagnostics and states the report format. It differentiates from 'run' and 'docs' via the SDK context, but does not explicitly contrast with the sibling 'status', leaving ambiguity about when to choose one over the other.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives a general context ('local health diagnostics') but provides no explicit when-to-use vs alternatives, no exclusions, and no prerequisites. The reference to other operations as 'run' hints at an SDK pattern but does not guide selection between this tool and siblings like 'status' or 'deploy'.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

expand_resultA

Fetch more of a result a previous tool showed you only a window of. Tools on this surface truncate the VIEW, never the DATA: when one prints a ref together with shown and total, the full result is held behind that ref and this is how you read the rest of it. Pass the ref plus offset and limit to page through it. Refs live in this server process only — they expire after 30 minutes and only the most recent handful are kept, so re-run the producing tool rather than storing a ref across sessions. A result that carried a secret is never retained and never has a ref, so nothing here can hand one back.

ParametersJSON Schema
NameRequiredDescriptionDefault
refYesThe opaque handle a tool printed alongside its bounded view (res_ followed by 16 hex). Refs are per-process and short-lived — do not store one across sessions.
limitNoHow many items to return, 100 by default and at most 1000. The window stays bounded even when expanded — page with offset rather than asking for everything at once.
offsetNoIndex of the first item to return. Defaults to 0. Page by adding the previous window's shown count.

Output Schema

ParametersJSON Schema
NameRequiredDescription
refNo
kindNo
errorNo
itemsNo
shownNo
totalNo
offsetNo
statusYes

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations present, the description carries the full disclosure burden. It explains that refs are process-local, expire after 30 minutes, only the most recent few are kept, and secret-carrying results are never retained or given refs. This is substantial behavioral context beyond a generic 'fetch' statement.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The core purpose is front-loaded in the first sentence, and each subsequent sentence earns its place by explaining ref lifecycle, paging behavior, or security guarantees. There is no filler or repetition.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given a fully documented schema and an output schema, the description supplies the missing operational context: where refs come from, how long they live, how to page through results, and which results cannot be accessed this way. An agent has everything needed to invoke the tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema already documents all three parameters at 100% coverage, including ref format, expiry, limit bounds, and offset paging. The description reinforces the interaction ('Pass the ref plus offset and limit to page through it') but adds little meaning beyond what the structured schema already provides.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a precise verb and resource: 'Fetch more of a result a previous tool showed you only a window of.' This clearly identifies the tool as the pagination entry point and differentiates it from the unrelated sibling tools like up, deploy, and status.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives an explicit triggering condition: when a tool prints a ref together with shown and total counts, this is how to read the rest. It also provides a when-not: do not store refs across sessions; re-run the producing tool instead. It even excludes secret-bearing results from having refs.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

runA

Run a TypeScript snippet against the SDK in a sandbox. r is the Node SDK client (@run402/sdk/node); call docs for its reference. The code is the body of an async function: await r chains, and the value of the last expression (or a return) is the result, e.g. (await r.projects.list()).projects.map((p) => p.id). No filesystem, process, fetch, timers, or imports; console is captured. Returns { status, value, value_ref, shown, total, logs, logs_ref, calls, duration_ms, wallet, error? }; a large value is stored whole and expand_result pages it. Operations that return or consume a one-time secret (grant keys, Handoff and Invite Keys, project credentials, wallet keys) refuse with SECRET_REQUIRES_CLI naming the exact CLI command to hand the person. Any other operation is a run snippet against r.

ParametersJSON Schema
NameRequiredDescriptionDefault
codeYesTypeScript or JavaScript, run as the body of an async function: top-level await works, and the value of an explicit return or of the last expression statement is the result. `r` is the Node SDK client (`@run402/sdk/node`): await any chain, e.g. `(await r.projects.list()).map(p => p.project_id)` or `await r.project("prj_…").functions.list()`. Types are stripped, not compiled: no enum, no parameter properties. No filesystem, process, fetch, timers, or imports; `console` is captured.
timeout_secondsNoDeadline for the whole run, 60 s by default and at most 300. An SDK call in flight at the deadline completes on its own; the result lists every call that completed.

Output Schema

ParametersJSON Schema
NameRequiredDescription
logsNo
callsNo
errorNo
shownNo
totalNo
valueNo
statusYes
walletNo
logs_refNo
value_refNo
value_kindNo
duration_msNo
logs_droppedNo
value_windowNo
calls_droppedNo

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations exist, so the description carries the full burden. It details sandbox restrictions (no filesystem, process, fetch, timers, imports), async function semantics, return shape, secret refusal, and large-value paging behavior.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with its purpose, then systematically covers code semantics, restrictions, return shape, and special secret handling. Every sentence adds information; there is no filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity, the description covers sandbox behavior, result format, error condition for secrets, and paging. With an output schema present, return values are sufficiently explained.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so baseline is 3. The description adds an example but mostly restates what the schema already documents for `code`; `timeout_seconds` is fully documented in the schema as well.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific action: 'Run a TypeScript snippet against the SDK in a sandbox.' It also distinguishes itself from siblings by referencing `docs` for SDK reference and `expand_result` for paging large values.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly says when to use the tool: 'Any other operation is a run snippet against r,' and gives an exclusion for one-time secret operations that require the CLI (SECRET_REQUIRES_CLI). Also directs users to `docs` for reference, providing clear alternatives.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

statusA

The organization's state as this server's wallet sees it (r.status()): the wallet's local_label, server_label, and address, the tier and its lease, the allowance, the projects, and the active project. Never key material. Any other operation is a run snippet against r, the Node SDK client; docs is its reference.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
errorNo
resultNo
statusYes

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description must bear the full burden. It discloses a critical behavioral trait: 'Never key material' – a security guarantee that agents can rely on. It also describes the perspective ('as this server's wallet sees it') and lists exact fields, but it does not explicitly state whether the operation is mutation-free or describe error conditions. Given the simplicity of a status call, this is adequate additional context over a bare name.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is compact and front-loaded: it states the purpose in the first sentence, lists included fields, adds a security note, and then gives routing guidance for other operations. Every sentence adds value, and nothing is redundant. The structure makes it easy for an agent to quickly grasp what the tool does and what it doesn't do.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple, parameterless tool with an output schema available, the description is complete. It details the content of the state, excludes key material, and explains how it fits into the broader server API (via run and docs). No crucial information an agent would need to call this tool correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool has zero parameters, and the input schema is an empty object. Per the rubric, the baseline for 0 parameters is 4. The description does not need to explain any parameters, and it adds no parameter-related information, which is appropriate. It does not compensate for anything because there is nothing to compensate for.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool returns the organization's state as the server's wallet sees it (r.status()), listing specific fields (local_label, server_label, address, tier, lease, allowance, projects, active project). It explicitly notes it never returns key material, distinguishing it from potential secret-retrieval operations. It also differentiates from siblings by stating any other operation is a 'run' snippet, so an agent knows this is the dedicated status call.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives a clear when-to-use signal: this tool is the status operation. It also provides an explicit exclusion: 'Any other operation is a run snippet', which routes agents to the run tool for other tasks. It references 'docs' for the SDK reference, but it does not compare directly with sibling tools like whoami or deploy, so it's clear but not exhaustive.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

upA

Plan or run the canonical app-aware run402 up workflow from a local path or repo URL: any missing setup (wallet, tier, project, workspace link), then the deploy. Delegates to the SDK and returns the shared up result envelope with graph steps, resources, diagnostics, and next_actions. deploy only deploys.

ParametersJSON Schema
NameRequiredDescriptionDefault
dirNoWorkspace directory to inspect when source is omitted.
yesNoApprove non-interactive prerequisite, spend, and local-write prompts.
nameNoProject/app instance name, for example kysigned2.
tierNoBootstrap tier if account readiness is needed.
sourceNoLocal app directory or public Git repository URL. Defaults to the current directory.
dry_runNoPlan only. No gateway mutation, build execution, release commit, local link write, or prune.
manifestNoExplicit manifest path. Defaults to run402.json, then advanced release-only manifests.
build_modeNoOverride app build mode.
project_idNoExisting project id to install into.
allow_pruneNoApprove destructive managed-resource prune steps.
no_rehearseNoSkip the automatic rehearsal. By default a migration-bearing deploy against a project with a live release is rehearsed on a contained branch and committed only on a passing report; a first deploy has nothing to protect and commits directly, and a plan whose migrations are all already applied with an identical checksum is not rehearsed either (result.deploy.rehearsal says which: no_live_release / migrations_unchanged / no_migrations).
display_nameNoDisplay name to set on this principal (promotion credit and room presence use it); overrides an existing name. Omitted: RUN402_AGENT_NAME when set (same effect); else, only when the principal has no name yet, the detected client name (claude-code, codex, cursor, or grok; RUN402_CLIENT=<name> declares one that is not auto-detected) is set and reported as identity.source "detected"; otherwise nothing is written (identity.source "undetected", room presence 'agent' for coordination only). A detected client that was not applied is still reported under identity.detected / identity.detection.
max_spend_usdNoMaximum spend up may approve for readiness steps.
idempotency_keyNoRoot idempotency key for resumable app-up graph mutations.
allow_shell_buildNoApprove shell-string build commands after review.

Output Schema

ParametersJSON Schema
NameRequiredDescription
errorNo
resultNo
statusYes

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the behavioral burden. It discloses plan-vs-run behavior, automatic setup steps (wallet, tier, project, workspace link), delegation to the SDK, and the shared result envelope. It does not spell out all side effects such as spend, local writes, or destructive prune, but these are surfaced through parameter descriptions, and the overall mutating/deploying nature is clear.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three sentences with no filler: the first states the action and scope, the second explains return contents, and the third draws a clear boundary against `deploy`. It is well front-loaded and every sentence earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 15-parameter tool with an output schema and complete parameter documentation, the description provides the essential overview, source input options, setup/deploy behavior, and return envelope. It does not cover every behavioral edge case, but those are adequately documented in the schema; only a small amount of extra context about plan-only invocation would make it fully complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, and the individual parameter descriptions are rich. The tool description adds only high-level contextual grouping like 'missing setup (wallet, tier, project, workspace link)', which maps to parameters but does not add detailed semantics beyond the schema. Baseline 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource: 'Plan or run the canonical app-aware run402 up workflow'. It also specifies the input source ('local path or repo URL'), what the workflow includes (missing setup + deploy), and differentiates from the sibling `deploy` by noting that 'deploy only deploys'.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It clearly frames `up` as the full setup-plus-deploy workflow and distinguishes it from `deploy` with 'deploy only deploys'. This gives an agent enough to choose between the two, though it does not explicitly mention when to prefer other siblings like `status` or `doctor`. The guidance is present but not exhaustive.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

whoamiA

The remote identity (r.orgs.whoami()): the control-plane principal this server's wallet resolves to, its active authenticator and linked identities, its org memberships, and the sign-in session grade (none for a wallet). For the local wallet use status. Any other operation is a run snippet against r, the Node SDK client; docs is its reference.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
errorNo
resultNo
statusYes

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are present, so the description carries the full burden. It discloses not just that the tool returns identity, but the specific elements of that identity, including the nuance that wallet sessions have no grade. It does not explicitly state that the operation is read-only or describe error cases, but that is lightly implied by the whoami concept.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two dense sentences carry substantial information without filler. The first sentence lists the output components, the second provides sibling routing and a note about the SDK. Some technical jargon ('control-plane principal', 'r.orgs.whoami()') makes it less instantly accessible, but each clause earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a no-parameter tool with an output schema, the description is nearly complete: it covers what is returned, how to distinguish this from the local wallet tool, and how it fits into the broader run/docs ecosystem. It could optionally explain 'session grade' in plainer language, but this is a minor gap given the output schema likely documents structure.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool has zero parameters, so the schema is trivially 100% covered and the baseline is 4. The description adds no parameter detail because none is needed; it focuses instead on output and context.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly defines the tool as returning the remote identity, enumerating its components: control-plane principal, active authenticator, linked identities, org memberships, and session grade. It distinguishes from siblings by explicitly noting that local wallet identity is handled by 'status' and that other operations belong to 'run'.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides explicit routing: 'For the local wallet use status' and 'Any other operation is a run snippet'. This tells an agent exactly when to use this tool versus alternatives and when to use run instead.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 203 tool updates
    • Removedaccept_project_transfer
    • Removedadd_org_member
    • Removedadmin_archive_project
    • Removedadmin_reactivate_project
    • Removedadmin_set_lease_perpetual
    • Removedai_moderate
    • Removedai_translate
    • Removedai_usage
    • Removedallowance_create
    • Removedallowance_export
    • Removedallowance_status
    • Removedapp_up
    • Removedapply_expose
    • Removedassets_get
    • Removedassets_ls
    • Removedassets_put
    • Removedassets_rm
    • Removedassets_sign
    • Removedauth_settings
    • Removedbilling_history
    • Removedbrowse_apps
    • Removedcancel_function_run
    • Removedcancel_project_transfer
    • Removedcheck_balance
    • Removedci_create_binding
    • Removedci_get_binding
    • Removedci_list_bindings
    • Removedci_revoke_binding
    • Removedclaim_project_transfer
    • Removedclaim_subdomain
    • Removedcontract_call
    • Removedcontract_deploy
    • Removedcontract_read
    • Removedcreate_auth_user
    • Removedcreate_checkout
    • Removedcreate_email_organization
    • Removedcreate_function_run
    • Removedcreate_mailbox
    • Removedcreate_notification_rule
    • Removedcreate_org
    • Removedcreate_project_branch
    • Removedcreate_project_grant
    • Removedcreate_project_snapshot
    • Removeddelete_function
    • Removeddelete_mailbox
    • Removeddelete_mailbox_webhook
    • Removeddelete_notification_rule
    • Removeddelete_passkey
    • Removeddelete_project
    • Removeddelete_project_branch
    • Removeddelete_project_snapshot
    • Removeddelete_secret
    • Removeddelete_signer
    • Removeddelete_subdomain
    • Removeddelete_version
    • Removeddemote_user
    • Changeddeploy5 fields changed
      • removedInput schema / properties / database / properties / migrations / items / properties / sql_ref / properties / contentType
        Removed value: -{
        -  "type": "string"
        -}
      • addedInput schema / properties / database / properties / migrations / items / properties / sql_ref / properties / content_type
        Added value: +{
        +  "type": "string"
        +}
      • changedInput schema / properties / functions / properties / replace / additionalProperties / properties / source / anyOf
        Previous value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "properties": {
        -      "contentType": {
        -        "description": "MIME type override. Auto-detected from the path's extension when omitted.",
        -        "type": "string"
        -      },
        -      "data": {
        -        "type": "string"
        -      },
        -      "encoding": {
        -        "enum": [
        -          "utf-8",
        -          "base64"
        -        ],
        -        "type": "string"
        -      }
        -    },
        -    "required": [
        -      "data"
        -    ],
        -    "type": "object"
        -  }
        -]New value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "properties": {
        +      "content_type": {
        +        "description": "MIME type override (snake_case, like every wire field). Auto-detected from the path's extension when omitted.",
        +        "type": "string"
        +      },
        +      "data": {
        +        "type": "string"
        +      },
        +      "encoding": {
        +        "enum": [
        +          "utf-8",
        +          "base64"
        +        ],
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "data"
        +    ],
        +    "type": "object"
        +  }
        +]
      • changedInput schema / properties / site / anyOf
        Previous value: -[
        -  {
        -    "additionalProperties": false,
        -    "properties": {
        -      "public_paths": {
        -        "anyOf": [
        -          {
        -            "additionalProperties": false,
        -            "properties": {
        -              "mode": {
        -                "const": "implicit",
        -                "description": "Restore filename-derived public reachability for static assets.",
        -                "type": "string"
        -              }
        -            },
        -            "required": [
        -              "mode"
        -            ],
        -            "type": "object"
        -          },
        -          {
        -            "additionalProperties": false,
        -            "properties": {
        -              "mode": {
        -                "const": "explicit",
        -                "description": "Use only the complete replace table as direct public static URLs.",
        -                "type": "string"
        -              },
        -              "replace": {
        -                "additionalProperties": {
        -                  "additionalProperties": false,
        -                  "properties": {
        -                    "asset": {
        -                      "description": "Release static asset path, such as events.html. This is not a public URL.",
        -                      "type": "string"
        -                    },
        -                    "cache_class": {
        -                      "description": "Optional static cache class, for example html, immutable_versioned, or revalidating_asset.",
        -                      "type": "string"
        -                    }
        -                  },
        -                  "required": [
        -                    "asset"
        -                  ],
        -                  "type": "object"
        -                },
        -                "description": "Complete map from public browser paths such as /events to release static asset paths such as events.html.",
        -                "type": "object"
        -              }
        -            },
        -            "required": [
        -              "mode",
        -              "replace"
        -            ],
        -            "type": "object"
        -          }
        -        ]
        -      },
        -      "replace": {
        -        "$ref": "#/properties/functions/properties/replace/additionalProperties/properties/files"
        -      }
        -    },
        -    "required": [
        -      "replace"
        -    ],
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "properties": {
        -      "patch": {
        -        "additionalProperties": false,
        -        "properties": {
        -          "delete": {
        -            "items": {
        -              "type": "string"
        -            },
        -            "type": "array"
        -          },
        -          "put": {
        -            "$ref": "#/properties/functions/properties/replace/additionalProperties/properties/files"
        -          }
        -        },
        -        "type": "object"
        -      },
        -      "public_paths": {
        -        "$ref": "#/properties/site/anyOf/0/properties/public_paths"
        -      }
        -    },
        -    "required": [
        -      "patch"
        -    ],
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "properties": {
        -      "public_paths": {
        -        "$ref": "#/properties/site/anyOf/0/properties/public_paths"
        -      }
        -    },
        -    "required": [
        -      "public_paths"
        -    ],
        -    "type": "object"
        -  }
        -]New value: +[
        +  {
        +    "additionalProperties": false,
        +    "properties": {
        +      "embedding": {
        +        "anyOf": [
        +          {
        +            "additionalProperties": false,
        +            "properties": {
        +              "frame_ancestors": {
        +                "description": "Platform embedding catalog KEYS (never raw origins). `localhost` expands to http://localhost:* and http://127.0.0.1:*; the gateway rejects unknown keys with INVALID_SPEC naming the valid ones.",
        +                "items": {
        +                  "type": "string"
        +                },
        +                "minItems": 1,
        +                "type": "array"
        +              }
        +            },
        +            "required": [
        +              "frame_ancestors"
        +            ],
        +            "type": "object"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "description": "tenant-site-embedding: who may put the site in an iframe. The gateway then sends `Content-Security-Policy: frame-ancestors <expanded origins>` and drops X-Frame-Options on every response of the host. Omit to carry the previous release's declaration forward; null returns to the default deny."
        +      },
        +      "public_paths": {
        +        "anyOf": [
        +          {
        +            "additionalProperties": false,
        +            "properties": {
        +              "mode": {
        +                "const": "implicit",
        +                "description": "Restore filename-derived public reachability for static assets.",
        +                "type": "string"
        +              }
        +            },
        +            "required": [
        +              "mode"
        +            ],
        +            "type": "object"
        +          },
        +          {
        +            "additionalProperties": false,
        +            "properties": {
        +              "mode": {
        +                "const": "explicit",
        +                "description": "Use only the complete replace table as direct public static URLs.",
        +                "type": "string"
        +              },
        +              "replace": {
        +                "additionalProperties": {
        +                  "additionalProperties": false,
        +                  "properties": {
        +                    "asset": {
        +                      "description": "Release static asset path, such as events.html. This is not a public URL.",
        +                      "type": "string"
        +                    },
        +                    "cache_class": {
        +                      "description": "Optional static cache class, for example html, immutable_versioned, or revalidating_asset.",
        +                      "type": "string"
        +                    }
        +                  },
        +                  "required": [
        +                    "asset"
        +                  ],
        +                  "type": "object"
        +                },
        +                "description": "Complete map from public browser paths such as /events to release static asset paths such as events.html.",
        +                "type": "object"
        +              }
        +            },
        +            "required": [
        +              "mode",
        +              "replace"
        +            ],
        +            "type": "object"
        +          }
        +        ]
        +      },
        +      "replace": {
        +        "$ref": "#/properties/functions/properties/replace/additionalProperties/properties/files"
        +      }
        +    },
        +    "required": [
        +      "replace"
        +    ],
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "properties": {
        +      "embedding": {
        +        "$ref": "#/properties/site/anyOf/0/properties/embedding",
        +        "description": "tenant-site-embedding: who may put the site in an iframe. The gateway then sends `Content-Security-Policy: frame-ancestors <expanded origins>` and drops X-Frame-Options on every response of the host. Omit to carry the previous release's declaration forward; null returns to the default deny."
        +      },
        +      "patch": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "delete": {
        +            "items": {
        +              "type": "string"
        +            },
        +            "type": "array"
        +          },
        +          "put": {
        +            "$ref": "#/properties/functions/properties/replace/additionalProperties/properties/files"
        +          }
        +        },
        +        "type": "object"
        +      },
        +      "public_paths": {
        +        "$ref": "#/properties/site/anyOf/0/properties/public_paths"
        +      }
        +    },
        +    "required": [
        +      "patch"
        +    ],
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "properties": {
        +      "embedding": {
        +        "$ref": "#/properties/site/anyOf/0/properties/embedding",
        +        "description": "tenant-site-embedding: who may put the site in an iframe. The gateway then sends `Content-Security-Policy: frame-ancestors <expanded origins>` and drops X-Frame-Options on every response of the host. Omit to carry the previous release's declaration forward; null returns to the default deny."
        +      },
        +      "public_paths": {
        +        "$ref": "#/properties/site/anyOf/0/properties/public_paths"
        +      }
        +    },
        +    "required": [
        +      "public_paths"
        +    ],
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "properties": {
        +      "embedding": {
        +        "$ref": "#/properties/site/anyOf/0/properties/embedding"
        +      }
        +    },
        +    "required": [
        +      "embedding"
        +    ],
        +    "type": "object"
        +  }
        +]
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "http://json-schema.org/draft-07/schema#",
        +  "additionalProperties": true,
        +  "properties": {
        +    "error": {
        +      "additionalProperties": true,
        +      "properties": {
        +        "category": {
        +          "type": "string"
        +        },
        +        "code": {
        +          "type": "string"
        +        },
        +        "column": {
        +          "type": "integer"
        +        },
        +        "details": {},
        +        "fix": {},
        +        "http_status": {
        +          "type": "integer"
        +        },
        +        "line": {
        +          "type": "integer"
        +        },
        +        "logs": {
        +          "items": {
        +            "type": "string"
        +          },
        +          "type": "array"
        +        },
        +        "message": {
        +          "type": "string"
        +        },
        +        "mutation_state": {
        +          "type": "string"
        +        },
        +        "next_actions": {
        +          "items": {
        +            "additionalProperties": true,
        +            "properties": {
        +              "type": {
        +                "type": "string"
        +              }
        +            },
        +            "type": "object"
        +          },
        +          "type": "array"
        +        },
        +        "operation_id": {
        +          "type": "string"
        +        },
        +        "phase": {
        +          "type": "string"
        +        },
        +        "plan_id": {
        +          "type": "string"
        +        },
        +        "resource": {
        +          "type": "string"
        +        },
        +        "retryable": {
        +          "type": "boolean"
        +        },
        +        "safe_to_retry": {
        +          "type": "boolean"
        +        },
        +        "trace_id": {
        +          "type": "string"
        +        }
        +      },
        +      "required": [
        +        "code",
        +        "message",
        +        "next_actions"
        +      ],
        +      "type": "object"
        +    },
        +    "events": {
        +      "items": {
        +        "additionalProperties": true,
        +        "properties": {
        +          "type": {
        +            "type": "string"
        +          }
        +        },
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "result": {
        +      "additionalProperties": true,
        +      "properties": {
        +        "next_actions": {
        +          "items": {
        +            "$ref": "#/properties/error/properties/next_actions/items"
        +          },
        +          "type": "array"
        +        },
        +        "operation_id": {
        +          "type": "string"
        +        },
        +        "release_id": {
        +          "type": "string"
        +        },
        +        "urls": {
        +          "additionalProperties": {},
        +          "type": "object"
        +        },
        +        "warnings": {
        +          "items": {
        +            "additionalProperties": true,
        +            "properties": {
        +              "code": {
        +                "type": "string"
        +              }
        +            },
        +            "type": "object"
        +          },
        +          "type": "array"
        +        }
        +      },
        +      "required": [
        +        "release_id",
        +        "operation_id",
        +        "urls"
        +      ],
        +      "type": "object"
        +    },
        +    "status": {
        +      "enum": [
        +        "ok",
        +        "error"
        +      ],
        +      "type": "string"
        +    },
        +    "warnings": {
        +      "items": {
        +        "$ref": "#/properties/result/properties/warnings/items"
        +      },
        +      "type": "array"
        +    }
        +  },
        +  "required": [
        +    "status"
        +  ],
        +  "type": "object"
        +}
    • Removeddeploy_diagnose_url
    • Removeddeploy_events
    • Removeddeploy_function
    • Removeddeploy_list
    • Removeddeploy_rehearse
    • Removeddeploy_release_active
    • Removeddeploy_release_diff
    • Removeddeploy_release_get
    • Removeddeploy_resume
    • Removeddeploy_site
    • Removeddeploy_site_dir
    • Removeddeploy_verify_edge
    • Removeddiagnose_public_url
    • Addeddocs
    • Addeddoctor
    • Removeddomains_activate
    • Removeddomains_apply
    • Removeddomains_check
    • Removeddomains_disconnect
    • Removeddomains_ensure
    • Removeddomains_get
    • Removeddomains_list
    • Removeddomains_repair
    • Removeddomains_test_receive
    • Removeddrain_signer
    • Removederrors_list
    • Addedexpand_result
    • Removedexport_project_archive
    • Removedfork_app
    • Removedfunctions_rebuild
    • Removedgenerate_image
    • Removedget_agent_contact_status
    • Removedget_app
    • Removedget_contract_call_status
    • Removedget_email
    • Removedget_email_raw
    • Removedget_expose
    • Removedget_function_logs
    • Removedget_function_run
    • Removedget_function_run_logs
    • Removedget_mailbox
    • Removedget_mailbox_webhook
    • Removedget_notification_preferences
    • Removedget_operator_status
    • Removedget_org
    • Removedget_project_snapshot
    • Removedget_quote
    • Removedget_schema
    • Removedget_signer
    • Removedget_usage
    • Removedimport_project_archive
    • Removedinit
    • Removedinitiate_project_transfer
    • Removedinspect_project_archive
    • Removedinvite_auth_user
    • Removedinvoke_function
    • Removedjobs_cancel
    • Removedjobs_download_artifact
    • Removedjobs_get
    • Removedjobs_logs
    • Removedjobs_purge
    • Removedjobs_submit
    • Removedlink_wallet_to_organization
    • Removedlist_emails
    • Removedlist_function_runs
    • Removedlist_functions
    • Removedlist_incoming_transfers
    • Removedlist_mailbox_webhook_deliveries
    • Removedlist_mailbox_webhooks
    • Removedlist_mailboxes
    • Removedlist_notification_channels
    • Removedlist_notification_rules
    • Removedlist_notifications
    • Removedlist_org_members
    • Removedlist_orgs
    • Removedlist_outgoing_transfers
    • Removedlist_passkeys
    • Removedlist_project_branches
    • Removedlist_project_events
    • Removedlist_project_snapshots
    • Removedlist_projects
    • Removedlist_secrets
    • Removedlist_signers
    • Removedlist_subdomains
    • Removedlist_tenant_payments
    • Removedlist_versions
    • Removedpasskey_login_options
    • Removedpasskey_login_verify
    • Removedpasskey_register_options
    • Removedpasskey_register_verify
    • Removedpay_url
    • Removedpreview_project_transfer
    • Removedproject_get
    • Removedproject_key_cache_export
    • Removedproject_key_cache_status
    • Removedproject_use
    • Removedpromote_user
    • Removedprovision_postgres_project
    • Removedprovision_signer
    • Removedpublish_app
    • Removedredrive_function_run
    • Removedredrive_mailbox_webhook_delivery
    • Removedregister_mailbox_webhook
    • Removedremove_org_member
    • Removedrename_org
    • Removedrename_project
    • Removedrenew_project_branch
    • Removedrequest_faucet
    • Removedrequest_magic_link
    • Removedrest_query
    • Removedrestore_project_snapshot
    • Removedrevoke_project_grant
    • Removedrotate_webhook_secret
    • Addedrun
    • Removedrun_sql
    • Removedscaffold_roles
    • Removedsend_email
    • Removedsend_message
    • Removedservice_health
    • Removedservice_status
    • Removedset_agent_contact
    • Removedset_auto_recharge
    • Removedset_low_balance_alert
    • Removedset_mailbox_defaults
    • Removedset_notification_preferences
    • Removedset_org_member_role
    • Removedset_org_payout_wallet
    • Removedset_recovery_address
    • Removedset_secret
    • Removedset_tier
    • Removedset_user_password
    • Removedstart_operator_passkey_enrollment
    • Changedstatus2 fields changed
      • addedInput schema / additionalProperties
        Added value: +false
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "http://json-schema.org/draft-07/schema#",
        +  "additionalProperties": true,
        +  "properties": {
        +    "error": {
        +      "additionalProperties": true,
        +      "properties": {
        +        "category": {
        +          "type": "string"
        +        },
        +        "code": {
        +          "type": "string"
        +        },
        +        "column": {
        +          "type": "integer"
        +        },
        +        "details": {},
        +        "fix": {},
        +        "http_status": {
        +          "type": "integer"
        +        },
        +        "line": {
        +          "type": "integer"
        +        },
        +        "logs": {
        +          "items": {
        +            "type": "string"
        +          },
        +          "type": "array"
        +        },
        +        "message": {
        +          "type": "string"
        +        },
        +        "mutation_state": {
        +          "type": "string"
        +        },
        +        "next_actions": {
        +          "items": {
        +            "additionalProperties": true,
        +            "properties": {
        +              "type": {
        +                "type": "string"
        +              }
        +            },
        +            "type": "object"
        +          },
        +          "type": "array"
        +        },
        +        "operation_id": {
        +          "type": "string"
        +        },
        +        "phase": {
        +          "type": "string"
        +        },
        +        "plan_id": {
        +          "type": "string"
        +        },
        +        "resource": {
        +          "type": "string"
        +        },
        +        "retryable": {
        +          "type": "boolean"
        +        },
        +        "safe_to_retry": {
        +          "type": "boolean"
        +        },
        +        "trace_id": {
        +          "type": "string"
        +        }
        +      },
        +      "required": [
        +        "code",
        +        "message",
        +        "next_actions"
        +      ],
        +      "type": "object"
        +    },
        +    "result": {
        +      "additionalProperties": true,
        +      "properties": {
        +        "active_project": {
        +          "type": [
        +            "string",
        +            "null"
        +          ]
        +        },
        +        "projects": {
        +          "items": {
        +            "additionalProperties": true,
        +            "properties": {
        +              "project_id": {
        +                "type": "string"
        +              }
        +            },
        +            "required": [
        +              "project_id"
        +            ],
        +            "type": "object"
        +          },
        +          "type": "array"
        +        },
        +        "tier": {
        +          "anyOf": [
        +            {
        +              "additionalProperties": true,
        +              "properties": {
        +                "expires": {
        +                  "type": [
        +                    "string",
        +                    "null"
        +                  ]
        +                },
        +                "name": {
        +                  "type": "string"
        +                },
        +                "status": {
        +                  "type": "string"
        +                }
        +              },
        +              "required": [
        +                "name",
        +                "status",
        +                "expires"
        +              ],
        +              "type": "object"
        +            },
        +            {
        +              "type": "null"
        +            }
        +          ]
        +        },
        +        "wallet": {
        +          "anyOf": [
        +            {
        +              "additionalProperties": true,
        +              "properties": {
        +                "address": {
        +                  "type": "string"
        +                },
        +                "local_label": {
        +                  "type": "string"
        +                },
        +                "server_label": {
        +                  "type": [
        +                    "string",
        +                    "null"
        +                  ]
        +                }
        +              },
        +              "required": [
        +                "local_label",
        +                "address"
        +              ],
        +              "type": "object"
        +            },
        +            {
        +              "type": "null"
        +            }
        +          ]
        +        }
        +      },
        +      "required": [
        +        "wallet",
        +        "projects",
        +        "active_project"
        +      ],
        +      "type": "object"
        +    },
        +    "status": {
        +      "enum": [
        +        "ok",
        +        "error"
        +      ],
        +      "type": "string"
        +    }
        +  },
        +  "required": [
        +    "status"
        +  ],
        +  "type": "object"
        +}
    • Removedtest_notification
    • Removedtier_status
    • Addedup
    • Removedupdate_function
    • Removedupdate_mailbox
    • Removedupdate_mailbox_webhook
    • Removedupdate_version
    • Removedvalidate_manifest
    • Removedverify_agent_contact_email
    • Removedverify_magic_link
    • Removedverify_project_archive
    • Removedwait_for_cdn_freshness
    • Changedwhoami2 fields changed
      • addedInput schema / additionalProperties
        Added value: +false
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "http://json-schema.org/draft-07/schema#",
        +  "additionalProperties": true,
        +  "properties": {
        +    "error": {
        +      "additionalProperties": true,
        +      "properties": {
        +        "category": {
        +          "type": "string"
        +        },
        +        "code": {
        +          "type": "string"
        +        },
        +        "column": {
        +          "type": "integer"
        +        },
        +        "details": {},
        +        "fix": {},
        +        "http_status": {
        +          "type": "integer"
        +        },
        +        "line": {
        +          "type": "integer"
        +        },
        +        "logs": {
        +          "items": {
        +            "type": "string"
        +          },
        +          "type": "array"
        +        },
        +        "message": {
        +          "type": "string"
        +        },
        +        "mutation_state": {
        +          "type": "string"
        +        },
        +        "next_actions": {
        +          "items": {
        +            "additionalProperties": true,
        +            "properties": {
        +              "type": {
        +                "type": "string"
        +              }
        +            },
        +            "type": "object"
        +          },
        +          "type": "array"
        +        },
        +        "operation_id": {
        +          "type": "string"
        +        },
        +        "phase": {
        +          "type": "string"
        +        },
        +        "plan_id": {
        +          "type": "string"
        +        },
        +        "resource": {
        +          "type": "string"
        +        },
        +        "retryable": {
        +          "type": "boolean"
        +        },
        +        "safe_to_retry": {
        +          "type": "boolean"
        +        },
        +        "trace_id": {
        +          "type": "string"
        +        }
        +      },
        +      "required": [
        +        "code",
        +        "message",
        +        "next_actions"
        +      ],
        +      "type": "object"
        +    },
        +    "result": {
        +      "additionalProperties": true,
        +      "properties": {
        +        "memberships": {
        +          "items": {
        +            "additionalProperties": true,
        +            "properties": {
        +              "org_id": {
        +                "type": "string"
        +              },
        +              "role": {
        +                "type": "string"
        +              }
        +            },
        +            "required": [
        +              "org_id"
        +            ],
        +            "type": "object"
        +          },
        +          "type": "array"
        +        },
        +        "principal": {
        +          "additionalProperties": true,
        +          "properties": {
        +            "id": {
        +              "type": "string"
        +            },
        +            "type": {
        +              "type": "string"
        +            }
        +          },
        +          "required": [
        +            "id"
        +          ],
        +          "type": "object"
        +        },
        +        "session": {
        +          "anyOf": [
        +            {
        +              "additionalProperties": true,
        +              "properties": {
        +                "grade": {
        +                  "type": "string"
        +                }
        +              },
        +              "type": "object"
        +            },
        +            {
        +              "type": "null"
        +            }
        +          ]
        +        }
        +      },
        +      "required": [
        +        "principal",
        +        "memberships"
        +      ],
        +      "type": "object"
        +    },
        +    "status": {
        +      "enum": [
        +        "ok",
        +        "error"
        +      ],
        +      "type": "string"
        +    }
        +  },
        +  "required": [
        +    "status"
        +  ],
        +  "type": "object"
        +}
  2. 3 tool updatesv4.13.3
    • Addedpay_url
    • Changedrequest_magic_link4 fields changed
      • addedInput schema / properties / delivery
        Added value: +{
        +  "description": "Email credential mode. Defaults to link.",
        +  "enum": [
        +    "link",
        +    "code",
        +    "both"
        +  ],
        +  "type": "string"
        +}
      • changedInput schema / properties / email / description
        Previous value: -"Email address to send the magic link to"New value: +"Email address to authenticate"
      • changedInput schema / properties / redirect_url / description
        Previous value: -"URL to redirect to after clicking the magic link. Must be an allowed origin for this project (localhost, claimed subdomain, or custom domain)."New value: +"Allowed redirect URL. Required for link/both; optional for code."
      • changedInput schema / required
        Previous value: -[
        -  "project_id",
        -  "email",
        -  "redirect_url"
        -]New value: +[
        +  "project_id",
        +  "email"
        +]
    • Changedverify_magic_link4 fields changed
      • addedInput schema / properties / challenge_id
        Added value: +{
        +  "description": "Opaque email-code challenge handle. Required with code.",
        +  "type": "string"
        +}
      • addedInput schema / properties / code
        Added value: +{
        +  "description": "Six-digit email code. Required with challenge_id.",
        +  "type": "string"
        +}
      • changedInput schema / properties / token / description
        Previous value: -"The magic link token from the email link URL (?token=...)"New value: +"Magic-link token. Mutually exclusive with challenge_id/code."
      • changedInput schema / required
        Previous value: -[
        -  "project_id",
        -  "token"
        -]New value: +[
        +  "project_id"
        +]
  3. 5 tool updatesv4.8.0
    • Addedcreate_notification_rule
    • Addeddelete_notification_rule
    • Addedlist_notification_channels
    • Addedlist_notification_rules
    • Changedtest_notification3 fields changed
      • addedInput schema / additionalProperties
        Added value: +false
      • addedInput schema / properties / event_type
        Added value: +{
        +  "description": "Synthetic event_type override (flat snake_case, e.g. `signature_failed`) — use this to exercise a specific routing rule's `event_types` filter precisely. Defaults to the gateway's built-in sample event when omitted.",
        +  "type": "string"
        +}
      • addedInput schema / properties / source
        Added value: +{
        +  "description": "Route the synthetic test event as if it came from the app lane or the platform, so it exercises a specific Telegram routing rule's `source` filter. Defaults to 'platform' when omitted.",
        +  "enum": [
        +    "app",
        +    "platform"
        +  ],
        +  "type": "string"
        +}
  4. 1 tool updatev4.6.0
    • Changedlist_project_events2 fields changed
      • addedInput schema / properties / event_type
        Added value: +{
        +  "description": "Restrict to one or more event types, comma-separated (e.g. \"signature_completed,booking_created\"). Composes with source — e.g. source: \"app\" + event_type to watch for one specific business fact.",
        +  "type": "string"
        +}
      • addedInput schema / properties / source
        Added value: +{
        +  "description": "Restrict to one source: \"app\" (business facts a deployed function emitted itself via events.emit) or \"platform\" (every non-app source — the platform's own operational record). Omit to read both lanes in one merged, cursor-ordered feed.",
        +  "enum": [
        +    "app",
        +    "platform"
        +  ],
        +  "type": "string"
        +}
  5. 1 tool updatev4.5.0
    • Addederrors_list
  6. 1 tool updatev4.4.0
    • Addedlist_project_events
  7. 1 tool updatev4.2.1
    • Changedinvoke_function4 fields changed
      • addedInput schema / properties / idempotency_key
        Added value: +{
        +  "description": "Stable Idempotency-Key required by paid function invocations. Reuse it for the same paid intent; use a new key only for a new paid intent.",
        +  "type": "string"
        +}
      • addedInput schema / properties / poll_interval_ms
        Added value: +{
        +  "description": "Polling interval in milliseconds when wait is true.",
        +  "minimum": 0,
        +  "type": "integer"
        +}
      • addedInput schema / properties / timeout_ms
        Added value: +{
        +  "description": "Maximum wait time in milliseconds when wait is true.",
        +  "exclusiveMinimum": 0,
        +  "type": "integer"
        +}
      • addedInput schema / properties / wait
        Added value: +{
        +  "description": "When a paid invocation returns a 202 run handle, poll the run and replay the same idempotency key for the retained result.",
        +  "type": "boolean"
        +}
  8. 2 tool updatesv4.2.0
    • Addedlist_tenant_payments
    • Addedset_org_payout_wallet
  9. 1 tool updatev4.1.0
    • Addeddeploy_verify_edge
  10. 10 tool updatesv4.0.3
    • Addedcreate_project_branch
    • Addedcreate_project_snapshot
    • Addeddelete_project_branch
    • Addeddelete_project_snapshot
    • Addeddeploy_rehearse
    • Addedget_project_snapshot
    • Addedlist_project_branches
    • Addedlist_project_snapshots
    • Addedrenew_project_branch
    • Addedrestore_project_snapshot
  11. 32 tool updatesv4.0.2
    • Removedadd_custom_domain
    • Addedapp_up
    • Addedcancel_function_run
    • Removedcheck_domain_status
    • Addedcreate_function_run
    • Changedcreate_mailbox1 field changed
      • changedInput schema / properties / slug / description
        Previous value: -"Mailbox slug (3-63 chars, lowercase alphanumeric + hyphens, no consecutive hyphens). Creates <slug>@mail.run402.com"New value: +"Project-scoped mailbox local part (3-63 chars, lowercase alphanumeric + hyphens, no consecutive hyphens). Creates <slug>@<project-mail-host>.mail.run402.com"
    • Changeddeploy3 fields changed
      • changedInput schema / properties / database / properties / migrations / items / properties / id / description
        Previous value: -"Stable migration id (e.g. '001_init'). Same id+checksum across re-deploys is a registry noop; same id+different checksum is a hard error."New value: +"Stable versioned migration id (e.g. '001_init'). Same id+checksum across re-deploys is a registry noop; same id+different checksum is a hard error. Use name instead for generated/idempotent SQL."
      • addedInput schema / properties / database / properties / migrations / items / properties / name
        Added value: +{
        +  "description": "Content-tracked migration name for generated/idempotent SQL. The SDK compiles this to <name>_<sha256(sql)[0:16]>; changed content applies once under a new id and identical re-deploys noop. SQL declared with name MUST be idempotent.",
        +  "pattern": "^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$",
        +  "type": "string"
        +}
      • removedInput schema / properties / database / properties / migrations / items / required
        Removed value: -[
        -  "id"
        -]
    • Removeddisable_sender_domain_inbound
    • Addeddomains_activate
    • Addeddomains_apply
    • Addeddomains_check
    • Addeddomains_disconnect
    • Addeddomains_ensure
    • Addeddomains_get
    • Addeddomains_list
    • Addeddomains_repair
    • Addeddomains_test_receive
    • Removedenable_sender_domain_inbound
    • Changedget_function_logs2 fields changed
      • changedInput schema / properties / request_id / description
        Previous value: -"Only return logs correlated to this routed/function request id, such as req_abc123."New value: +"Only return logs correlated to this routed request id, function run id, or attempt id, such as req_abc123, fnrun_abc123, or fnatt_abc123."
      • changedInput schema / properties / request_id / pattern
        Previous value: -"^req_[A-Za-z0-9_-]{4,128}$"New value: +"^(?:req|fnrun|fnatt)_[A-Za-z0-9_-]{4,128}$"
    • Addedget_function_run
    • Addedget_function_run_logs
    • Removedlist_custom_domains
    • Addedlist_function_runs
    • Removedproject_info
    • Addedproject_key_cache_export
    • Addedproject_key_cache_status
    • Removedproject_keys
    • Addedredrive_function_run
    • Removedregister_sender_domain
    • Removedremove_custom_domain
    • Removedremove_sender_domain
    • Removedsender_domain_status
  12. 171 tool updatesv3.6.0
    • First observedaccept_project_transfer
    • First observedadd_custom_domain
    • First observedadd_org_member
    • First observedadmin_archive_project
    • First observedadmin_reactivate_project
    • First observedadmin_set_lease_perpetual
    • First observedai_moderate
    • First observedai_translate
    • First observedai_usage
    • First observedallowance_create
    • First observedallowance_export
    • First observedallowance_status
    • First observedapply_expose
    • First observedassets_get
    • First observedassets_ls
    • First observedassets_put
    • First observedassets_rm
    • First observedassets_sign
    • First observedauth_settings
    • First observedbilling_history
    • First observedbrowse_apps
    • First observedcancel_project_transfer
    • First observedcheck_balance
    • First observedcheck_domain_status
    • First observedci_create_binding
    • First observedci_get_binding
    • First observedci_list_bindings
    • First observedci_revoke_binding
    • First observedclaim_project_transfer
    • First observedclaim_subdomain
    • First observedcontract_call
    • First observedcontract_deploy
    • First observedcontract_read
    • First observedcreate_auth_user
    • First observedcreate_checkout
    • First observedcreate_email_organization
    • First observedcreate_mailbox
    • First observedcreate_org
    • First observedcreate_project_grant
    • First observeddelete_function
    • First observeddelete_mailbox
    • First observeddelete_mailbox_webhook
    • First observeddelete_passkey
    • First observeddelete_project
    • First observeddelete_secret
    • First observeddelete_signer
    • First observeddelete_subdomain
    • First observeddelete_version
    • First observeddemote_user
    • First observeddeploy
    • First observeddeploy_diagnose_url
    • First observeddeploy_events
    • First observeddeploy_function
    • First observeddeploy_list
    • First observeddeploy_release_active
    • First observeddeploy_release_diff
    • First observeddeploy_release_get
    • First observeddeploy_resume
    • First observeddeploy_site
    • First observeddeploy_site_dir
    • First observeddiagnose_public_url
    • First observeddisable_sender_domain_inbound
    • First observeddrain_signer
    • First observedenable_sender_domain_inbound
    • First observedexport_project_archive
    • First observedfork_app
    • First observedfunctions_rebuild
    • First observedgenerate_image
    • First observedget_agent_contact_status
    • First observedget_app
    • First observedget_contract_call_status
    • First observedget_email
    • First observedget_email_raw
    • First observedget_expose
    • First observedget_function_logs
    • First observedget_mailbox
    • First observedget_mailbox_webhook
    • First observedget_notification_preferences
    • First observedget_operator_status
    • First observedget_org
    • First observedget_quote
    • First observedget_schema
    • First observedget_signer
    • First observedget_usage
    • First observedimport_project_archive
    • First observedinit
    • First observedinitiate_project_transfer
    • First observedinspect_project_archive
    • First observedinvite_auth_user
    • First observedinvoke_function
    • First observedjobs_cancel
    • First observedjobs_download_artifact
    • First observedjobs_get
    • First observedjobs_logs
    • First observedjobs_purge
    • First observedjobs_submit
    • First observedlink_wallet_to_organization
    • First observedlist_custom_domains
    • First observedlist_emails
    • First observedlist_functions
    • First observedlist_incoming_transfers
    • First observedlist_mailbox_webhook_deliveries
    • First observedlist_mailbox_webhooks
    • First observedlist_mailboxes
    • First observedlist_notifications
    • First observedlist_org_members
    • First observedlist_orgs
    • First observedlist_outgoing_transfers
    • First observedlist_passkeys
    • First observedlist_projects
    • First observedlist_secrets
    • First observedlist_signers
    • First observedlist_subdomains
    • First observedlist_versions
    • First observedpasskey_login_options
    • First observedpasskey_login_verify
    • First observedpasskey_register_options
    • First observedpasskey_register_verify
    • First observedpreview_project_transfer
    • First observedproject_get
    • First observedproject_info
    • First observedproject_keys
    • First observedproject_use
    • First observedpromote_user
    • First observedprovision_postgres_project
    • First observedprovision_signer
    • First observedpublish_app
    • First observedredrive_mailbox_webhook_delivery
    • First observedregister_mailbox_webhook
    • First observedregister_sender_domain
    • First observedremove_custom_domain
    • First observedremove_org_member
    • First observedremove_sender_domain
    • First observedrename_org
    • First observedrename_project
    • First observedrequest_faucet
    • First observedrequest_magic_link
    • First observedrest_query
    • First observedrevoke_project_grant
    • First observedrotate_webhook_secret
    • First observedrun_sql
    • First observedscaffold_roles
    • First observedsend_email
    • First observedsend_message
    • First observedsender_domain_status
    • First observedservice_health
    • First observedservice_status
    • First observedset_agent_contact
    • First observedset_auto_recharge
    • First observedset_low_balance_alert
    • First observedset_mailbox_defaults
    • First observedset_notification_preferences
    • First observedset_org_member_role
    • First observedset_recovery_address
    • First observedset_secret
    • First observedset_tier
    • First observedset_user_password
    • First observedstart_operator_passkey_enrollment
    • First observedstatus
    • First observedtest_notification
    • First observedtier_status
    • First observedupdate_function
    • First observedupdate_mailbox
    • First observedupdate_mailbox_webhook
    • First observedupdate_version
    • First observedvalidate_manifest
    • First observedverify_agent_contact_email
    • First observedverify_magic_link
    • First observedverify_project_archive
    • First observedwait_for_cdn_freshness
    • First observedwhoami

TDQS

A4/5.0

Scored across 8 tools

Disambiguation4/5

Most tools have clearly distinct purposes: status, whoami, doctor, docs, run, and expand_result are each unique. The main overlap is between up and deploy, where up bundles setup plus deploy and deploy is the lower-level apply primitive; descriptions clarify the difference, but an agent might still be uncertain which to call for a simple deployment.

Naming Consistency3/5

Naming is mixed: some tools are single verbs (up, deploy, run), some are nouns (status, doctor, docs), and expand_result uses snake_case with a verb_noun pattern. The names are readable and follow CLI conventions, but there is no consistent grammatical pattern across the set.

Tool Count5/5

With 8 tools, the server is well-scoped: it exposes high-level workflows (up, deploy), status/inspection (status, whoami, doctor), a reference (docs), a generic execution primitive (run), and a result-pagination helper (expand_result). Each tool earns its place without bloat.

Completeness5/5

The surface is intentionally minimal but has no real gaps because run provides a generic SDK snippet escape hatch, and the docs tool gives the reference needed to use it. Common workflows are covered by up/deploy, status/whoami/doctor handle inspection, and expand_result ensures large results are accessible.

Maintenance

ActivityActive
ResponsivenessResponsive

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    A general-purpose PostgreSQL MCP server with full read-write SQL access, atomic multi-statement transactions, and schema inspection. Works with any PostgreSQL instance — local, Supabase, AWS RDS, or self-hosted — and connects to Claude, Cursor, Windsurf, or any MCP-compatible AI client.
    127 npm
    3
    ISC
  • A
    license
    Not graded
    quality
    C
    maintenance
    An extensible MCP server for database operations that supports PostgreSQL for managing schemas, tables, data, and user permissions. It features automatic migration recording for DDL changes and integrates with various AI-powered editors like Cursor, Zed, and Claude Code.
    16 npm
    2
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables AI assistants to securely interact with PostgreSQL databases through a standardized MCP interface, supporting SQL queries, schema inspection, and database management.
    MIT