Skip to main content
Glama

update_app

Destructive

Use this to update an existing Charming app identified by UUID. Existing-contract saves publish synchronously. ESM saves require idempotency_key and expected_revision, return a durable buildId promptly, and keep the last active app running while the build completes. Poll with get_app_build. Two mutually exclusive input shapes: (A) full-source — pass module/ui/styles to replace those buckets, following the same authoring rules as create_app; (B) edits[] — exact-string find/replace operations plus expected_revision from your last source read. If the app's behavior changed materially (new operation, new data tracked, new use-case), also pass an updated description so list_apps stays accurate; backfill one if an older app has none. For synchronous results, give the user shareUrl to open or share the app; the url field is machine-only and embeds a write-capable access token.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
uiNoFull-source path only. Optional replacement frontend JavaScript. Follow the create_app rules for the selected contract. Existing-contract UI must (a) populate #app innerHTML BEFORE attaching event listeners, and (b) call operations as `window.charming.api('<manifest-id>').<opName>(input)` (operation name = method name, strips the { ok, value } envelope — you receive the value only, never ok) — NOT `api.operation(name, params)` and NOT raw fetch(). The signed-in caller is exposed synchronously as `window.charming.user` (`{ id, handle?, name?, image? }`, or `null` for an anonymous visitor) — read `user.name` to greet, attribute, or personalize, instead of asking the user to type their name. PUBLIC fields only (never email); it is a convenience signal, not enforcement. The outermost container MUST fill the viewport — use `<main class="min-h-screen">` (or a grid/flex layout that spans width) as the root shell. Do NOT wrap the root in `max-w-md`, `max-w-2xl`, or `container mx-auto`: those cap the entire app to a narrow central column with wide empty margins on 2K+ monitors. Cap the reading measure on an INNER wrapper only for text-heavy views, e.g. `<main class="min-h-screen"><div class="mx-auto max-w-2xl">…</div></main>`. Mutually exclusive with `edits`.
editsNoEdits path. Array of exact-string find/replace operations applied atomically. Pass the last-read revision through `expected_revision` to gate optimistic concurrency. Mutually exclusive with full-source fields (`module` / `ui` / `styles`).
app_idYesThe app ID (UUID) to update
moduleNoFull-source path only. Optional replacement ES module source. Same selected contract as create_app. For the existing contract, declare capabilities.imports including "charming:storage/kv@1.0" for persistence. For ESM, keep the exact ESM schema and import { kv } from "charming:storage/kv@1.0"; omit capabilities. A route handler in `export const routes = [...]` receives `(input, { env, ctx, request })` and returns exactly the value declared by `outputSchema`; for an array schema, use `handler: async (_input, { env }) => (await env.storage.get("key")) ?? []`. Charming creates the transport envelope. Do not add a `{ ok, value }` or `{ value }` envelope unless those fields belong to `outputSchema` itself. A named context reads storage as `context.env.storage`. The optional unmatched-request fallback has the separate signature `export default { fetch(request, env, ctx) { ... } }`, where the second argument is the environment itself; Charming supplies a generic 404 handler when it is absent. `env.user` is always present, not gated by any import: the caller's public identity (`{ id, handle?, name?, image? }`) or `null`. It lives only on `env` — read `env.user` (or `context.env.user`); there is no `ctx.user`. Full reference: call read_docs with path `llms-full.txt` (https://charm.ing/docs/llms-full.txt). Keep persisted state in backend storage (env.storage for the existing contract, imported kv for ESM); do not move it into localStorage/sessionStorage/IndexedDB, which are empty inside Claude/ChatGPT and lose the data. env.storage stores JSON-compatible values directly; do not JSON.stringify before put or JSON.parse after get. Mutually exclusive with `edits`. Migrating a legacy app to the existing dated contract requires the complete canonical manifest with the exact dated `$schema: "https://charm.ing/schema/app-manifest/2026-07-31.json"` plus `migrate_contract: true`; on this full-source path only, an omitted `$schema` is inserted automatically, but a wrong one still fails. ESM migration instead selects `https://charm.ing/schema/app-manifest/2026-09-05.json` through full-source or edits and requires `migrate_contract: true`, `expected_revision`, and `idempotency_key`.
stylesNoFull-source path only. Optional replacement CSS. Mutually exclusive with `edits`.
descriptionNoOptional replacement description. Omit to preserve the existing description, pass `null` to clear, pass a string to replace. Same shape and intent as create_app.description (1-2 sentences covering purpose + key actions + synonyms). Limit 500 characters.
idempotency_keyNoRequired for ESM saves. Retry the exact request with the same key; use a new key and current expected_revision for a new save.
expected_versionNoDeprecated compatibility input. Use `expected_revision`; while legacy hosts remain active, expected_version maps to the same revision precondition.
migrate_contractNoSet true only when replacing a legacy app with a complete valid dated manifest and routes contract. Source submissions do not roll back contracts. History can explicitly restore a retained validated existing-contract revision.
expected_revisionNoRevision precondition. Required for every ESM save and for existing-contract edits[]. Pass revision from your last get_app_source response. A new request against a stale desired revision fails with revision_mismatch; an identical idempotent retry returns its original build.

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
idNoUUID of the created or updated app.
okYes
urlNoApp URL. Present only after successful publication.
iconNoThe effective home-screen icon stored for the app. Canonical source declares it at `manifest.meta.icon`.
appIdNoExisting target app ID, or the created app ID after publication.
errorNo
stateNo
intentNoThe accepted operation: create, update, migrate, restore, or copy.
sourceNoExact accepted source, returned only when include_source is true.
appNameNoURL-safe slug for the app, unique per owner. Distinct from manifestId and displayName, and stable across edits — use rename_app to change it (the title changing does NOT change the slug).
buildIdNoDurable build ID. Use get_app_build to inspect progress or source.
attemptsNo
deadlineNo
revisionNoServer-owned app source revision. Historical null counters read as 0; new apps start at 1; each successful source write advances it once. Pass this value through `expected_revision` when guarding update_app.
shareUrlNoThe link to give humans whenever the user wants to open or share the app. Friendly /<handle>/<app-name> form when the OWNER has a live handle and the app a slug, /app/<uuid> otherwise. Always token-free — safe to show, paste, and send. Not an API base: appending /api/<op> to the friendly form 404s; use `url` for machine calls.
warningsNoNon-blocking publish feedback (#1126): present when static validation found UI/backend contract mismatches or legacy icon input needed a fallback. The write succeeded; fix the named source field.
elapsedMsNo
lockStateNo
statusUrlNoAuthenticated HTTP status URL for this build.
updatedAtNo
acceptedAtNo
advisoriesNoStructured non-fatal advisories, including authoring issues and unread staff feedback responses. Each advisory.summary is also appended to the text content for the LLM path.
finishedAtNo
lockDigestNo
sourceEtagNoETag identifying the immutable input accepted for this build.
inputDigestNo
queueDeadlineNo
activeRevisionNo
desiredRevisionNo
retryAfterSecondsNoWait at least this many seconds before polling again.
inspectionExpiresAtNo
idempotencyExpiresAtNo
resolvedDependenciesNo

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed42 schema fields changed
    • changedInput schema / properties / edits / description
      Previous value: -"Edits path. Array of exact-string find/replace operations applied atomically. Pass `expected_version` alongside to gate optimistic-concurrency. Mutually exclusive with full-source fields (`module` / `ui` / `styles`)."New value: +"Edits path. Array of exact-string find/replace operations applied atomically. Pass the last-read revision through `expected_revision` to gate optimistic concurrency. Mutually exclusive with full-source fields (`module` / `ui` / `styles`)."
    • addedInput schema / properties / expected_revision
      Added value: +{
      +  "description": "Revision precondition. Required for every ESM save and for existing-contract edits[]. Pass revision from your last get_app_source response. A new request against a stale desired revision fails with revision_mismatch; an identical idempotent retry returns its original build.",
      +  "maximum": 9007199254740991,
      +  "minimum": -9007199254740991,
      +  "type": "integer"
      +}
    • changedInput schema / properties / expected_version / description
      Previous value: -"Required when `edits[]` is present. Pass the `version` from your last get_app_source or update_app response. If the stored version has advanced, the edit is rejected with `version_mismatch` and the current version is surfaced so you can re-read and retry."New value: +"Deprecated compatibility input. Use `expected_revision`; while legacy hosts remain active, expected_version maps to the same revision precondition."
    • addedInput schema / properties / idempotency_key
      Added value: +{
      +  "description": "Required for ESM saves. Retry the exact request with the same key; use a new key and current expected_revision for a new save.",
      +  "type": "string"
      +}
    • addedInput schema / properties / migrate_contract
      Added value: +{
      +  "description": "Set true only when replacing a legacy app with a complete valid dated manifest and routes contract. Source submissions do not roll back contracts. History can explicitly restore a retained validated existing-contract revision.",
      +  "type": "boolean"
      +}
    • changedInput schema / properties / module / description
      Previous value: -"Full-source path only. Optional replacement ES module source. Same contract as create_app: must export a `manifest` with capabilities.imports including \"buildy:storage/kv@1.0\" for any app that persists state, plus route handlers that use env.storage.get/put. default.fetch is optional; Charming supplies a generic 404 handler when it is absent. Keep all persisted state in env.storage — do not move it into localStorage/sessionStorage/IndexedDB, which are empty inside Claude/ChatGPT and lose the data. env.storage stores JSON-compatible values directly; do not JSON.stringify before put or JSON.parse after get. Mutually exclusive with `edits`."New value: +"Full-source path only. Optional replacement ES module source. Same selected contract as create_app. For the existing contract, declare capabilities.imports including \"charming:storage/kv@1.0\" for persistence. For ESM, keep the exact ESM schema and import { kv } from \"charming:storage/kv@1.0\"; omit capabilities. A route handler in `export const routes = [...]` receives `(input, { env, ctx, request })` and returns exactly the value declared by `outputSchema`; for an array schema, use `handler: async (_input, { env }) => (await env.storage.get(\"key\")) ?? []`. Charming creates the transport envelope. Do not add a `{ ok, value }` or `{ value }` envelope unless those fields belong to `outputSchema` itself. A named context reads storage as `context.env.storage`. The optional unmatched-request fallback has the separate signature `export default { fetch(request, env, ctx) { ... } }`, where the second argument is the environment itself; Charming supplies a generic 404 handler when it is absent. `env.user` is always present, not gated by any import: the caller's public identity (`{ id, handle?, name?, image? }`) or `null`. It lives only on `env` — read `env.user` (or `context.env.user`); there is no `ctx.user`. Full reference: call read_docs with path `llms-full.txt` (https://charm.ing/docs/llms-full.txt). Keep persisted state in backend storage (env.storage for the existing contract, imported kv for ESM); do not move it into localStorage/sessionStorage/IndexedDB, which are empty inside Claude/ChatGPT and lose the data. env.storage stores JSON-compatible values directly; do not JSON.stringify before put or JSON.parse after get. Mutually exclusive with `edits`. Migrating a legacy app to the existing dated contract requires the complete canonical manifest with the exact dated `$schema: \"https://charm.ing/schema/app-manifest/2026-07-31.json\"` plus `migrate_contract: true`; on this full-source path only, an omitted `$schema` is inserted automatically, but a wrong one still fails. ESM migration instead selects `https://charm.ing/schema/app-manifest/2026-09-05.json` through full-source or edits and requires `migrate_contract: true`, `expected_revision`, and `idempotency_key`."
    • changedInput schema / properties / ui / description
      Previous value: -"Full-source path only. Optional replacement frontend JavaScript. Same contract as create_app: must (a) populate #app innerHTML BEFORE attaching event listeners, and (b) call operations as `window.buildy.api('<manifest-id>').<opName>(input)` (operation name = method name, strips the { ok, value } envelope — you receive the value only, never ok) — NOT `api.operation(name, params)` and NOT raw fetch(). The signed-in caller is exposed synchronously as `window.buildy.user` (`{ id, handle?, name?, image? }`, or `null` for an anonymous visitor) — read `user.name` to greet, attribute, or personalize, instead of asking the user to type their name. PUBLIC fields only (never email); it is a convenience signal, not enforcement. The outermost container MUST fill the viewport — use `<main class=\"min-h-screen\">` (or a grid/flex layout that spans width) as the root shell. Do NOT wrap the root in `max-w-md`, `max-w-2xl`, or `container mx-auto`: those cap the entire app to a narrow central column with wide empty margins on 2K+ monitors. Cap the reading measure on an INNER wrapper only for text-heavy views, e.g. `<main class=\"min-h-screen\"><div class=\"mx-auto max-w-2xl\">…</div></main>`. Mutually exclusive with `edits`."New value: +"Full-source path only. Optional replacement frontend JavaScript. Follow the create_app rules for the selected contract. Existing-contract UI must (a) populate #app innerHTML BEFORE attaching event listeners, and (b) call operations as `window.charming.api('<manifest-id>').<opName>(input)` (operation name = method name, strips the { ok, value } envelope — you receive the value only, never ok) — NOT `api.operation(name, params)` and NOT raw fetch(). The signed-in caller is exposed synchronously as `window.charming.user` (`{ id, handle?, name?, image? }`, or `null` for an anonymous visitor) — read `user.name` to greet, attribute, or personalize, instead of asking the user to type their name. PUBLIC fields only (never email); it is a convenience signal, not enforcement. The outermost container MUST fill the viewport — use `<main class=\"min-h-screen\">` (or a grid/flex layout that spans width) as the root shell. Do NOT wrap the root in `max-w-md`, `max-w-2xl`, or `container mx-auto`: those cap the entire app to a narrow central column with wide empty margins on 2K+ monitors. Cap the reading measure on an INNER wrapper only for text-heavy views, e.g. `<main class=\"min-h-screen\"><div class=\"mx-auto max-w-2xl\">…</div></main>`. Mutually exclusive with `edits`."
    • addedOutput schema / anyOf
      Added value: +[
      +  {
      +    "$schema": "https://json-schema.org/draft/2020-12/schema",
      +    "additionalProperties": false,
      +    "properties": {
      +      "advisories": {
      +        "description": "Structured non-fatal advisories, including authoring issues and unread staff feedback responses. Each advisory.summary is also appended to the text content for the LLM path.",
      +        "items": {
      +          "additionalProperties": false,
      +          "properties": {
      +            "data": {
      +              "additionalProperties": {},
      +              "description": "Kind-specific structured payload. Shape varies per advisory kind.",
      +              "propertyNames": {
      +                "type": "string"
      +              },
      +              "type": "object"
      +            },
      +            "doc_url": {
      +              "description": "Optional docs pointer for this advisory kind.",
      +              "type": "string"
      +            },
      +            "kind": {
      +              "description": "Stable advisory identifier (e.g. \"legacy-bridge\").",
      +              "type": "string"
      +            },
      +            "severity": {
      +              "description": "Severity; omitted advisories are treated as 'info'.",
      +              "enum": [
      +                "info",
      +                "warn"
      +              ],
      +              "type": "string"
      +            },
      +            "summary": {
      +              "description": "Agent-facing summary. Self-sufficient; no extra context required.",
      +              "type": "string"
      +            },
      +            "userSummary": {
      +              "description": "End-user-facing summary. Set when the advisory should render in-app.",
      +              "type": "string"
      +            }
      +          },
      +          "required": [
      +            "kind",
      +            "summary"
      +          ],
      +          "type": "object"
      +        },
      +        "type": "array"
      +      },
      +      "appName": {
      +        "description": "URL-safe slug for the app, unique per owner. Distinct from manifestId and displayName, and stable across edits — use rename_app to change it (the title changing does NOT change the slug).",
      +        "type": [
      +          "string",
      +          "null"
      +        ]
      +      },
      +      "icon": {
      +        "additionalProperties": false,
      +        "description": "The effective home-screen icon stored for the app. Canonical source declares it at `manifest.meta.icon`.",
      +        "properties": {
      +          "bg": {
      +            "description": "The icon background as a hex color (e.g. \"#1d8a4e\").",
      +            "type": "string"
      +          },
      +          "emoji": {
      +            "description": "The single emoji rendered on the icon.",
      +            "type": "string"
      +          }
      +        },
      +        "required": [
      +          "emoji",
      +          "bg"
      +        ],
      +        "type": "object"
      +      },
      +      "id": {
      +        "description": "UUID of the created or updated app.",
      +        "type": "string"
      +      },
      +      "ok": {
      +        "const": true,
      +        "description": "Indicates success. Errors arrive as content with isError:true.",
      +        "type": "boolean"
      +      },
      +      "revision": {
      +        "description": "Server-owned app source revision. Historical null counters read as 0; new apps start at 1; each successful source write advances it once. Pass this value through `expected_revision` when guarding update_app.",
      +        "maximum": 9007199254740991,
      +        "minimum": 0,
      +        "type": "integer"
      +      },
      +      "shareUrl": {
      +        "description": "The link to give humans whenever the user wants to open or share the app. Friendly /<handle>/<app-name> form when the OWNER has a live handle and the app a slug, /app/<uuid> otherwise. Always token-free — safe to show, paste, and send. Not an API base: appending /api/<op> to the friendly form 404s; use `url` for machine calls.",
      +        "type": "string"
      +      },
      +      "url": {
      +        "description": "Machine/API URL for the app (stable /app/<uuid> form). Embeds a write-capable ?t= access token — NEVER show, paste, or send it to the user. Append /api/<op> to its path for out-of-band operation calls. For anything user-facing, use shareUrl instead.",
      +        "type": "string"
      +      },
      +      "warnings": {
      +        "description": "Non-blocking publish feedback (#1126): present when static validation found UI/backend contract mismatches or legacy icon input needed a fallback. The write succeeded; fix the named source field.",
      +        "items": {
      +          "type": "string"
      +        },
      +        "type": "array"
      +      }
      +    },
      +    "required": [
      +      "ok",
      +      "id",
      +      "url",
      +      "shareUrl",
      +      "revision",
      +      "icon"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "$schema": "https://json-schema.org/draft/2020-12/schema",
      +    "additionalProperties": false,
      +    "properties": {
      +      "acceptedAt": {
      +        "type": "string"
      +      },
      +      "activeRevision": {
      +        "maximum": 9007199254740991,
      +        "minimum": 0,
      +        "type": "integer"
      +      },
      +      "appId": {
      +        "description": "Existing target app ID, or the created app ID after publication.",
      +        "type": "string"
      +      },
      +      "attempts": {
      +        "maximum": 9007199254740991,
      +        "minimum": 0,
      +        "type": "integer"
      +      },
      +      "buildId": {
      +        "description": "Durable build ID. Use get_app_build to inspect progress or source.",
      +        "type": "string"
      +      },
      +      "deadline": {
      +        "type": "string"
      +      },
      +      "desiredRevision": {
      +        "maximum": 9007199254740991,
      +        "minimum": 0,
      +        "type": "integer"
      +      },
      +      "elapsedMs": {
      +        "minimum": 0,
      +        "type": "number"
      +      },
      +      "error": {
      +        "additionalProperties": false,
      +        "properties": {
      +          "column": {
      +            "maximum": 9007199254740991,
      +            "minimum": -9007199254740991,
      +            "type": "integer"
      +          },
      +          "kind": {
      +            "type": "string"
      +          },
      +          "line": {
      +            "maximum": 9007199254740991,
      +            "minimum": -9007199254740991,
      +            "type": "integer"
      +          },
      +          "message": {
      +            "type": "string"
      +          },
      +          "retryable": {
      +            "type": "boolean"
      +          },
      +          "specifier": {
      +            "type": "string"
      +          },
      +          "target": {
      +            "type": "string"
      +          }
      +        },
      +        "required": [
      +          "kind",
      +          "message",
      +          "retryable"
      +        ],
      +        "type": "object"
      +      },
      +      "finishedAt": {
      +        "type": [
      +          "string",
      +          "null"
      +        ]
      +      },
      +      "idempotencyExpiresAt": {
      +        "type": [
      +          "string",
      +          "null"
      +        ]
      +      },
      +      "inputDigest": {
      +        "type": "string"
      +      },
      +      "inspectionExpiresAt": {
      +        "type": [
      +          "string",
      +          "null"
      +        ]
      +      },
      +      "intent": {
      +        "description": "The accepted operation: create, update, migrate, restore, or copy.",
      +        "type": "string"
      +      },
      +      "lockDigest": {
      +        "type": "string"
      +      },
      +      "lockState": {
      +        "enum": [
      +          "locked",
      +          "unresolved"
      +        ],
      +        "type": "string"
      +      },
      +      "ok": {
      +        "const": true,
      +        "type": "boolean"
      +      },
      +      "queueDeadline": {
      +        "type": "string"
      +      },
      +      "resolvedDependencies": {
      +        "additionalProperties": false,
      +        "properties": {
      +          "client": {
      +            "additionalProperties": {
      +              "type": "string"
      +            },
      +            "propertyNames": {
      +              "type": "string"
      +            },
      +            "type": "object"
      +          },
      +          "server": {
      +            "additionalProperties": {
      +              "type": "string"
      +            },
      +            "propertyNames": {
      +              "type": "string"
      +            },
      +            "type": "object"
      +          }
      +        },
      +        "required": [
      +          "server",
      +          "client"
      +        ],
      +        "type": "object"
      +      },
      +      "retryAfterSeconds": {
      +        "description": "Wait at least this many seconds before polling again.",
      +        "exclusiveMinimum": 0,
      +        "maximum": 9007199254740991,
      +        "type": "integer"
      +      },
      +      "revision": {
      +        "description": "Published app source revision.",
      +        "exclusiveMinimum": 0,
      +        "maximum": 9007199254740991,
      +        "type": "integer"
      +      },
      +      "source": {
      +        "additionalProperties": false,
      +        "description": "Exact accepted source, returned only when include_source is true.",
      +        "properties": {
      +          "description": {
      +            "type": [
      +              "string",
      +              "null"
      +            ]
      +          },
      +          "module": {
      +            "type": "string"
      +          },
      +          "styles": {
      +            "type": [
      +              "string",
      +              "null"
      +            ]
      +          },
      +          "ui": {
      +            "type": [
      +              "string",
      +              "null"
      +            ]
      +          }
      +        },
      +        "required": [
      +          "module",
      +          "ui",
      +          "styles",
      +          "description"
      +        ],
      +        "type": "object"
      +      },
      +      "sourceEtag": {
      +        "description": "ETag identifying the immutable input accepted for this build.",
      +        "type": "string"
      +      },
      +      "state": {
      +        "enum": [
      +          "queued",
      +          "resolving",
      +          "building",
      +          "validating",
      +          "published",
      +          "failed",
      +          "superseded",
      +          "canceled",
      +          "expired"
      +        ],
      +        "type": "string"
      +      },
      +      "statusUrl": {
      +        "description": "Authenticated HTTP status URL for this build.",
      +        "type": "string"
      +      },
      +      "updatedAt": {
      +        "type": "string"
      +      },
      +      "url": {
      +        "description": "App URL. Present only after successful publication.",
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "ok",
      +      "buildId",
      +      "intent",
      +      "state",
      +      "sourceEtag",
      +      "statusUrl",
      +      "attempts",
      +      "acceptedAt",
      +      "updatedAt",
      +      "finishedAt",
      +      "queueDeadline",
      +      "deadline",
      +      "inspectionExpiresAt",
      +      "idempotencyExpiresAt",
      +      "elapsedMs",
      +      "lockState",
      +      "inputDigest"
      +    ],
      +    "type": "object"
      +  }
      +]
    • addedOutput schema / properties / acceptedAt
      Added value: +{
      +  "type": "string"
      +}
    • addedOutput schema / properties / activeRevision
      Added value: +{
      +  "maximum": 9007199254740991,
      +  "minimum": 0,
      +  "type": "integer"
      +}
    • changedOutput schema / properties / advisories / description
      Previous value: -"Structured advisories attached when the tool detected a non-fatal authoring issue (e.g. legacy bridge usage). Each advisory.summary is also appended to the text content for the LLM path."New value: +"Structured non-fatal advisories, including authoring issues and unread staff feedback responses. Each advisory.summary is also appended to the text content for the LLM path."
    • addedOutput schema / properties / appId
      Added value: +{
      +  "description": "Existing target app ID, or the created app ID after publication.",
      +  "type": "string"
      +}
    • removedOutput schema / properties / appName / anyOf
      Removed value: -[
      -  {
      -    "type": "string"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • addedOutput schema / properties / appName / type
      Added value: +[
      +  "string",
      +  "null"
      +]
    • addedOutput schema / properties / attempts
      Added value: +{
      +  "maximum": 9007199254740991,
      +  "minimum": 0,
      +  "type": "integer"
      +}
    • addedOutput schema / properties / buildId
      Added value: +{
      +  "description": "Durable build ID. Use get_app_build to inspect progress or source.",
      +  "type": "string"
      +}
    • addedOutput schema / properties / deadline
      Added value: +{
      +  "type": "string"
      +}
    • addedOutput schema / properties / desiredRevision
      Added value: +{
      +  "maximum": 9007199254740991,
      +  "minimum": 0,
      +  "type": "integer"
      +}
    • addedOutput schema / properties / elapsedMs
      Added value: +{
      +  "minimum": 0,
      +  "type": "number"
      +}
    • addedOutput schema / properties / error
      Added value: +{
      +  "additionalProperties": false,
      +  "properties": {
      +    "column": {
      +      "maximum": 9007199254740991,
      +      "minimum": -9007199254740991,
      +      "type": "integer"
      +    },
      +    "kind": {
      +      "type": "string"
      +    },
      +    "line": {
      +      "maximum": 9007199254740991,
      +      "minimum": -9007199254740991,
      +      "type": "integer"
      +    },
      +    "message": {
      +      "type": "string"
      +    },
      +    "retryable": {
      +      "type": "boolean"
      +    },
      +    "specifier": {
      +      "type": "string"
      +    },
      +    "target": {
      +      "type": "string"
      +    }
      +  },
      +  "required": [
      +    "kind",
      +    "message",
      +    "retryable"
      +  ],
      +  "type": "object"
      +}
    • addedOutput schema / properties / finishedAt
      Added value: +{
      +  "type": [
      +    "string",
      +    "null"
      +  ]
      +}
    • changedOutput schema / properties / icon / description
      Previous value: -"The effective home-screen icon stored for the app, after normalization. Read this back to confirm what stuck: if you sent a `manifest.icon` and this is the default `{ emoji: \"🧱\", bg: \"#3b82f6\" }`, your icon was invalid and rejected — see `warnings`."New value: +"The effective home-screen icon stored for the app. Canonical source declares it at `manifest.meta.icon`."
    • addedOutput schema / properties / idempotencyExpiresAt
      Added value: +{
      +  "type": [
      +    "string",
      +    "null"
      +  ]
      +}
    • addedOutput schema / properties / inputDigest
      Added value: +{
      +  "type": "string"
      +}
    • addedOutput schema / properties / inspectionExpiresAt
      Added value: +{
      +  "type": [
      +    "string",
      +    "null"
      +  ]
      +}
    • addedOutput schema / properties / intent
      Added value: +{
      +  "description": "The accepted operation: create, update, migrate, restore, or copy.",
      +  "type": "string"
      +}
    • addedOutput schema / properties / lockDigest
      Added value: +{
      +  "type": "string"
      +}
    • addedOutput schema / properties / lockState
      Added value: +{
      +  "enum": [
      +    "locked",
      +    "unresolved"
      +  ],
      +  "type": "string"
      +}
    • removedOutput schema / properties / ok / description
      Removed value: -"Indicates success. Errors arrive as content with isError:true."
    • addedOutput schema / properties / queueDeadline
      Added value: +{
      +  "type": "string"
      +}
    • addedOutput schema / properties / resolvedDependencies
      Added value: +{
      +  "additionalProperties": false,
      +  "properties": {
      +    "client": {
      +      "additionalProperties": {
      +        "type": "string"
      +      },
      +      "propertyNames": {
      +        "type": "string"
      +      },
      +      "type": "object"
      +    },
      +    "server": {
      +      "additionalProperties": {
      +        "type": "string"
      +      },
      +      "propertyNames": {
      +        "type": "string"
      +      },
      +      "type": "object"
      +    }
      +  },
      +  "required": [
      +    "server",
      +    "client"
      +  ],
      +  "type": "object"
      +}
    • addedOutput schema / properties / retryAfterSeconds
      Added value: +{
      +  "description": "Wait at least this many seconds before polling again.",
      +  "exclusiveMinimum": 0,
      +  "maximum": 9007199254740991,
      +  "type": "integer"
      +}
    • addedOutput schema / properties / revision
      Added value: +{
      +  "description": "Server-owned app source revision. Historical null counters read as 0; new apps start at 1; each successful source write advances it once. Pass this value through `expected_revision` when guarding update_app.",
      +  "maximum": 9007199254740991,
      +  "minimum": 0,
      +  "type": "integer"
      +}
    • addedOutput schema / properties / source
      Added value: +{
      +  "additionalProperties": false,
      +  "description": "Exact accepted source, returned only when include_source is true.",
      +  "properties": {
      +    "description": {
      +      "type": [
      +        "string",
      +        "null"
      +      ]
      +    },
      +    "module": {
      +      "type": "string"
      +    },
      +    "styles": {
      +      "type": [
      +        "string",
      +        "null"
      +      ]
      +    },
      +    "ui": {
      +      "type": [
      +        "string",
      +        "null"
      +      ]
      +    }
      +  },
      +  "required": [
      +    "module",
      +    "ui",
      +    "styles",
      +    "description"
      +  ],
      +  "type": "object"
      +}
    • addedOutput schema / properties / sourceEtag
      Added value: +{
      +  "description": "ETag identifying the immutable input accepted for this build.",
      +  "type": "string"
      +}
    • addedOutput schema / properties / state
      Added value: +{
      +  "enum": [
      +    "queued",
      +    "resolving",
      +    "building",
      +    "validating",
      +    "published",
      +    "failed",
      +    "superseded",
      +    "canceled",
      +    "expired"
      +  ],
      +  "type": "string"
      +}
    • addedOutput schema / properties / statusUrl
      Added value: +{
      +  "description": "Authenticated HTTP status URL for this build.",
      +  "type": "string"
      +}
    • addedOutput schema / properties / updatedAt
      Added value: +{
      +  "type": "string"
      +}
    • changedOutput schema / properties / url / description
      Previous value: -"Machine/API URL for the app (stable /app/<uuid> form). Embeds a write-capable ?t= access token — NEVER show, paste, or send it to the user. Append /api/<op> to its path for out-of-band operation calls. For anything user-facing, use shareUrl instead."New value: +"App URL. Present only after successful publication."
    • removedOutput schema / properties / version
      Removed value: -{
      -  "description": "Monotonic int version of the app. 0 on truly-legacy rows that predate versioning (still editable — pass `expected_version: 0`); otherwise a positive int bumped on every successful update_app/PUT. Pass back as `expected_version` on edits-aware update_app and as `If-Match: \"v<N>\"` on PATCH /app/:id/source.",
      -  "maximum": 9007199254740991,
      -  "minimum": -9007199254740991,
      -  "type": "integer"
      -}
    • changedOutput schema / properties / warnings / description
      Previous value: -"Non-blocking publish feedback (#1126): present when static validation found UI/backend contract mismatches, or when a provided `manifest.icon` was invalid and coerced to the default. The write succeeded; fix by adding the backend op, renaming the UI call, or correcting the icon `{ emoji, bg }`."New value: +"Non-blocking publish feedback (#1126): present when static validation found UI/backend contract mismatches or legacy icon input needed a fallback. The write succeeded; fix the named source field."
    • changedOutput schema / required
      Previous value: -[
      -  "ok",
      -  "id",
      -  "url",
      -  "shareUrl",
      -  "version",
      -  "icon"
      -]New value: +[
      +  "ok"
      +]
  2. Changed1 schema field changed
    • changedInput schema / properties / module / description
      Previous value: -"Full-source path only. Optional replacement ES module source. Same contract as create_app: must export a `manifest` with capabilities.imports including \"buildy:storage/kv@1.0\" for any app that persists state, plus a default.fetch handler that routes /api/<opName> requests using env.storage.get/put. Keep all persisted state in env.storage — do not move it into localStorage/sessionStorage/IndexedDB, which are empty inside Claude/ChatGPT and lose the data. env.storage stores JSON-compatible values directly; do not JSON.stringify before put or JSON.parse after get. Mutually exclusive with `edits`."New value: +"Full-source path only. Optional replacement ES module source. Same contract as create_app: must export a `manifest` with capabilities.imports including \"buildy:storage/kv@1.0\" for any app that persists state, plus route handlers that use env.storage.get/put. default.fetch is optional; Charming supplies a generic 404 handler when it is absent. Keep all persisted state in env.storage — do not move it into localStorage/sessionStorage/IndexedDB, which are empty inside Claude/ChatGPT and lose the data. env.storage stores JSON-compatible values directly; do not JSON.stringify before put or JSON.parse after get. Mutually exclusive with `edits`."
  3. First observed

TDQS

A4.3/5.0
Behavior5/5

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

The description richly discloses behavior: synchronous publishing for existing-contract saves, async ESM builds that keep the last active app running, atomic edits with revision gating, and the warning that `url` is machine-only with a write-capable token while `shareUrl` is for users. This goes well beyond the annotations' destructive/openWorld hints and does not contradict them.

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 but front-loaded with the core update purpose and sync/async behavior, and every paragraph adds new guidance (migration, docs reference, sharing semantics). It earns its length for a 10-parameter tool, though it repeats some 'mutually exclusive' information that already lives in the schema.

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 tool this complex, the description covers input shapes, concurrency controls, polling, contract migration, output fields, and a pointer to full docs, while the output schema covers return values. Nothing an agent needs in order to call 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?

With 100% schema coverage, the schema already documents the parameters, and the description builds on it by explaining when to pass an updated `description`, when `idempotency_key`/`expected_revision` are required, and how `shareUrl` vs `url` relate to the result. It adds cross-cutting semantics without needing to re-document each parameter.

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 a specific verb+resource ('update an existing Charming app identified by UUID') and immediately lays out the two input shapes, so an agent knows what the tool does. It distinguishes from create_app by emphasizing existing apps, but it never differentiates from the sibling mutate_app, so it is not quite a 5.

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 gives concrete when-to-use context: existing-contract saves publish synchronously, ESM saves require idempotency_key/expected_revision and should be polled via get_app_build, and full-source vs edits[] are mutually exclusive. It does not explicitly name alternatives or say when not to use this tool versus mutate_app or other siblings.

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

Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.