lathe docs

MCP reference

Everything your agent needs to deploy, in one API/MCP. Same rules, same checks, same jobs. See the API reference for the same ground from code.

API keys

One credential for both the MCP server and the REST API.

Sign in, then create a key on the Access page. A key acts as the customer who created it and cannot create or revoke other keys.

MCP server

Streamable HTTP · https://mcp.lathe.live · setup for Claude Code, Cursor and ChatGPT

One paste, for a coding agent

Paste this into Claude Code, Cursor or any agent that can read a URL; skill.md tells it how to connect for the client it runs in and which tools to call, in order.

Read https://lathe.live/skill.md and follow it

Claude.ai and other clients that sign you in

Settings → Connectors → Add custom connector: name it Lathe, paste the URL above, keep Requires sign-in on and leave the client id and secret blank (the server registers the client itself). Claude sends you to app.lathe.live to sign in with your email link and approve access; the app then appears under Connected apps, where you can disconnect it.

https://mcp.lathe.live

Claude Code

No key needed: add the server, then run /mcp and finish the sign-in in the browser.

claude mcp add --transport http lathe https://mcp.lathe.live

Clients that take a header

Give the agent the endpoint and an API key. Claude Code takes one too, for a session with no browser:

claude mcp add --transport http lathe https://mcp.lathe.live --header "Authorization: Bearer lathe_…"

Claude Desktop, Cursor and most other clients take a JSON entry of this shape:

{
  "mcpServers": {
    "lathe": {
      "type": "http",
      "url": "https://mcp.lathe.live",
      "headers": { "Authorization": "Bearer lathe_…" }
    }
  }
}

Tools

ToolWhat it does
list_plans readList plans with monthly prices in USD, hardware, included storage and traffic, and available locations.
get_account readRead the account profile, card status, caller role and permissions. Members are limited to their granted scope; billing and account-wide changes require an admin. Use list_plans for the current Free and trial offer.
update_accountUpdate the profile that goes on receipts. Omitted fields keep their value; the name is required before the first purchase.
get_billing readRead subscriptions, status, tier, next charge, trial end and charge history.
list_instances readList the customer's instances with status, tier, location, tags, billing and pending jobs. No secrets; use get_connection_urls.
get_instance readRead an instance's status, billing, usage, monitoring, backups, activity, upgrade options and pending job.
get_connection_urlsGet connection strings with TEMPORARY credentials for selected engines. Credentials expire after an hour by default; ttl_minutes allows 5-720 minutes, but auth keys last at most 60. Treat every credential as a secret. Use refresh_access for a person-approved extension; permanent credentials come from the portal or REST API. Use pooled Postgres connections for many short sessions, direct connections for migrations and LISTEN/NOTIFY. See docs/MCP-WORKFLOWS.md#get_connection_urls.
refresh_accessExtend a temporary credential (a lease_id from get_connection_urls) by ttl_minutes from now (default 60). A person must approve: the first call answers status approval_required and an approval_url - ask the person to open it and click Extend, then call again within 10 minutes. The next call answers status extended: for Postgres, Redis, CouchDB, NATS and SeaweedFS the credential you hold keeps working until the new expires_at; for the auth engine `entry` carries a new key, since a key's expiry cannot be moved. A lease that has ended cannot be extended: call get_connection_urls again.
revoke_access destructiveEnd a temporary credential now (its connections are closed). The auth engine's key cannot be ended early and lapses at its expires_at; the answer says so.
list_access readList temporary credentials with engine, principal, expiry and the caller that created each. No secrets; optionally filter by instance.
create_instanceCreate an instance on a plan (free | mini | starter | plus | pro). Where list_plans offers Free, tier free creates a $0 instance at once, with no card or checkout; it has 1000 GB outbound traffic per month and no daily backups. A paid plan returns checkout_url, which the person must open in a browser and pay - the instance is created when the payment page returns, and the returned instance_id is valid from the start (poll get_instance). A coupon that covers the whole first month creates it immediately. Where list_plans reports a trial, a customer's first Mini answers trial=true: with trial.card 'later' it is created at once with no checkout (a card added on the portal keeps it past the trial); with 'start' checkout_url verifies the card and nothing is charged until the trial ends. Optional: name (a label), tags (lowercase tokens), location (from list_plans), coupon, engines (names from list_plans; default Postgres alone).
update_instanceRename or retag an instance. Omitted fields keep their value; an empty name or an empty tag list clears it. Allowed while a job is running.
configure_enginesSet the enabled engines and their memory budgets in MB. Omit memory_mb to split the remaining budget; use list_plans for floors and limits. Omitted engines stop with data kept; at least one must remain. Auth requires postgres and apps. Changes may restart an engine; a budget-only change does not restart it, and Postgres applies shared_buffers at its next restart. See docs/MCP-WORKFLOWS.md#configure_engines.
configure_reallocationSwitch automatic memory reallocation on or off. When on, memory moves between engines and apps within the plan budget at most daily, without restarting. When off, get_instance retains advice for reallocate_now.
reallocate_nowMove memory between the instance's engines now, as the advisor would from the last day of readings; nothing restarts. Refused with the reason when there is nothing to move, or when the box has no room to give and the answer is a bigger plan (upgrade_instance - never taken on its own; downgrade_instance goes the other way).
set_allowlist destructiveReplace the IP allowlist for ports 5432 and 6432 with a list of CIDRs. An empty list opens the instance to the internet (TLS and password still required); an address left out stops connecting. Takes effect within a minute. confirm must be the exact change string returned when called with confirm=""; it binds this instance, action, resource and values.
configure_engine_settings destructiveReplace one engine's settings; omitted keys reset to defaults. Read get_engine_settings first or use update_engine_settings for a partial change. Keys, bounds and defaults come from list_plans. Secrets read back masked; sending the mask keeps their stored value. Restarts or reloads the engine. confirm must be the exact change string returned when called with confirm=""; it binds this instance, action, engine and values. See docs/MCP-WORKFLOWS.md#configure_engine_settings.
update_engine_settings destructiveChange selected engine settings, keeping all others. Empty values reset keys; add/remove amend list-valued keys without replacing the whole list, with remove applied first. Secrets read masked, and sending the mask preserves them. Returns the settings after the queued change; wait for the job before reading them back. Restarts or reloads the engine. confirm must be the exact change string returned when called with confirm=""; it binds this instance, action, engine and values. See docs/MCP-WORKFLOWS.md#update_engine_settings.
get_engine_settings readRead current engine settings, defaults, explicitly set keys and list-valued keys. Secrets read as a mask; sending that mask back preserves the stored value. Read before replacing settings; use update_engine_settings to change only selected keys. See docs/MCP-WORKFLOWS.md#get_engine_settings.
app_guide readRead the maintained guide for running an app, choosing build settings and connecting it to databases and auth. Call before writing a Dockerfile, workflow or deployment command. The guide includes the image contract, limits and configuration fields for this deployment. See docs/MCP-WORKFLOWS.md#app_guide.
list_apps readList apps, images, ports, health paths, domains, memory and deploy state. Environment names are visible, but secret values never are. Includes pending imports and connected repositories; app_repo and list_builds provide build details. See docs/MCP-WORKFLOWS.md#list_apps.
set_appAdd an app to the box, or change one by name. Every field but the name is optional: one you do not pass keeps the value the app already has (and takes the default below on an app being created), so this call never moves a setting you did not name. An app being created does need an image; one that exists keeps the image it runs. To change one thing on an app that exists, update_app says so plainly. The box pulls the image, starts it beside the version already running and moves the front door to it once it answers health_path; a version that never answers is left out and the running one stays. public=true serves it over https on a name of its own, <instance>-<name>.<the box's zone>, and on any domains (which must point at the box first); subdomain is a name of the customer's choosing under our own zone, the label alone (shop) or the full name, whose record we write ourselves - one per app, "" takes it off, a name let go is held for them for 30 days, and it has to be enabled on the account; default=true makes it the app the box's own hostname goes to as well, one per box (the first public app, when none is marked). public=false is loopback only, for the box's other apps. An app owns its port and the two above it, so leave three between apps. cpu_limit caps vCPUs (fractional, at most the plan's vCPUs, 0 for uncapped); cpu_weight is relative priority under contention (1 to 10000, default 100). CPU and memory changes alone apply without restarting. memory_mb comes out of the apps engine's budget (configure_engines sets that). singleton=true for an app that must never run twice: it is restarted in place, so a deploy has a moment of downtime. links names the other apps this one talks to (each becomes <NAME>_URL in its environment); nothing else about a neighbour reaches it. read_only runs it on a read-only root once the image tolerates one; capabilities names the Linux capabilities it needs, none by default; upload_mb is the largest request body the front door passes it, in MB (32 unless set, 0 for none). cgroup_delegate=true lets the app hold its own processes to memory and CPU limits beneath the ones the box holds it to, which it cannot raise; changing it restarts the app, and it has to be enabled on the account. inbound opens ports to the internet for a protocol the front door cannot carry - a game server, a VPN endpoint, MQTT, a peer-to-peer client - each written as a port or a port and a protocol (25565, 51820/udp; bare means TCP), 1024 and above, and it has to be enabled on the account. analytics (true on a new app) counts the app's traffic where analytics is enabled on the account, as totals with no address or cookie kept; false stops counting and keeps what was counted. analytics_events lists the custom event names the app may send, each 1 to 40 of a-z, 0-9, dot, dash and underscore, at most 50; an empty list keeps the first 50 names seen each day. domain_check is a path of the app's (/domain-check) that approves the names it serves: before a certificate is issued for one the box asks that path with ?domain=<name>, and only a 2xx gets one. With a wildcard in domains (*.tenants.example.com, one level deep) it gates the names under it, and every app on a box can have its own; without one, the app answers every name pointing at the box that no other app claims, one such app per box. Public apps only, "" turns it off. network=isolated gives the app a network of its own that reaches only what egress names and nothing of the box itself; egress lists internet, internet:<port>, another isolated app as name or name:port (reached by that name), an engine by name, or a public address or range with an optional port, and private, local and reserved ranges are refused whatever it says. untrusted=true is for code an attacker may control, such as a browser opening sites nobody vouches for: isolated, egress internet unless set and never an engine, no engine credentials, a /data emptied whenever the container is replaced, a read-only root, no capabilities and no link to sign-in; restart_app replaces its container with a fresh one, and max_lifetime (seconds, 300 at least) does it on a schedule. health=tcp brings up an app that speaks no HTTP by a connection to its port, and such an app is not public. public_allow lists the only addresses a public app's names answer. runtime=sandboxed runs the container on a kernel of its own in user space, one more wall between its code and the box. This runs the image you name on the box: name one you trust.
update_appChange some of one app's settings and leave every other one as it is: pass only the fields you mean to move. Safe when you do not hold the whole spec - to raise an app's memory, pass memory_mb and nothing else, and its port, health path and whether it is published are untouched. The app must already be on the box; set_app is what creates one. Every field means what it does on set_app. The box pulls the image and swaps the container the same way.
set_app_memorySplit the apps engine's budget again between the apps on the box, in one job: memory is {name: memory_mb} for the apps to move, and an app not named keeps its share. This is how memory moves from one app to another when the budget is spoken for - lowering one with update_app and raising the other would be two jobs, the second refused while the first runs. The whole split has to fit the engine's budget (get_instance carries it under memory.engines.apps; configure_engines changes it). Nothing restarts: each app's ceiling moves in place.
remove_app destructiveStop an app and remove it from the box. Its data directory stays; its environment goes. An app imported from a repository and still waiting for its first build is nothing on the box yet: only its connection goes. confirm must be the exact change string returned when called with confirm=""; it binds this instance, action, resource and values.
set_app_env destructiveReplace one app's environment, as a whole: what is not in the mapping is gone. To add or change some variables and keep the rest, use update_app_env instead. The machine fetches the secrets afresh and the app restarts with them, and - where Lathe builds the repository - the next build is given them too, which is what a front-end bundle and a private package registry need. Values are secrets and are never read back; list_apps shows the names. confirm must be the exact change string returned when called with confirm=""; it binds this instance, action, resource and values.
update_app_env destructiveAdd, overwrite or remove some of one app's environment variables and leave every other one as it is: `set` maps names to new values, `unset` lists names to remove. Safe when you do not hold the whole environment, which nothing reads back, so an overwritten value is gone. The machine fetches the secrets afresh and the app restarts with them. confirm must be the exact change string returned when called with confirm=""; it binds this instance, action, resource and values.
deploy_appPull the app's image again, tag unchanged or not, and restart it: what to call after pushing a new build to the tag the app already carries.
restart_appRestart one app's container on the image it is already running: no pull, no new container, the other apps and every engine untouched. For a wedged process or state that only a fresh start clears; deploy_app is what fetches a new build.
disk_breakdown readRead disk use by engine data, app data, images, build caches, logs and operating system, largest first. Includes bytes, plan-disk percentage and unaccounted space. Refused while the instance is unavailable.
app_logs readRead the last lines of an app's log, up to 1000, from the instance.
app_analytics readHow one app's traffic went over a window, counted at the box's front door with no cookie and no address kept. Call it for "how many visitors", "top pages", "where is my traffic coming from", "which countries", "mobile or desktop", "how many errors" or "is it slower", and for the figures behind an answer from app_health or app_changes. name is the app's name from list_apps. range is 1h, 24h, 72h, 7d, 30d, 90d or 365d; since and until name a window in UTC instead (2026-09-30 or 2026-09-30T14:00:00Z, until is now when left out), and a range and a since together are refused. A refusal's sentence is the portal's and calls since and until `from` and `to`: pass since and until here. Each plan keeps its own length of history (Free 7 days, Mini 30 days, Starter 90 days, Plus 365 days, Pro 730 days), and a window that starts before it is refused with code before_retention, whose message names the earliest time that can be asked about. `window` says what was answered: from and to, grain (5m, hour or day: the step of the series), label (the window in words), visitors_basis (daily: visitors are the sum of each UTC day's unique visitors, hourly: of each hour's, for a window that does not start and end at midnight UTC), in_progress and complete_until. A window that ends now is in_progress: its newest point is still being added to, its figures are what has been counted so far, it is left out of anomalies and changes, and complete_until is where the finished figures end. Left at its default the window is the last 24 hours. `totals` has requests, pageviews, visitors, client_pageviews (the pageview event a single-page app sends itself, never added to pageviews), bot_requests, opted_out, long_lived (WebSocket and event-stream connections), s2xx, s3xx, s4xx, s5xx (each a count), out_bytes (bytes sent), events and events_dropped (custom events: app_events has them by name), error_rate_pct and client_error_rate_pct (5xx and 4xx as a percentage of requests, 0 to 100), p50_ms, p95_ms and p99_ms (response times in milliseconds), up_minutes and uptime_pct (the minutes the app was up out of the minutes measured), memory_peak_mb (the most memory the app held, in megabytes) and cpu_avg_pct (its average share of the whole instance's CPU, 0 to 100). null means not measured and 0 means measured with none: never read a null as a zero, and say "not measured" rather than "none" when a figure is null. compare=true (the default) also answers `previous`, the same length just before: its window, its totals (null with reason before_retention when the plan no longer keeps it) and `compared`, the finished part's own figures while the window is in_progress; and `deltas`, each figure's {change (in its own unit, points for a _pct figure), change_pct (null when either side is null, when the previous is 0, and for a _pct figure)} for requests, pageviews, visitors, client_pageviews, bot_requests, s4xx, s5xx, out_bytes, error_rate_pct, client_error_rate_pct, p50_ms, p95_ms, p99_ms, uptime_pct, events. `series` is at most 400 points {t, requests, pageviews, visitors, s4xx, s5xx, error_rate_pct, out_bytes, p95_ms}, a point of nulls being a stretch nothing was counted in. breakdown names the lists wanted, from paths, referrers, countries, devices, browsers, os, statuses and host_kinds (left out: paths and referrers, or with a filter that filter's own list, such as statuses for a status; an empty list for none); each comes back under `breakdowns` as {rows, total_rows, next_cursor}, a row being {key, requests, pageviews, s4xx, s5xx, out_bytes, share_pct} largest first, and (other) the keys past what is kept one by one. limit is rows per list, 1 to 200 (20 by default); pass a list's next_cursor as cursor, with everything else the same, for its next page (null when there is none). path, referrer (a host, or (direct)), status (a code such as 404, or a class such as 5xx) and host_kind (subdomain, custom or box) narrow the answer to one key, exactly as a breakdown row shows it. Only requests, pageviews, the 4xx and 5xx counts and bytes are kept per key, so a filtered answer has null for client_pageviews, bot_requests, opted_out, long_lived, s2xx, s3xx, events, events_dropped, visitors, p50_ms, p95_ms, p99_ms, up_minutes, uptime_pct, memory_peak_mb, cpu_avg_pct, and its series is empty at the 5m grain, where a key's figures are not kept. `coverage` says what the figures may be missing: collecting_since (when counting began for this app, null when nothing has been counted), gaps (stretches with no figures, each with from, to, reason and why in words), partial (true when any of it applies to this window) and reason, one of: not_enabled (analytics is not on for the account); app_opted_out (the app's analytics switch is off: update_app with analytics=true turns it back on, and what was counted before is kept); box_needs_update (the instance has to be updated before it counts this app: saving any app on it with update_app starts that); unsupported_front_door (the instance's front door cannot count yet: it counts once the instance is updated, and nothing about the app needs changing); collector_unreachable (the newest figures could not be read from the instance, so the latest hours may be missing and arrive once it answers: check get_instance and wait, there is nothing to change); asleep (a free instance that is asleep counts nothing until wake_instance); before_retention (part of the window is older than the plan keeps). coverage.note is the same in a sentence to pass on. `summary` is what to answer from: text (sentences that always come out the same for the same figures, with any coverage note already in them), anomalies (hours that stood out, each with at, metric, kind drop, dip or spike, value, what was expected and `near`: the deploy, build, restart or settings change within the three hours before it, with minutes_before, or null), top_movers (the keys whose requests moved most against the period before) and next_tools (each a tool, its args and why to call it; the args leave out instance_id: pass the one you called with). An event beside a change is named, never claimed as its cause. `definitions` says in words what a pageview, a visitor, a bot and an opted-out request are, and every unit: a pageview is a GET answered with an HTML page and not from a bot; a visitor is one browser on one address on one UTC day, estimated without a cookie; requests sending Do Not Track or Global Privacy Control count in requests and pageviews, never in visitors, and are counted in opted_out. Refusals come back as a tool error in the portal's words: not_enabled (analytics is not on for this account; nothing to retry), not_found (no such instance on this account: list_instances), name (no such app on the instance: list_apps), range and before_retention (ask for a shorter range or a later since), cross_filter (two filters at once, or a filter with a breakdown of another dimension: figures are kept for each on its own, and crossing two could single a person out, so ask one at a time), filter (a value no figure is kept under), breakdown (a name not in the list), limit, and cursor (a cursor from another question, or from an earlier hour of a range that ends now: ask again without it). Next: app_changes to line a move up with deploys and restarts, app_health for whether the app is up now, and whatever summary.next_tools names.
app_health readWhether an app is ok, degraded or down right now, and why, from what is measured. The first call for "is my app healthy", "is it up" or "is something wrong". With name, one app; with name left out, every app on the instance under `apps`, and `verdict` the worst of them. Each app has verdict (ok, degraded or down, or null when nothing about the app has been measured yet, which is not the same as ok), reasons (each a code and a sentence: asleep, not_running, down_minutes, server_errors, slow, restarts, memory, and recent_deploy when a deploy in the last three hours sits beside a problem), and figures: running (at the last reading), up_minutes_last_hour (of the last whole hour), requests_last_hour, error_rate_pct_last_hour (5xx as a percentage, 0 to 100), p95_ms_last_hour against p95_ms_7d (the app's own over seven days, in milliseconds), restarts_24h, memory_held_mb against memory_limit_mb, and last_deploy_at (UTC). A rate is judged only on 20 requests or more in the hour. null means not measured and 0 means measured with none: never read a null as a zero, and say "not measured" rather than "none" when a figure is null. `summary` is what to answer from: text (sentences that always come out the same for the same figures, with any coverage note already in them), anomalies (hours that stood out, each with at, metric, kind drop, dip or spike, value, what was expected and `near`: the deploy, build, restart or settings change within the three hours before it, with minutes_before, or null), top_movers (the keys whose requests moved most against the period before) and next_tools (each a tool, its args and why to call it; the args leave out instance_id: pass the one you called with). An event beside a change is named, never claimed as its cause. Refusals come back as a tool error in the portal's words: not_enabled (analytics is not on for this account; nothing to retry), not_found (no such instance on this account: list_instances), name (no such app on the instance: list_apps), range and before_retention (ask for a shorter range or a later since). Next: for degraded or down, app_changes to see what happened before it and app_logs for the app's own account of it; for ok, app_analytics for its traffic.
app_changes readWhat moved in an app's traffic, server errors and response times over a window, and what happened to the app around then. The call for "what changed", "why did traffic drop" and "did that deploy break anything". range is 1h, 24h, 72h, 7d, 30d, 90d or 365d; since and until name a window in UTC instead (2026-09-30 or 2026-09-30T14:00:00Z, until is now when left out), and a range and a since together are refused. A refusal's sentence is the portal's and calls since and until `from` and `to`: pass since and until here. Each plan keeps its own length of history (Free 7 days, Mini 30 days, Starter 90 days, Plus 365 days, Pro 730 days), and a window that starts before it is refused with code before_retention, whose message names the earliest time that can be asked about. `window` says what was answered: from and to, grain (5m, hour or day: the step of the series), label (the window in words), visitors_basis (daily: visitors are the sum of each UTC day's unique visitors, hourly: of each hour's, for a window that does not start and end at midnight UTC), in_progress and complete_until. A window that ends now is in_progress: its newest point is still being added to, its figures are what has been counted so far, it is left out of anomalies and changes, and complete_until is where the finished figures end. Left at its default the window is the last 72 hours. `timeline` is newest first and holds two kinds of item: type change (a change point or an anomaly: at, metric requests, error_rate_pct or p95_ms, before and after for a lasting shift or kind drop, dip or spike with value for an hour that stood out, grain, and `near`, the deploy, build, restart, environment, memory or plan change within the three hours before it with minutes_before, or null) and type event (at, kind and what, in words). An event beside a change is named, never claimed as its cause. At most 50 items a page, never splitting one moment; next_before is the time to pass as before for the page after, null at the end. `window`, `coverage` and `definitions` are as in app_analytics. `coverage` says what the figures may be missing: collecting_since (when counting began for this app, null when nothing has been counted), gaps (stretches with no figures, each with from, to, reason and why in words), partial (true when any of it applies to this window) and reason, one of: not_enabled (analytics is not on for the account); app_opted_out (the app's analytics switch is off: update_app with analytics=true turns it back on, and what was counted before is kept); box_needs_update (the instance has to be updated before it counts this app: saving any app on it with update_app starts that); unsupported_front_door (the instance's front door cannot count yet: it counts once the instance is updated, and nothing about the app needs changing); collector_unreachable (the newest figures could not be read from the instance, so the latest hours may be missing and arrive once it answers: check get_instance and wait, there is nothing to change); asleep (a free instance that is asleep counts nothing until wake_instance); before_retention (part of the window is older than the plan keeps). coverage.note is the same in a sentence to pass on. `summary` is what to answer from: text (sentences that always come out the same for the same figures, with any coverage note already in them), anomalies (hours that stood out, each with at, metric, kind drop, dip or spike, value, what was expected and `near`: the deploy, build, restart or settings change within the three hours before it, with minutes_before, or null), top_movers (the keys whose requests moved most against the period before) and next_tools (each a tool, its args and why to call it; the args leave out instance_id: pass the one you called with). An event beside a change is named, never claimed as its cause. Refusals come back as a tool error in the portal's words: not_enabled (analytics is not on for this account; nothing to retry), not_found (no such instance on this account: list_instances), name (no such app on the instance: list_apps), range and before_retention (ask for a shorter range or a later since), and a before that is not a time. Next: app_analytics over the hours around a change for its figures and breakdowns, app_health for now, and app_logs for the app's own account of a deploy that went wrong.
app_events readThe custom events an app sent (a signup, a purchase, a click it chose to count) over whole UTC days: the call for "how many signups this week". An app sends one by a request to /_lathe/event with a name and up to five properties; update_app's analytics_events lists the names it may send, and without a list the first 50 names of a day are kept. range is 1h, 24h, 72h, 7d, 30d, 90d or 365d; since and until name a window in UTC instead (2026-09-30 or 2026-09-30T14:00:00Z, until is now when left out), and a range and a since together are refused. A refusal's sentence is the portal's and calls since and until `from` and `to`: pass since and until here. Each plan keeps its own length of history (Free 7 days, Mini 30 days, Starter 90 days, Plus 365 days, Pro 730 days), and a window that starts before it is refused with code before_retention, whose message names the earliest time that can be asked about. `window` says what was answered: from and to, grain (5m, hour or day: the step of the series), label (the window in words), visitors_basis (daily: visitors are the sum of each UTC day's unique visitors, hourly: of each hour's, for a window that does not start and end at midnight UTC), in_progress and complete_until. A window that ends now is in_progress: its newest point is still being added to, its figures are what has been counted so far, it is left out of anomalies and changes, and complete_until is where the finished figures end. Left at its default the window is the last 7 days, and events are kept by the day, so a window is widened to the whole days it touches: window.widened_from is then what was asked, and the label says so. With event left out, `events` is each name with its count, largest first; with event named, `properties` is each property of that event with its values and their counts. `totals` has events and events_dropped (events not kept whole: a name past the 50 kept without a list or not on the app's list, a name that is not a valid one, past 60 a minute from one browser or server or 600 a minute shared by all that send Do Not Track or Global Privacy Control and by a flood of new visitors, sent from another site's page, or an event whose property value was left out of an hour with more values than can be sent). A day keeps its 50 most counted names and the 20 most counted values of each property, the rest in (other); `series` is {t, count} a day, and `summary` has the text to answer from and next_tools (whose args leave out instance_id: pass the one you called with). limit is rows a page, 1 to 200 (50 by default), and next_cursor goes back as cursor for the next page. null means not measured and 0 means measured with none: never read a null as a zero, and say "not measured" rather than "none" when a figure is null. `coverage` says what the figures may be missing: collecting_since (when counting began for this app, null when nothing has been counted), gaps (stretches with no figures, each with from, to, reason and why in words), partial (true when any of it applies to this window) and reason, one of: not_enabled (analytics is not on for the account); app_opted_out (the app's analytics switch is off: update_app with analytics=true turns it back on, and what was counted before is kept); box_needs_update (the instance has to be updated before it counts this app: saving any app on it with update_app starts that); unsupported_front_door (the instance's front door cannot count yet: it counts once the instance is updated, and nothing about the app needs changing); collector_unreachable (the newest figures could not be read from the instance, so the latest hours may be missing and arrive once it answers: check get_instance and wait, there is nothing to change); asleep (a free instance that is asleep counts nothing until wake_instance); before_retention (part of the window is older than the plan keeps). coverage.note is the same in a sentence to pass on. Refusals come back as a tool error in the portal's words: not_enabled (analytics is not on for this account; nothing to retry), not_found (no such instance on this account: list_instances), name (no such app on the instance: list_apps), range and before_retention (ask for a shorter range or a later since), event (a name that is not 1 to 40 of a-z, 0-9, dot, dash and underscore), limit and cursor. Next: app_events with one event's name for its properties, and app_analytics for the traffic around them.
ask_app_analytics readA question about an app's traffic in words, answered by the tools above: use it when the person's own question is the easiest thing to pass on. It is mapped to one intent and one time by a fixed table, never guessed at, and `interpreted_as` says what it understood {intent, app, time, and range or from and to}: check it before answering. `answer` is the answer in sentences, `data` the whole answer of the tool it ran (app_analytics, app_health, app_changes or app_events, with every field as described there) and `summary` that answer's summary. The questions it answers, with how they may be put: traffic ("How many visitors did I get today?"), top pages ("What were the top pages last week?"), referrers ("Where is my traffic coming from this week?"), countries ("Which countries are my visitors from in the last 30 days?"), devices ("How many visitors use mobile?"), errors ("Any 5xx errors in the last 24 hours?"), latency ("Is the app slow today?"), what changed ("What changed since yesterday?"), why traffic dropped ("Why did traffic drop yesterday?"), custom events ("How many signup events this week?") and health ("Is my app healthy?"). Time can be said as today, yesterday, yesterday vs today, last hour, last 24 hours, this week, last week (Monday to Sunday), last 7 days, last 30 days, last 90 days, since Monday, since 2026-09-01, this month or last month; a question that names none takes the last 24 hours (72 for what changed and why it dropped, 7 days for events). A question it cannot map, or whose time it cannot read, is refused with code not_understood and a message listing what it can answer: put the question another way from that list, or call app_analytics directly. Next: whatever summary.next_tools names, whose args leave out instance_id: pass the one you called with. Refusals come back as a tool error in the portal's words: not_enabled (analytics is not on for this account; nothing to retry), not_found (no such instance on this account: list_instances), name (no such app on the instance: list_apps), range and before_retention (ask for a shorter range or a later since) (the window a question names is limited to what the plan keeps, like any other).
set_pull_registry destructiveSet registry credentials used by every app on the instance to pull private images. An empty pull_user removes saved credentials; omitted fields retain them. Secret values cannot be read back. This can revoke image-pull access and affect future deploys; no typed confirmation is enforced.
app_repo readRead an app's connected repository, branch, workflow or Lathe build configuration, deploy status and history. Read build_log for output, list_builds for builds that never deployed, and inspect_repo before changing the connection. See docs/MCP-WORKFLOWS.md#app_repo.
connect_app_repoConnect an app to GitHub Actions (source="actions") or Lathe builds (source="lathe"). Requires an installed GitHub app with access to the repository. Actions needs a workflow that pushes a container image; Lathe builds need a Dockerfile or supported plan. Successful builds may deploy automatically. Workflow, branch, build stage, build variables and checks affect what is built and deployed. Call inspect_repo and app_guide before selecting them. See docs/MCP-WORKFLOWS.md#connect_app_repo.
list_repos readList repositories accessible through the account's GitHub installation, including build workflows and import hints. A repository not shared with the installation cannot be imported. Use inspect_repo before importing. See docs/MCP-WORKFLOWS.md#list_repos.
limit_repos destructiveNarrow the repositories this account may use on a GitHub installation it shares with another Lathe account (list_repos: connections[].limited and repos). repos is the whole list to keep; every name must be on it already, since only Lathe's operator adds one. An app built from a repository left off stops building and deploying.
inspect_repo readInspect a repository's build and deployment choices without creating an app or starting a build. Requires access through the account's GitHub installation. Returns workflow, Dockerfile or plan hints and fields needed by import_repo.
import_repoImport a GitHub repository as an app and connect its builds. Requires the apps engine and repository access through the account's GitHub installation. source="actions" follows a workflow that publishes an image; source="lathe" builds a Dockerfile or supported plan. Green builds can deploy automatically; ports, health checks, memory and build variables affect the result. Call inspect_repo and app_guide first. Importing can start a build and deploy an app; monitor app_repo, list_builds and build_log. See docs/MCP-WORKFLOWS.md#import_repo.
build_log readRead a build's full output and result, including failure details and check output. run comes from list_builds or app_repo. The complete log can contain sensitive app output; status alone may hide the failure. Use its activity and test_log fields for progress and build-check output. See docs/MCP-WORKFLOWS.md#build_log.
list_builds readList an app's builds, newest first, including failures and successful builds that never deployed. Includes method, runtime, duration and deployment result. Read one build with build_log.
build_activity readRead running and recent builds across the account, repository configuration, success counts and typical duration. Queue and capacity figures describe the shared builder; can_build says whether builds can start. list_builds gives one app's full history. See docs/MCP-WORKFLOWS.md#build_activity.
disconnect_app_repoStop watching a repository for this app. The app keeps running exactly what it runs; only the automatic deploys and the build history stop.
deploy_latest_buildDeploy the newest green build of the connected workflow now, whether or not this app deploys automatically: what to call after connecting a repository, after a build that arrived while automatic deploys were off, and after a deploy that failed for a reason you have since put right - a green build is never used up by a deploy that did not work, and no new commit is needed to take it. To deploy a build that is not the newest, retry_deploy names one.
retry_deployDeploy one named green build again after its deploy failed: `run` is the run id from app_repo's history. For a registry credential the box was refused, or a budget that would not fit the app - fix that, then call this, and the build that is already sitting there goes on the box. The bytes recorded for that run are used where the build was pinned, so this deploys the build you named rather than whatever its tag means now.
rebuild_appRebuild a commit for an app using source="lathe". run selects an existing build, or omit it for the latest commit; options can force cache-free work or diagnostics. A successful rebuild may deploy when automatic deployment is enabled. Refused for Actions-built apps or inaccessible repositories. Use build_log for the result. See docs/MCP-WORKFLOWS.md#rebuild_app.
roll_back_appPut a build this app ran before back on the box, by the digest it ran then, so it is the same bytes even though the tag has moved. `run` is the run id from app_repo's history. The app stays connected and the next green build takes over from here.
restart_engineRestart one engine on the box; the others are untouched. Connections to it drop for a few seconds.
reset_engine destructiveWipe one engine's data and start it empty (an undo snapshot is kept 7 days); the other engines keep running. confirm must be the instance id.
list_databases readList Postgres databases with sizes and connections, or CouchDB databases with document counts and sizes.
create_databaseCreate a database on postgres or couchdb (lowercase letters, digits, underscores).
drop_database destructiveDrop a database on postgres or couchdb. confirm must be the database's name. Data is gone (the instance's daily backups remain).
list_extensions readList installed and offered Postgres extensions for a database.
set_extensionInstall (installed=true) or remove a Postgres extension in a database, from the offered list.
postgres_query destructiveRun one SQL statement and return rows. Requires the SQL console to be enabled: ro uses a read-only role and transaction; rw allows changes only in the approved database. A person must approve credential-requested rw enablement through set_sql_console. Rejects multiple statements and unsafe transaction controls; limits rows and execution time. Database defaults to the selected console database. See docs/MCP-WORKFLOWS.md#postgres_query.
postgres_connections readList current Postgres connections by role, application, state and query, with age and client address. Read-only; does not terminate sessions. Includes pooler figures where available. Query text can contain sensitive values. See docs/MCP-WORKFLOWS.md#postgres_connections.
redis_console destructiveSwitch the Redis console off, ro or rw. redis_keys / redis_key / redis_command need it on; rw allows writes. A person must approve rw: the first call answers status approval_required with an approval_url - ask the person to open it and click Approve, then call again within 10 minutes. off and ro take effect at once.
redis_keys readScan Redis keys matching a glob (type, TTL, size), 50 at a time; pass the returned cursor to continue.
redis_key readRead one Redis key's value, up to 200 collection members. Requires the Redis console to be enabled.
redis_command destructiveRun one Redis command line on the instance (read-only commands unless the console is rw; server management never).
list_streams readList NATS JetStream streams with subjects, message counts, bytes and consumers.
create_streamCreate a JetStream stream: name, subjects (e.g. ['orders.>']), retention limits|interest|workqueue, storage file|memory, max_bytes (-1 = none).
purge_stream destructivePurge every message from a stream. confirm must be the stream's name.
delete_stream destructiveDelete a stream and its messages. confirm must be the stream's name.
list_buckets readList object-storage buckets with names and creation times.
create_bucketCreate a bucket on the object storage engine. A name is 3 to 63 lower-case letters, digits and hyphens.
delete_bucket destructiveDelete every object in a bucket, then the bucket (up to 10,000 objects; a bigger one is emptied with an S3 client first). confirm must be the bucket's name.
list_auth_users readList app users, sign-in providers, creation and last-sign-in times, confirmation and ban status. Paginated; query filters by email. Requires the auth engine and permission to manage its users.
create_auth_userAdd a user to the auth engine. Without a password they sign in with a link or a code to the address. The address counts as confirmed unless email_confirm is false, in which case they must confirm it before signing in.
invite_auth_userMail somebody an invitation: the auth engine makes the user and sends a link that signs them in. The engine needs an SMTP server set (configure_engine_settings, group mail).
send_auth_linkMail somebody who already has an account a sign-in link (magiclink) or a password reset (recovery).
ban_auth_user destructiveBan a user, revoking sessions and new sign-ins, or lift a ban with banned=false. No user data is deleted. To ban, confirm must be the exact change string returned when called with confirm=""; it binds this instance, action and user. Lifting a ban needs no confirmation.
delete_auth_user destructiveDelete a user from the auth engine: their account, identities and sessions go, and rows of the customer's own keyed by that id stay. confirm must be their email address.
rotate_password destructiveIssue a new password for one engine (engine = postgres, redis, …) or for every engine on the box (omit engine). The old connection URLs stop working immediately; the new ones come from get_connection_urls once the job is done. The account's email address is told that the passwords changed; the new ones are not in that email. confirm must be the exact change string returned when called with confirm=""; it binds this instance, action, resource and values.
set_sql_console destructiveTurn the browser SQL console (reachable only through the portal) off, read-only (ro) or read-write (rw); postgres_query follows the same mode. The console is connected to one database: pass `database` to open it there (default: the owner database). A person must approve rw: the first call answers status approval_required with an approval_url - ask the person to open it and click Approve, then call again within 10 minutes. off and ro queue a change without approval.
upgrade_instance destructiveMove an instance to a higher plan, which raises its monthly price. A person must approve the price: the first call changes nothing and answers status approval_required with the new monthly price, an estimate of what is invoiced now for the rest of the period, the expected downtime (5-10 minutes) and an approval_url - show the person the price, ask them to open the link and click Approve, then call again with the same tier within 10 minutes. A free instance answers checkout_url instead, a payment page the person opens. downgrade_instance is the other direction.
downgrade_instance destructiveMove an instance to a LOWER plan. The cloud has no way down, so this moves the instance onto a smaller machine: its data is copied across and its address goes with it, so connection strings and DNS records keep working. The instance is down for the length of the copy (minutes, with the data). The daily backups belong to the machine being left and do not come along - a snapshot of it is kept - and the new machine's own daily backups start the next day. The unused part of the bigger plan comes back as credit on the next invoice and the smaller price applies from then on. confirm must be the instance's name or its id. A smaller plan keeps less analytics history, so the history older than it keeps is deleted once the instance is on it (Free 7 days, Mini 30 days, Starter 90 days, Plus 365 days, Pro 730 days). Refused when the data, the engine memory floors or the apps would not fit the smaller plan, with the reason.
list_backups readList daily backups and undo snapshots, newest first, for in-place restoration.
restore_backup destructiveRestore a backup or undo snapshot in place: the current data is replaced (a snapshot of it is kept 7 days). Same address, so connection strings keep working. confirm must be the instance id.
sleep_instance destructivePut an instance to sleep, on any plan: its engines and apps stop, a copy of its disk is taken, and its machine is shut down; its data, address and connection details are kept, and the price does not change. Nothing answers until it is woken (wake_instance, about a minute), so ask the person first: every app and client using it stops working. Poll get_instance until status is asleep.
wake_instanceWake an asleep instance: its machine is built again from its newest snapshot on the same address, in about a minute; poll get_instance until status is running. get_instance says `asleep` when an instance has no machine (a free one nobody was using, or one its owner put to sleep), and a client connecting then waits and times out. Asking for its connection URLs wakes it too.
reset_instance destructiveWipe every engine's data and start empty (a snapshot is kept 7 days). confirm must be the instance id.
delete_instance destructiveDelete an instance: billing stops, the VM and its backups are destroyed, a final snapshot is kept 7 days. confirm must be the instance id.
get_job readRead a queued, running or finished operation, including status, failure, timestamps and current phase. Poll slow changes with its job_id.

A Free instance costs $0 and is created without checkout or a card; it has traffic and backup limits. For a paid plan the tool returns a checkout URL that a person opens and pays (on a trial plan it verifies the card and the trial starts); the instance is provisioned when the payment page returns. What confirm must equal depends on the action, and the row above names it. rotate_password, set_allowlist, configure_engine_settings, update_engine_settings, remove_app, set_app_env, update_app_env and banning with ban_auth_user take the exact change string a call with confirm="" returns; that string binds the instance, the action, the resource and the values, and any change to those needs a new string. Lifting a ban needs no confirmation. reset_instance, restore_backup, delete_instance, downgrade_instance and reset_engine take the instance's name or its id. drop_database, purge_stream, delete_stream and delete_bucket take that resource's name, and delete_auth_user takes the user's email. A paid upgrade is a price and billing approval on the portal: the first upgrade_instance call changes nothing and returns the new monthly price, an estimate of what is invoiced now for the rest of the period, the expected downtime and an approval_url. The person opens that page and approves the quote; the same tier is then called again within 10 minutes. A free instance returns checkout_url for the first month instead. Console read-write is a portal approval scoped to one instance, one engine and, for Postgres, one database: redis_console and set_sql_console with mode rw answer approval_required and an approval_url until the person approves that scope. A different database needs its own approval. Switching the console to read-only or off cancels a write approval that is still waiting.