Deploy App
deploy_appDeploy a v2 app: an HTML document plus a capability manifest, hosted at its own URL.
A redeploy only needs the content that changed. Every content field is optional when app_id is given, and an omitted one keeps what is live: omit manifest for an HTML-only change, omit html for a manifest-only change, omit assets to keep the current files. This is the cheap path and the default, because an omitted field costs no output tokens at all: a one-line colour change does not resend the whole document, and a manifest edit does not resend it either. A field only needs sending when its content differs from what is live. assets: [] is the explicit way to clear the asset set, and omitting all three is refused, since there would be nothing to change.
The extension keys used most often: app metadata; collections, with per-collection write, update, read and delete role lists, where write gates creates and also gates updates unless an update list is declared; externalHosts, a fetch allowlist; cdn, to allow CDN scripts and styles; capabilities, for Permissions-Policy opt-ins; embeds, an iframe frame-src allowlist; notify, for email-on-row rules; webhooks, for signed HTTP POST on-row rules; and agentTasks, to queue work for an agent running on the owner's own machine, described as a prompt rather than as code. The manifest grammar is documented in the Homespun guide that get_skill returns.
Pass no app_id to create, which mints a slug and URL and requires both html and manifest, or pass app_id to redeploy an existing app. Supply the HTML inline as html, or as html_path, an absolute path read on the MCP-server host, which is the relay for a hosted connector or the CLI host for a locally-run one, and not the remote agent's machine; it avoids retransmitting a large HTML file on every deploy, only a locally-run connector can read it, and inline html wins if both are given. dry_run:true (alias check) validates only: it runs the full manifest and asset validation, the redeploy compat gate and the schedule-timezone advisory, then returns { ok, warnings, compat?, breaks? } without creating a version or mutating anything, and it resolves omitted fields the same way a real deploy would, so it reports on exactly the deploy that would run.
A redeploy is refused with manifest_incompatible_redeploy, unless force:true, when it would strand rows already written (dropping a collection, tightening a schema, flipping appendOnly), or when it would widen what the app's install screen discloses: a collection's read reaching further than the live manifest, a capability added, cdn turned on, or a host added to externalHosts, embeds or a webhook target. The break quotes the sentence a user would now be asked to approve. Taking access away never prompts: dropping a role, dropping a capability, host or webhook, turning cdn off, or adding update:["creator"] to a write:["anyone"] collection, all redeploy clean. A removed collection is detached rather than deleted.
Images, fonts, audio, video and data files ship with the app in the same call via assets[]. Each is validated and stored app-scoped and served at its path on the app's own origin, so the HTML references it by a stable same-origin path such as <img src=\"frames/000.jpg\">; media and font paths support HTTP Range for seeking. A redeploy's assets replace the previous version's set when sent, carry over when omitted, and are cleared by assets: [].
Returns { app_id, slug, url, version, visibility, created } on create, or { app_id, version, compat, breaks? } on redeploy.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| html | No | The app's UI as a complete HTML document (single file, with CSS and JS inline), sent inline. Capped at 2 MB of UTF-8; over that the deploy is refused with 413 document_size_exceeded. A document near the cap is almost always carrying a file inlined as a data: URI; the same file in `assets[]` is served from the app's own origin, cached separately, and does not count toward this cap. The document comes from either this field or `html_path`. Inline is the only form a hosted or remote connector with no filesystem can use, and inline `html` wins if both are given. On a redeploy an omitted `html` keeps the live document, so a manifest-only change (adding a collection, widening externalHosts) costs nothing in HTML. | |
| slug | No | Create only. Accepted with visibility private or public, including the private default; rejected with explicit visibility 'link', where the slug is always server-generated. | |
| check | No | Alias for `dry_run`. | |
| force | No | Redeploy only. Bypasses the compat gate, whether it fired on a stranded-rows narrowing or on a widening of what the install screen discloses (a removed collection is detached, never deleted). | |
| app_id | No | Omit to create a new app; pass an existing app's id to redeploy it (a new version, compat-gated unless force:true). | |
| assets | No | Optional bundle of files shipped with the app in one deploy: images, fonts, audio/video, data. Each asset either carries its bytes inline as `content_base64` or references an already-uploaded attachment by `attachment_id`; the reference form suits real images and media, where the file is uploaded once via `attachments fetch` or presign and then bound here, with no base64 in the deploy body. Each asset is validated + stored app-scoped exactly like a normal attachment (byte-sniff, allowlist, size cap, quota, scan) and served at its `path` on the app's own origin, so the page references it by a stable same-origin path (`<img src="frames/000.jpg">`, `<video src="media/intro.mp4">`; media/font paths support HTTP Range). The whole deploy is rejected atomically if any asset fails validation. On a redeploy, sent assets replace the previous version's set, an omitted `assets` keeps the live set (no re-upload, no re-encoding), and `assets: []` is the explicit way to clear it. Bounded by the relay's per-deploy asset-count cap; total bytes by the per-app blob quota. | |
| dry_run | No | Validate only: run the full manifest + asset-shape validation, the compat gate (for a redeploy), and the schedule-timezone advisory, then return { ok, warnings, compat?, breaks? } without creating a version or mutating anything. An invalid manifest returns the same error a real deploy would; a redeploy the compat gate would refuse reports the break instead of applying it. `check` is an accepted alias. | |
| manifest | No | The x-homespun-manifest capability document (a JSON object). Required to create; on a redeploy an omitted `manifest` keeps the live one, which fits most redeploys (the manifest was byte-identical to the previous version in 71% of real redeploys). The extension keys used most often: app metadata; collections (+ per-collection write/update/read/delete role lists, where write gates creates and also updates unless the optional update list is declared); externalHosts (fetch allowlist); cdn (allow CDN scripts/styles); capabilities (Permissions-Policy opt-ins); embeds (iframe frame-src allowlist); notify (email-on-row rules); webhooks (signed HTTP POST on-row rules); agentTasks (queue work for an agent on the owner's own machine, described as a prompt). The full grammar is documented in the Homespun guide that get_skill returns. | |
| html_path | No | Absolute path to the app's HTML document, read on the MCP-server host (the machine running this connector: the relay for a hosted connector, or the CLI host for a locally-run one), not on the remote agent's machine. An alternative to inline `html` that avoids retransmitting a large HTML file on every deploy. It resolves only when the file is local to the MCP server, so it serves a locally-run connector rather than a hosted or remote one, where the path does not exist and the call returns a clean error; inline `html` is the form that works there. If both `html` and `html_path` are given, inline `html` wins. | |
| visibility | No | Create only. Default 'private' (owner plus invited members, sign-in gated). 'link' shares with anyone holding the returned share_url, whose #k= fragment carries a secret key that can be reset (rotate it via the apps tool, action share_link_rotate) to cut off everyone with the old link; a 'link' app always gets a server-generated unguessable slug. 'private' and 'public' accept an owner-chosen `slug`. |