Update App
MCP contract for update_app.
MCP name: update_app
Tool contract
{
"_meta": {
"ui": {
"resourceUri": "ui://charming/app-v2"
},
"ui/resourceUri": "ui://charming/app-v2"
},
"annotations": {
"destructiveHint": true,
"openWorldHint": true,
"readOnlyHint": false,
"title": "Update App"
},
"description": "Save the whole tree as `files`, or exact edits that each name a `path`, with idempotency_key and expected_revision from a fresh get_app_source; the save returns a durable buildId promptly and keeps the last active app running while the build completes. Poll with get_app_build. The package contract is dated 2026-10-25 and frozen. Use this to change an existing Charming app by UUID. Pass either the whole replacement tree as `files` or `edits`, exact find-and-replace operations that each name a `path`, with `expected_revision` from your last get_app_source read. Files follow the same rules as create_app; read the build guide with read_docs({ path: \"build-mcp.md\" }) before writing them. The description lives in package.json, so change it there. An app saved before packages still takes `module`, `ui`, and `styles` or edits that name a `bucket`; fix it that way, and send `files` with `migrate_contract: true` only when you mean to migrate it. Give the user `shareUrl`, never `url`, which carries 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. Canonical Apps only; a package App names a `path` instead.",
"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"
},
"path": {
"description": "Package Apps only: the file the edit applies to, such as `server.js` or `lib/save.js`. The file must already exist; add files with a full `files` save.",
"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": [
"old_string",
"new_string"
],
"type": "object"
},
"type": "array"
},
"expected_revision": {
"description": "Revision precondition. Required for every package save and for canonical-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"
},
"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"
},
"files": {
"additionalProperties": {
"type": "string"
},
"description": "Full-source path for a package App: its whole tree, replacing every file. The App as a package: a map from path to text holding `package.json` and `server.js`, plus optional `ui.js`, `styles.css`, and any other .js, .mjs, or .json files they import with relative paths. package.json follows npm: `name` is the App's id, `dependencies` holds public npm packages for both targets, and Charming facts sit under `charming` (`name`, `icon`, `permissions`); it may declare `\"$schema\": \"https://charm.ing/schema/app-package/2026-10-25.json\"`. server.js exports `routes` and imports what it uses, such as `import { kv } from 'charming:storage/[email protected]'`; capabilities come from those imports, never from a declaration. ui.js imports `{ api, onStateChange }` from 'charming:ui/[email protected]'. Mutually exclusive with module, ui, styles, and display_name. To call a person's connected service, such as Gmail, declare its Composio tool slugs in package.json under `charming.permissions.server.connectors` (for example `{ gmail: ['GMAIL_FETCH_EMAILS', 'GMAIL_FETCH_MESSAGE_BY_MESSAGE_ID'] }`), `import { connectors } from 'charming:[email protected]'`, and call `connectors.call(service, tool, args)` inside a route handler; on a `connector_not_granted` refusal the UI calls `charming.connect(service)` from a click. Never write a person's service data into the App's shared storage, where everyone using the App can read it. See read_docs({ path: \"capabilities/connectors.md\" }). The package contract is dated 2026-10-25 and frozen. Mutually exclusive with module, ui, styles, and edits.",
"propertyNames": {
"type": "string"
},
"type": "object"
},
"idempotency_key": {
"description": "Required for package saves. Retry the exact request with the same key; use a new key and current expected_revision for a new save.",
"type": "string"
},
"migrate_contract": {
"description": "Set true, with `files` and `expected_revision`, to move an app saved before packages onto the package contract; see read_docs({ path: \"guides/migrate-to-packages.md\" }). Source submissions do not roll back contracts. History can explicitly restore a retained validated revision.",
"type": "boolean"
},
"module": {
"description": "Full-source path only. Replacement backend source, following the build-mcp.md rules. Mutually exclusive with `edits`. Migrating a legacy app to the dated contract requires the complete canonical manifest with the exact dated `$schema: \"https://charm.ing/schema/app-manifest/2026-10-16.json\"` plus `migrate_contract: true`; on this full-source path only, an omitted `$schema` is inserted automatically, but a wrong one still fails. Only for an app saved before packages. Passing `files` with `migrate_contract: true`, `expected_revision`, and `idempotency_key` moves it onto the package contract; see read_docs({ path: \"guides/migrate-to-packages.md\" }).",
"type": "string"
},
"styles": {
"description": "Full-source path only. Optional replacement CSS. Mutually exclusive with `edits`.",
"type": "string"
},
"ui": {
"description": "Full-source path only. Replacement frontend JavaScript. Standard browser APIs such as `fetch` and `localStorage` work differently in Charming or may be unavailable; read build-mcp.md before writing it. 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,
"anyOf": [
{
"$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"
},
"details": {
"anyOf": [
{
"additionalProperties": false,
"properties": {
"execution": {
"enum": [
"not_started",
"may_have_run"
],
"type": "string"
},
"operationId": {
"minLength": 1,
"type": "string"
},
"reservationId": {
"minLength": 1,
"type": "string"
}
},
"required": [
"execution",
"operationId"
],
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"execution": {
"const": "may_have_run",
"type": "string"
}
},
"required": [
"execution"
],
"type": "object"
}
]
},
"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": {
"type": "string"
},
"description": "Exact package versions the build resolved for the declared dependencies.",
"propertyNames": {
"type": "string"
},
"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": {
"files": {
"additionalProperties": {
"type": "string"
},
"description": "The accepted file tree: package.json, server.js, and any other files.",
"propertyNames": {
"type": "string"
},
"type": "object"
}
},
"required": [
"files"
],
"type": "object"
},
"sourceEtag": {
"description": "The quoted ETag of the revision this build saved once an App exists; an HTTP save passes it as If-Match. It doesn't change after a later save; get_app_source returns the current revision. Before a creation publishes, the digest of the accepted input.",
"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"
}
],
"properties": {
"acceptedAt": {
"type": "string"
},
"activeRevision": {
"maximum": 9007199254740991,
"minimum": 0,
"type": "integer"
},
"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"
},
"appId": {
"description": "Existing target app ID, or the created app ID after publication.",
"type": "string"
},
"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"
]
},
"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"
},
"details": {
"anyOf": [
{
"additionalProperties": false,
"properties": {
"execution": {
"enum": [
"not_started",
"may_have_run"
],
"type": "string"
},
"operationId": {
"minLength": 1,
"type": "string"
},
"reservationId": {
"minLength": 1,
"type": "string"
}
},
"required": [
"execution",
"operationId"
],
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"execution": {
"const": "may_have_run",
"type": "string"
}
},
"required": [
"execution"
],
"type": "object"
}
]
},
"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"
]
},
"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"
},
"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": {
"type": "string"
},
"description": "Exact package versions the build resolved for the declared dependencies.",
"propertyNames": {
"type": "string"
},
"type": "object"
},
"retryAfterSeconds": {
"description": "Wait at least this many seconds before polling again.",
"exclusiveMinimum": 0,
"maximum": 9007199254740991,
"type": "integer"
},
"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"
},
"source": {
"additionalProperties": false,
"description": "Exact accepted source, returned only when include_source is true.",
"properties": {
"files": {
"additionalProperties": {
"type": "string"
},
"description": "The accepted file tree: package.json, server.js, and any other files.",
"propertyNames": {
"type": "string"
},
"type": "object"
}
},
"required": [
"files"
],
"type": "object"
},
"sourceEtag": {
"description": "The quoted ETag of the revision this build saved once an App exists; an HTTP save passes it as If-Match. It doesn't change after a later save; get_app_source returns the current revision. Before a creation publishes, the digest of the accepted input.",
"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"
},
"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"
],
"type": "object"
}
}
ChatGPT metadata
ChatGPT adds or replaces these fields for progress messages and inline app rendering.
{
"_meta": {
"openai/outputTemplate": "ui://charming/chatgpt/app-v2",
"openai/toolInvocation/invoked": "App updated",
"openai/toolInvocation/invoking": "Updating app…",
"ui": {
"resourceUri": "ui://charming/chatgpt/app-v2"
},
"ui/resourceUri": "ui://charming/chatgpt/app-v2"
}
}