MCP · Tools
Tools: site
Three tools on the management plane. No human required. Descriptions are quoted as the agent reads them. The full deployment flow is in Deploy from GitHub.
deploy_site#
Deploy (or replace) the static front end of a project's app. There is one site per project, and deploying replaces it wholesale. Each file carries its path in the site and the MIME type that will be served for it — nothing is guessed. ⛔ SMALL SITES ONLY: at most 30 files and 100 KB in total, because every byte travels through this conversation as base64. This tool CANNOT carry the output of a real build (vite, next, astro): for that, call
get_deploy_workflow, which returns the GitHub Actions workflow to write into the repository that builds this site, andlink_repoto authorise that repository to deploy. The build then runs in the repository, not in this conversation. Use this tool for a hand-written page or a tiny prototype; use the workflow for anything a bundler produced. The response carries the public address the site is served at — the project's READABLE subdomain, assigned at this first deployment if the project did not have one yet — plussubdomain, the name itself. Once assigned it never changes, and the derived proj-… address keeps serving alongside it. A project name that gives no usable name falls back to app-<8 chars of the project id>: callclaim_site_subdomainBEFORE the first deployment to choose instead. It also carries app_url: deploying here fills in the project's app URL when it has none, and NEVER replaces one that is already set — changing an app URL that exists redirects every user of the app, so it stays a signed-in human's call via set_app_url.
| Parameter | Type | Required | Meaning |
|---|---|---|---|
project_id | string | yes | |
files | array, 1 to 30 items | yes | |
files[].path | string, min 1 | yes | The path in the site, from its root — absolute, e.g. "/index.html". |
files[].content_type | string, min 1 | yes | The MIME type that will be served for this file, e.g. "text/html". Never inferred. |
files[].content_base64 | string | yes | The file bytes, base64-encoded. |
Calls PUT /v1/projects/:pid/site. The 30-file bound is in the schema; the 100 KB total is checked by the tool before anything is sent, and the refusal points at the workflow. The site is public the second the call succeeds, and no copy of it is kept. There is no tool to take a site down: that is DELETE /v1/projects/:pid/site, or the dashboard.
list_site_deployments#
Read what has happened to this project's site: every deployment, every removal, and every REFUSED deployment attempt, newest first. This is the only place a rejected push can be explained after the fact — a GitHub Actions run that received a 404 or a 413 leaves a line here, and the run itself is gone. Match
request_idagainst the one the run printed to find the exact attempt.It is a HISTORY, not a status: it never says whether a site is being served right now, what files it holds, or what its address is. A project whose site was deployed and then removed still shows both lines.
viasays where the ENTRY came from, whatever its action — a removal and a refused attempt carry it too: "github" (andrepository_full_namenames the repository the call ran in), "key" (an account key made that call directly, so there is no repository to name), or "unknown" (the line predates provenance being recorded). On a refused linevianames who ATTEMPTED: nothing was deployed; on a subdomain claim it names who claimed the name, and nothing was deployed either.detaildepends on the action: files and bytes for a deployment, already_gone and app_url_cleared for a removal, status and error_code for a refusal, subdomain and subdomain_via for a subdomain claim. The refusal's human-readable message is NOT kept — naming the offending file or MIME type is what this trace deliberately does not do.This history is not guaranteed complete, and it cannot tell you when it is incomplete: refusal lines are written under a rate limit counted per IP, so during a burst a legitimate refusal may never have been recorded. An absent refusal is not proof that a push was accepted. Never tell a user "nothing was refused" on the strength of this list alone.
| Parameter | Type | Required | Meaning |
|---|---|---|---|
project_id | string | yes | |
action | site.deploy, site.destroy, site.deploy.refused or site.subdomain.claimed | no | Keep only one kind of entry. Omit it for all four — the list is already only these four. Use "site.deploy" with limit 1 to find the last successful deployment. |
limit | integer, 1–200 | no | How many entries, newest first. Default 50, maximum 200. Ask for 5 if you only need to know whether the last push landed. |
cursor | string | no | The cursor from a previous response, passed back unchanged, to read the next page. A response with no cursor is the last page. |
Calls GET /v1/projects/:pid/site/deployments. There is no order parameter and no automatic pagination: read one page, then decide.
claim_site_subdomain#
Claim a PRECISE readable subdomain for this project's site, before or after its first deployment:
https://<subdomain>.manybases.online.⛔ THIS IS PERMANENT. A project has at most one subdomain, and once assigned it never changes while the project exists — there is no rename and no release. Replaying the same name returns the same success (it is idempotent), but asking for a DIFFERENT one is refused and the error names the one the project already has. Choose it with the owner, not on their behalf.
Claiming before the first deployment is the only way to avoid the name being derived from the project name — and the only way to avoid the app-<8 chars> fallback when the project name gives nothing usable (fewer than three characters, an emoji, a name starting with proj-).
Refusals name their cause: the name is outside the grammar or reserved (400), another project holds it (409 taken), this project already holds a different one (409 immutable, and the response carries it). A 503 means two claims raced and NOTHING was assigned — replay once and the verdict is final.
The derived proj-… address keeps serving either way, and this call deploys nothing, redirects nothing, and never sets the project's app URL — only a deployment does that.
| Parameter | Type | Required | Meaning |
|---|---|---|---|
project_id | string | yes | |
subdomain | string | yes | 3 to 63 characters: a-z, 0-9 and single inner hyphens — never two in a row, never leading or trailing, and never starting with proj- (that prefix belongs to the derived addresses). Reserved names (www, api, dash, mcp, app, admin, docs…) are refused by name. This is what the site's public address will be, forever. |
Calls PUT /v1/projects/:pid/site/subdomain. Account key only: a GitHub Actions deployment token cannot claim a name — freezing a project's public address is not a runner's gesture.
