Skip to content
Charming Docs
Esc
navigateopen⌘Jpreview
On this page

Update App

Update an existing Charming app and render the fresh revision inline.

MCP name: update_app

Tool contract

{
  "_meta": {
    "ui": {
      "resourceUri": "ui://charming/app"
    },
    "ui/resourceUri": "ui://charming/app"
  },
  "annotations": {
    "destructiveHint": true,
    "openWorldHint": true,
    "readOnlyHint": false,
    "title": "Update App"
  },
  "description": "Update an existing Charming app and render the fresh revision inline. Use this to edit an app the caller already owns, identified by UUID. This tool call is synchronous: when it returns, the updated app is already live — no process to wait for, no deployment queue. Two mutually exclusive input shapes: (A) full-source — pass module/ui/styles to replace those buckets, following the same authoring rules as create_app (documented on those parameters and in the charming:app-guide prompt); (B) edits[] — exact-string find/replace operations plus the revision carried by `expected_revision` from your last read, much cheaper for small changes. 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. When telling the user where to open or share the app, always give them `shareUrl` from the result — never the `url` field, which is machine-only and embeds a write-capable access token.",
  "execution": {
    "taskSupport": "forbidden"
  },
  "inputSchema": {
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "properties": {
      "app_id": {
        "description": "The app ID (UUID) to update",
        "format": "uuid",
        "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
        "type": "string"
      },
      "description": {
        "anyOf": [
          {
            "maxLength": 500,
            "type": "string"
          },
          {
            "type": "null"
          }
        ],
        "description": "Optional 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."
      },
      "edits": {
        "description": "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`).",
        "items": {
          "properties": {
            "bucket": {
              "description": "Which source bucket the edit applies to.",
              "enum": [
                "module",
                "ui",
                "styles"
              ],
              "type": "string"
            },
            "new_string": {
              "description": "Replacement string. Empty string deletes the matched region.",
              "type": "string"
            },
            "old_string": {
              "description": "Exact substring to find. Must appear once in the bucket unless `replace_all: true`. Whitespace must match exactly — re-read with get_app_source if your local copy might be stale.",
              "type": "string"
            },
            "replace_all": {
              "default": false,
              "description": "When true, every occurrence in the bucket is replaced. When false (default), the edit fails with `old_string_not_unique` if the string matches more than once.",
              "type": "boolean"
            }
          },
          "required": [
            "bucket",
            "old_string",
            "new_string"
          ],
          "type": "object"
        },
        "type": "array"
      },
      "expected_revision": {
        "description": "Revision precondition. Required with `edits[]` and optional for full-source updates. Pass `revision` from your last get_app_source or update_app response. If the stored revision has advanced, the update is rejected with `revision_mismatch` and the current revision is surfaced so you can re-read and retry.",
        "maximum": 9007199254740991,
        "minimum": -9007199254740991,
        "type": "integer"
      },
      "expected_version": {
        "description": "Deprecated compatibility input. Use `expected_revision`; while legacy hosts remain active, expected_version maps to the same revision precondition.",
        "maximum": 9007199254740991,
        "minimum": -9007199254740991,
        "type": "integer"
      },
      "migrate_contract": {
        "description": "Set true only when replacing a legacy app with a complete valid dated manifest and routes contract. The migration is one-way.",
        "type": "boolean"
      },
      "module": {
        "description": "Full-source path only. Optional replacement ES module source. Same contract as create_app: must export a `manifest` with capabilities.imports including \"charming: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`.",
        "type": "string"
      },
      "styles": {
        "description": "Full-source path only. Optional replacement CSS. Mutually exclusive with `edits`.",
        "type": "string"
      },
      "ui": {
        "description": "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.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`.",
        "type": "string"
      }
    },
    "required": [
      "app_id"
    ],
    "type": "object"
  },
  "name": "update_app",
  "outputSchema": {
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "additionalProperties": false,
    "properties": {
      "advisories": {
        "description": "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.",
        "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": {
        "anyOf": [
          {
            "type": "string"
          },
          {
            "type": "null"
          }
        ],
        "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)."
      },
      "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"
  }
}

ChatGPT metadata

ChatGPT adds or replaces these fields for progress messages and inline app rendering.

{
  "_meta": {
    "openai/outputTemplate": "ui://charming/chatgpt/app-v1",
    "openai/toolInvocation/invoked": "App updated",
    "openai/toolInvocation/invoking": "Updating app…",
    "ui": {
      "resourceUri": "ui://charming/chatgpt/app-v1"
    },
    "ui/resourceUri": "ui://charming/chatgpt/app-v1"
  }
}

Was this page helpful?