{
  "standard": {
    "prompts": [
      {
        "description": "Charming dynamic-runtime app authoring contract.",
        "name": "charming:app-guide"
      },
      {
        "description": "Charming visual playbook — the default look for generated app UIs (override when the user asks for a different vibe). Use this when authoring or restyling an app's `ui`.",
        "name": "charming:design-guide"
      }
    ],
    "resourceTemplates": [],
    "resources": [
      {
        "_meta": {
          "ui": {
            "csp": {
              "connectDomains": ["https://charm.ing", "https://cdn.jsdelivr.net"],
              "frameDomains": ["https://charm.ing"],
              "resourceDomains": [
                "https://fonts.googleapis.com",
                "https://fonts.gstatic.com",
                "https://cdn.jsdelivr.net"
              ]
            },
            "prefersBorder": false
          }
        },
        "description": "Interactive web app hosted by Charming",
        "mimeType": "text/html;profile=mcp-app",
        "name": "Buildy App Viewer",
        "uri": "ui://buildy/app"
      },
      {
        "_meta": {
          "ui": {
            "csp": {
              "connectDomains": ["https://charm.ing", "https://cdn.jsdelivr.net"],
              "frameDomains": ["https://charm.ing"],
              "resourceDomains": [
                "https://fonts.googleapis.com",
                "https://fonts.gstatic.com",
                "https://cdn.jsdelivr.net"
              ]
            },
            "prefersBorder": false
          }
        },
        "description": "Interactive web app hosted by Charming",
        "mimeType": "text/html;profile=mcp-app",
        "name": "Charming App Viewer",
        "uri": "ui://charming/app"
      }
    ],
    "server": {
      "capabilities": {
        "logging": {},
        "prompts": {
          "listChanged": true
        },
        "resources": {
          "listChanged": true
        },
        "tools": {
          "listChanged": true
        }
      },
      "info": {
        "icons": [
          {
            "mimeType": "image/png",
            "sizes": ["512x512"],
            "src": "https://charm.ing/charming-mark.png"
          }
        ],
        "name": "charming",
        "title": "Charming",
        "version": "0.1.0",
        "websiteUrl": "https://usecharming.com"
      },
      "instructions": null
    },
    "tools": [
      {
        "_meta": {
          "ui": {
            "resourceUri": "ui://charming/app"
          },
          "ui/resourceUri": "ui://charming/app"
        },
        "annotations": {
          "destructiveHint": true,
          "openWorldHint": true,
          "readOnlyHint": false,
          "title": "Create App"
        },
        "description": "Create a new interactive web app and render it inline. Use this to build a new app from a request. This tool call returns only after the app is live at the response URL. Do not wait or poll for deployment. If `module` reuses a `manifest.id` the caller already owns, the new app replaces the existing app module, UI, and styles in place; use update_app to edit a known app ID by UUID. Apps persist state through a backend `env.storage` (Workers KV) binding and call backend operations from the frontend via `window.charming.api`. The `module`, `ui`, and `description` parameter docs carry the authoring rules — including the fill-viewport outer-container rule on `ui`; the charming:app-guide prompt has the full guide and a canonical example. If a request needs in-app AI calls, external integrations, native apps, scheduled jobs, SQL, notifications, or view-only sharing, do not fake support. Build the closest local-state version, then log the gap with `submit_feedback`. The charming:app-guide prompt has alternatives. The charming:design-guide prompt documents Charming's default visual style for generated `ui` (single accent, warm neutrals, real type hierarchy; no gradients or decorative emoji) — a default that yields to any user-requested aesthetic. 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": {
            "description": {
              "description": "Search-friendly summary of what this app does, surfaced by list_apps so a future session can match user intent (e.g. \"open my protein tracker\", \"log food\") to this app even when display_name is ambiguous. Write 1-2 sentences covering: (a) what the app tracks/does, (b) the key actions it supports, and (c) synonyms or alternative phrasings the user might say. Example: \"Tracks daily protein intake. Log meals, view weekly totals, set a daily goal. Synonyms: meals, food log, nutrition tracker, calorie counter.\" Limit 500 characters.",
              "maxLength": 500,
              "type": "string"
            },
            "display_name": {
              "description": "Optional display name override",
              "type": "string"
            },
            "module": {
              "description": "ES module source for the app backend. Export a strict canonical `manifest` with `$schema: \"https://charm.ing/schema/app-manifest/2026-07-31.json\"`, `id`, and `meta: { name, icon? }`. The server may insert the exact schema URL on create when the rest of the source is canonical. Unknown manifest keys are rejected. To persist any state, `capabilities.imports` must include \"charming:storage/kv@1.0\" (without it `env.storage` is undefined and every read/write throws `storage capability not granted`). All persistence goes through `env.storage` using Workers KV semantics — `get(key)`, `put(key, value)`, `delete(key)`, `list()`; `.set`/`.add`/`.write`/`.setItem`/`.removeItem` do not exist and throw `TypeError: env.storage.<x> is not a function`. env.storage stores JSON-compatible values directly; do not JSON.stringify before put or JSON.parse after get. Use env.storage for ALL persistence — it is the only storage that survives inside Claude/ChatGPT and syncs across devices. Do NOT keep app data or user state in localStorage/sessionStorage/IndexedDB: those APIs are empty inside chat hosts, so their data silently vanishes there (the most common cause of an app that appears not to save). The \"charming:browser/storage@1.0\" capability (claim-gated) unlocks them but only for throwaway, web-only caching; anything the user expects to keep belongs in env.storage. Export a `routes` array with unique `op` values and `handler` functions. Canonical route fields are `inputSchema`, `outputSchema`, and `annotations`; `method` defaults to `POST`, `path` defaults to `/api/<op>`, input defaults to a closed empty-object schema, and `public` defaults to true. Set all four MCP annotation hints when their defaults do not fit; Charming does not infer them from the HTTP method. A default `fetch` handler is an unmatched-request fallback only and is not discoverable. Apps that use a sensitive browser capability must declare its import in `capabilities.imports` — \"charming:browser/microphone@1.0\" (getUserMedia audio), \"charming:browser/camera@1.0\" (getUserMedia video), \"charming:browser/geolocation@1.0\", \"charming:browser/clipboard-read@1.0\" (reading the clipboard), \"charming:browser/display-capture@1.0\" (getDisplayMedia screen share), \"charming:browser/midi@1.0\" (Web MIDI, navigator.requestMIDIAccess), \"charming:browser/device-motion@1.0\" (device orientation/motion: DeviceOrientationEvent/DeviceMotionEvent + iOS requestPermission), \"charming:browser/ambient-light@1.0\" (ambient light, new AmbientLightSensor), or \"charming:browser/storage@1.0\" (native client storage — localStorage/sessionStorage/IndexedDB — web-only); access is granted only after the app is claimed/authenticated. To call external HTTPS APIs from backend code, declare \"charming:network/fetch@1.0\" and list each exact origin in `manifest.permissions.server.fetch`; both are required and public-only. For an endpoint that needs an API key, declare \"charming:secrets/fetch@1.0\" to get `env.fetch` (claimed apps only) — a sealed outbound fetch that substitutes `{{secret:NAME}}` references in request HEADER values host-side, so the key never enters app source or the sandbox; the app OWNER sets the value in the dashboard at `/app/<id>/secrets` and the agent only references the NAME. Never embed API keys in source. To render remote images, list each exact https origin in `manifest.permissions.browser[\"img-src\"]`. To make an image render in ANY host — standalone, ChatGPT, AND Claude inline (their injected CSP blocks a cross-origin `<img src>`) — set the src from `const src = await window.charming.images.load(remoteUrl)` (it fetches through Charming and returns a `data:` URL every embed CSP allows). `window.charming.images.proxy(remoteUrl)` returns a same-origin proxy URL that works standalone/ChatGPT but NOT in Claude inline; prefer `images.load(...)` when the app may be embedded. Both enforce the declared origins; neither bypasses them.",
              "type": "string"
            },
            "styles": {
              "description": "Optional CSS for the rendered app",
              "type": "string"
            },
            "ui": {
              "description": "Frontend JavaScript rendered into #app. The #app mount point starts empty, so (a) populate its 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(), which fails in the null-origin srcdoc iframe. 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 — the loudest \"AI-generated app\" tell. If the view is text-heavy (a note, an article, a form with long prose), cap the reading measure on an INNER wrapper only, e.g. `<main class=\"min-h-screen\"><div class=\"mx-auto max-w-2xl\">…</div></main>`. Dashboards, kanban, tables, canvases, galleries, and split views should use the full width. For live updates when an agent mutates state from another session, register `window.charming.onStateChange((e) => { ... })` and update the DOM surgically rather than wiping #app. See the charming:app-guide prompt for a canonical example.",
              "type": "string"
            }
          },
          "required": ["module", "description"],
          "type": "object"
        },
        "name": "create_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"
        }
      },
      {
        "annotations": {
          "destructiveHint": true,
          "openWorldHint": true,
          "readOnlyHint": false,
          "title": "Delete App"
        },
        "description": "Permanently delete a Charming app the caller owns, optionally also purging its stored data. Use this to remove an app the caller no longer wants. Deletion is irreversible and requires confirmation: the host is asked to show a confirmation prompt, and when it cannot, the call fails closed until re-invoked with confirm: true.",
        "execution": {
          "taskSupport": "forbidden"
        },
        "inputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "properties": {
            "app_id": {
              "description": "The app ID (UUID) to delete",
              "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"
            },
            "confirm": {
              "description": "Explicit confirmation that the caller intends to permanently delete this app. Fallback for hosts that cannot show a confirmation prompt: set true only after the deletion has been confirmed with the user through another channel. On hosts that do support a prompt, the prompt is authoritative and this field is ignored.",
              "type": "boolean"
            },
            "purge_storage": {
              "description": "Also delete stored app data",
              "type": "boolean"
            }
          },
          "required": ["app_id"],
          "type": "object"
        },
        "name": "delete_app",
        "outputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "additionalProperties": false,
          "properties": {
            "cancelled": {
              "description": "Set to true when the user declined the elicitation confirmation prompt.",
              "type": "boolean"
            },
            "deleted": {
              "description": "True if the app was deleted, false if deletion was cancelled by the user.",
              "type": "boolean"
            },
            "id": {
              "description": "UUID of the targeted app.",
              "type": "string"
            },
            "ok": {
              "const": true,
              "description": "Indicates success. Errors arrive as content with isError:true.",
              "type": "boolean"
            }
          },
          "required": ["ok", "id", "deleted"],
          "type": "object"
        }
      },
      {
        "_meta": {
          "ui": {
            "resourceUri": "ui://charming/app"
          },
          "ui/resourceUri": "ui://charming/app"
        },
        "annotations": {
          "destructiveHint": false,
          "openWorldHint": false,
          "readOnlyHint": true,
          "title": "Get App"
        },
        "description": "Fetch an existing app by ID and render it inline. Use this to open, re-render, or continue working with an app the caller already has. Returns the live embedded UI plus its callable API operations, which another agent can invoke with query_app (read-only) or mutate_app (writes) to query or update saved app data. Unlike list_apps, which returns metadata including IDs and URLs, get_app renders the app; call list_apps first to find the ID. 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)",
              "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"
            }
          },
          "required": ["app_id"],
          "type": "object"
        },
        "name": "get_app",
        "outputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "additionalProperties": false,
          "properties": {
            "$schema": {
              "const": "https://charm.ing/schema/app-manifest/2026-07-31.json",
              "description": "Exact dated canonical manifest schema URL. Omitted for legacy apps.",
              "type": "string"
            },
            "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"
            },
            "api": {
              "additionalProperties": false,
              "description": "Full API surface — same shape as `_meta.charming.api`, but model-facing.",
              "properties": {
                "baseUrl": {
                  "description": "Origin + app prefix the operations live under.",
                  "type": "string"
                },
                "operations": {
                  "description": "Discovered routes in author order.",
                  "items": {
                    "additionalProperties": false,
                    "properties": {
                      "description": {
                        "description": "Author-supplied prose description.",
                        "type": "string"
                      },
                      "destructive": {
                        "description": "True when the route destroys data. Default false unless derived as such.",
                        "type": "boolean"
                      },
                      "discoveredFrom": {
                        "description": "Sources that contributed this operation (precedence order).",
                        "items": {
                          "enum": ["routes_export", "manifest_export", "module_route"],
                          "type": "string"
                        },
                        "type": "array"
                      },
                      "examples": {
                        "description": "Author-supplied calling examples that flow into the query_app/mutate_app snippet.",
                        "items": {
                          "additionalProperties": false,
                          "properties": {
                            "description": {
                              "type": "string"
                            },
                            "input": {},
                            "output": {}
                          },
                          "type": "object"
                        },
                        "type": "array"
                      },
                      "input": {
                        "description": "JSON Schema for the field. Canonical source declares it as `route.inputSchema` or `route.outputSchema`."
                      },
                      "manifestExport": {
                        "description": "Original capability-export string when discovered via `manifest_export`.",
                        "type": "string"
                      },
                      "method": {
                        "description": "HTTP method the route accepts.",
                        "enum": ["GET", "POST", "PUT", "PATCH", "DELETE"],
                        "type": "string"
                      },
                      "op": {
                        "description": "Operation name. Matches the `op` field on the corresponding Route.",
                        "type": "string"
                      },
                      "output": {
                        "description": "JSON Schema for the field. Canonical source declares it as `route.inputSchema` or `route.outputSchema`."
                      },
                      "path": {
                        "description": "Path under the app, e.g. `/api/increment`.",
                        "type": "string"
                      },
                      "public": {
                        "description": "True when the route is visible to discovery. Default true.",
                        "type": "boolean"
                      },
                      "readOnly": {
                        "description": "True when the route is a safe read (resolved from author intent + derivation).",
                        "type": "boolean"
                      },
                      "title": {
                        "description": "Short author-supplied title for the operation.",
                        "type": "string"
                      },
                      "tool": {
                        "description": "MCP tool to invoke this op: `query_app` for read-only, `mutate_app` otherwise.",
                        "enum": ["query_app", "mutate_app"],
                        "type": "string"
                      },
                      "url": {
                        "description": "Full URL (origin + app prefix + path).",
                        "type": "string"
                      }
                    },
                    "required": [
                      "op",
                      "method",
                      "path",
                      "url",
                      "readOnly",
                      "destructive",
                      "public",
                      "tool",
                      "discoveredFrom"
                    ],
                    "type": "object"
                  },
                  "type": "array"
                }
              },
              "required": ["baseUrl", "operations"],
              "type": "object"
            },
            "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)."
            },
            "appVersion": {
              "description": "Canonical apps return decimal String(revision). Legacy apps return their stored effective SemVer.",
              "type": "string"
            },
            "description": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Search-friendly summary of what the app does. Null on apps created before descriptions existed; agents are nudged to backfill on the next update_app."
            },
            "displayName": {
              "description": "Human-readable name shown in app listings and headers.",
              "type": "string"
            },
            "id": {
              "description": "UUID of the opened app.",
              "type": "string"
            },
            "manifestId": {
              "description": "Stable manifest identifier declared by the app module. Distinct from `id` (DB primary key) — surface this when discussing the app with the user.",
              "type": "string"
            },
            "manifestVersion": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Deprecated public contract marker. Canonical apps return null; legacy apps return their stored contract date."
            },
            "ok": {
              "const": true,
              "description": "Indicates success. Errors arrive as content with isError:true.",
              "type": "boolean"
            },
            "recentIssues": {
              "additionalProperties": false,
              "description": "Runtime failures recorded against the current app revision since it was published (#1133): contract misses, runtime JS errors, CSP violations, and failed API calls. Present only when the current revision has at least one such event — absence means a clean render. The same summary is appended to the text content. Fetch full detail with GET /app/:id/activity.",
              "properties": {
                "counts": {
                  "additionalProperties": false,
                  "description": "Issue counts per category.",
                  "properties": {
                    "apiFailures": {
                      "description": "Failed calls to the app API proxy.",
                      "maximum": 9007199254740991,
                      "minimum": -9007199254740991,
                      "type": "integer"
                    },
                    "appLoadErrors": {
                      "description": "Caught manifest-resolve / app load failures the bridge swallowed before they became an unhandled rejection.",
                      "maximum": 9007199254740991,
                      "minimum": -9007199254740991,
                      "type": "integer"
                    },
                    "contractMisses": {
                      "description": "Publish-time UI/backend contract validation failures.",
                      "maximum": 9007199254740991,
                      "minimum": -9007199254740991,
                      "type": "integer"
                    },
                    "cspViolations": {
                      "description": "Content-Security-Policy violations (e.g. an external image blocked by img-src).",
                      "maximum": 9007199254740991,
                      "minimum": -9007199254740991,
                      "type": "integer"
                    },
                    "runtimeErrors": {
                      "description": "Runtime JS errors and unhandled rejections reported by the rendered page.",
                      "maximum": 9007199254740991,
                      "minimum": -9007199254740991,
                      "type": "integer"
                    }
                  },
                  "required": [
                    "contractMisses",
                    "runtimeErrors",
                    "cspViolations",
                    "apiFailures",
                    "appLoadErrors"
                  ],
                  "type": "object"
                },
                "examples": {
                  "description": "Most recent example per non-empty category, newest first.",
                  "items": {
                    "additionalProperties": false,
                    "properties": {
                      "category": {
                        "description": "Which `counts` bucket this example illustrates.",
                        "enum": [
                          "contractMisses",
                          "runtimeErrors",
                          "cspViolations",
                          "apiFailures",
                          "appLoadErrors"
                        ],
                        "type": "string"
                      },
                      "kind": {
                        "description": "Underlying durable event kind: `diag_report` (runtime errors and CSP violations, distinguished by category), `contract_validation` (contract misses), `api_proxy_result` (API failures), or `app_load_error` (caught load failures).",
                        "type": "string"
                      },
                      "summary": {
                        "description": "Compact one-line description.",
                        "type": "string"
                      },
                      "ts": {
                        "description": "ISO-8601 timestamp of the event.",
                        "type": "string"
                      }
                    },
                    "required": ["ts", "category", "kind", "summary"],
                    "type": "object"
                  },
                  "type": "array"
                },
                "sinceRevision": {
                  "description": "The app revision the issues are attributed to (the current revision at call time). Legacy nulls normalize to 0.",
                  "maximum": 9007199254740991,
                  "minimum": 0,
                  "type": "integer"
                },
                "total": {
                  "description": "Issue events counted for the current revision, capped at 500 — a value of 500 means 500 or more. Use GET /app/:id/activity for the exact, paginated list.",
                  "maximum": 9007199254740991,
                  "minimum": -9007199254740991,
                  "type": "integer"
                }
              },
              "required": ["sinceRevision", "total", "counts", "examples"],
              "type": "object"
            },
            "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"
            }
          },
          "required": [
            "ok",
            "id",
            "url",
            "shareUrl",
            "displayName",
            "description",
            "manifestId",
            "revision",
            "appVersion",
            "manifestVersion",
            "api"
          ],
          "type": "object"
        }
      },
      {
        "annotations": {
          "destructiveHint": false,
          "openWorldHint": false,
          "readOnlyHint": true,
          "title": "Get App Source"
        },
        "description": "Return an app's raw persisted source (module, frontend ui script, and styles) plus its current revision for optimistic-concurrency edits. Pass that revision through update_app's `expected_revision` input. Use this when you need the source to inspect, debug, or edit an app the caller owns.",
        "execution": {
          "taskSupport": "forbidden"
        },
        "inputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "properties": {
            "app_id": {
              "description": "The app ID (UUID)",
              "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"
            }
          },
          "required": ["app_id"],
          "type": "object"
        },
        "name": "get_app_source",
        "outputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "additionalProperties": false,
          "properties": {
            "$schema": {
              "const": "https://charm.ing/schema/app-manifest/2026-07-31.json",
              "description": "Exact dated canonical manifest schema URL. Omitted for legacy apps.",
              "type": "string"
            },
            "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"
            },
            "appVersion": {
              "description": "Canonical apps return decimal String(revision). Legacy apps return their stored effective SemVer.",
              "type": "string"
            },
            "capabilities": {
              "additionalProperties": false,
              "description": "Capability declarations parsed from the app manifest.",
              "properties": {
                "exports": {
                  "description": "WIT-style capability ids the app exposes for cross-app calls.",
                  "items": {
                    "type": "string"
                  },
                  "type": "array"
                },
                "imageHosts": {
                  "description": "External https image hosts injected into the app CSP img-src (e.g. [\"covers.openlibrary.org\"]). Omitted when the app declares none.",
                  "items": {
                    "type": "string"
                  },
                  "type": "array"
                },
                "imports": {
                  "description": "WIT-style capability ids the app requests (e.g. \"charming:storage/kv@1.0\"). Empty if the app declares none.",
                  "items": {
                    "type": "string"
                  },
                  "type": "array"
                }
              },
              "required": ["imports", "exports"],
              "type": "object"
            },
            "claimed": {
              "description": "True if the app is owned by an authenticated account. False for anonymous apps still in the unclaimed grace window.",
              "type": "boolean"
            },
            "description": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Search-friendly summary set by the creating agent. Null on apps created before descriptions existed."
            },
            "displayName": {
              "description": "Human-readable name from the manifest.",
              "type": "string"
            },
            "expiresAt": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "ISO timestamp when an unclaimed app will be hard-deleted. Null for claimed apps."
            },
            "id": {
              "description": "UUID of the inspected app.",
              "type": "string"
            },
            "manifestId": {
              "description": "Effective manifest id. May differ from `sourceManifestId` when a conflicting id was rewritten on save.",
              "type": "string"
            },
            "manifestVersion": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Deprecated public contract marker. Canonical apps return null; legacy apps return their stored contract date."
            },
            "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"
            },
            "source": {
              "additionalProperties": false,
              "description": "Persisted source bundle. The same fields accepted by create_app/update_app.",
              "properties": {
                "module": {
                  "description": "Backend ES module source.",
                  "type": "string"
                },
                "styles": {
                  "anyOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "null"
                    }
                  ],
                  "description": "Optional CSS for the rendered app. Null if not set."
                },
                "ui": {
                  "anyOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "null"
                    }
                  ],
                  "description": "Frontend JavaScript rendered into the #app mount point. Null if not set."
                }
              },
              "required": ["module", "ui", "styles"],
              "type": "object"
            },
            "sourceManifestId": {
              "description": "Manifest id as authored in the persisted module source.",
              "type": "string"
            },
            "url": {
              "description": "Public app URL without an access token, built from the OWNER's handle when available (friendly form) and the UUID form otherwise. Safe to show the user. Browsers open via session cookie; agents should call get_app to mint a per-app token for write access.",
              "type": "string"
            }
          },
          "required": [
            "ok",
            "id",
            "url",
            "manifestId",
            "sourceManifestId",
            "displayName",
            "description",
            "revision",
            "appVersion",
            "manifestVersion",
            "capabilities",
            "claimed",
            "expiresAt",
            "source"
          ],
          "type": "object"
        }
      },
      {
        "annotations": {
          "destructiveHint": false,
          "openWorldHint": false,
          "readOnlyHint": true,
          "title": "List App Shares"
        },
        "description": "Use this to see who an app is shared with: lists every outstanding invitation and active grant, labeled by status (`pending` = invited, no access yet; `accepted` = active grantee; `invited` = email-only invite, no Charming account yet) and by role (`collaborator` = read + write; `end-user` = run + write data, not edit source; `viewer` = read only).",
        "execution": {
          "taskSupport": "forbidden"
        },
        "inputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "properties": {
            "app_id": {
              "description": "The app ID (UUID) to list shares for",
              "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"
            }
          },
          "required": ["app_id"],
          "type": "object"
        },
        "name": "list_app_shares",
        "outputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "additionalProperties": false,
          "properties": {
            "ok": {
              "const": true,
              "description": "Indicates success. Errors arrive as content with isError:true.",
              "type": "boolean"
            },
            "shares": {
              "description": "Every outstanding invitation and active grant on the app.",
              "items": {
                "additionalProperties": false,
                "properties": {
                  "accepted_at": {
                    "anyOf": [
                      {
                        "type": "string"
                      },
                      {
                        "type": "null"
                      }
                    ],
                    "description": "ISO timestamp the grant was accepted, or null while pending."
                  },
                  "grantee": {
                    "description": "Grantee identity (handle when set, else email).",
                    "type": "string"
                  },
                  "invited_at": {
                    "description": "ISO timestamp the invitation was created.",
                    "type": "string"
                  },
                  "role": {
                    "description": "Tier this grantee was invited at. `collaborator` = read + write (edit source). `end-user` = run the app and write its data, but cannot edit the source. `viewer` = read only. The role attaches to the grant at invite/share time and survives status transitions (pending → accepted, invited → pending on materialize), so it's safe to summarize access tiers from this field without consulting status.",
                    "enum": ["collaborator", "end-user", "viewer"],
                    "type": "string"
                  },
                  "status": {
                    "description": "`invited` = an email with no Charming account yet (materializes to `pending` once they register + verify). `pending` = a Charming user invited, not yet accepted, no access. `accepted` = active collaborator.",
                    "enum": ["pending", "accepted", "invited"],
                    "type": "string"
                  }
                },
                "required": ["grantee", "status", "role", "invited_at", "accepted_at"],
                "type": "object"
              },
              "type": "array"
            }
          },
          "required": ["ok", "shares"],
          "type": "object"
        }
      },
      {
        "annotations": {
          "destructiveHint": false,
          "openWorldHint": false,
          "readOnlyHint": true,
          "title": "List Apps"
        },
        "description": "List the caller's Charming apps — owned ones plus apps shared with them (accepted invites, marked `[shared with you]`; a read-only share also reads `(view-only)`). Use this to find an app's id before fetching, editing, or calling it. Returns per app: id, revision, role (owner | collaborator | end-user | viewer | team-admin | team-member), displayName, description (a search-friendly summary), url (machine API base), shareUrl (the link to give the user), lastUpdatedAt (ISO-8601 of the last write), claimed (always true for now — exposed for forward-compat), and capabilities (imports + exports). A `collaborator` can edit and run a shared app; an `end-user` can run it and write its data but cannot edit the app source; a `viewer` can only open and read it. The description disambiguates apps with similar display names; capabilities.exports lists each app's callable operations, exposing the cross-app integration surface without N+1 get_app_source calls. Paginate with `cursor` from a prior call's `nextCursor` line. Apps with `description: null` predate descriptions and have not yet been backfilled. When telling the user where to open or share an app, always give them `shareUrl` — never construct links for humans from the `url` field.",
        "execution": {
          "taskSupport": "forbidden"
        },
        "inputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "properties": {
            "cursor": {
              "description": "Opaque cursor from a prior call's `nextCursor` to fetch the next page.",
              "type": "string"
            },
            "limit": {
              "description": "Max apps to return (1-100, default 50).",
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          },
          "type": "object"
        },
        "name": "list_apps",
        "outputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "additionalProperties": false,
          "properties": {
            "apps": {
              "description": "Apps on this page, ordered by creation time newest-first.",
              "items": {
                "additionalProperties": false,
                "properties": {
                  "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)."
                  },
                  "capabilities": {
                    "additionalProperties": false,
                    "description": "Declared capability surface from the app manifest. The `exports` array is the discoverability hook for cross-app calls.",
                    "properties": {
                      "exports": {
                        "description": "Operations this app exposes to other apps via `window.charming.api(<id>).<export>()`. Use to plan cross-app integrations without round-tripping through get_app_source.",
                        "items": {
                          "type": "string"
                        },
                        "type": "array"
                      },
                      "imageHosts": {
                        "description": "External https image hosts injected into the app CSP img-src (e.g. [\"covers.openlibrary.org\"]). Omitted when the app declares none.",
                        "items": {
                          "type": "string"
                        },
                        "type": "array"
                      },
                      "imports": {
                        "description": "Capability tokens the app depends on (e.g. \"charming:storage/kv@1.0\", \"charming:browser/microphone@1.0\").",
                        "items": {
                          "type": "string"
                        },
                        "type": "array"
                      }
                    },
                    "required": ["imports", "exports"],
                    "type": "object"
                  },
                  "claimed": {
                    "description": "True if the app is owned by an authenticated user (i.e. has been claimed). list_apps only ever returns apps owned by the caller, so this is always true today; exposed for forward-compatibility with shared/discoverable listings.",
                    "type": "boolean"
                  },
                  "description": {
                    "anyOf": [
                      {
                        "type": "string"
                      },
                      {
                        "type": "null"
                      }
                    ],
                    "description": "Search-friendly summary written by the creating agent. Match user intent (\"open my protein tracker\", \"log food\") against this rather than display name alone. Null on apps created before descriptions existed and not yet backfilled."
                  },
                  "displayName": {
                    "description": "Human-readable name.",
                    "type": "string"
                  },
                  "id": {
                    "description": "App UUID. Pass to get_app to render or get_app_source to read.",
                    "type": "string"
                  },
                  "lastUpdatedAt": {
                    "description": "ISO-8601 timestamp of the last write to this app's row (create, update_app, set_remixable, unset_remixable, claim, version pointer edits, etc.). Use for recency cues and \"what did I work on most recently\" surfaces. Not the sort key — apps are ordered by creation time, so sort client-side by this field if you need recency order.",
                    "type": "string"
                  },
                  "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"
                  },
                  "role": {
                    "description": "`owner` = your personal app. `collaborator` = shared with you via an accepted invite (edit + run, but not share/delete/transfer). `end-user` = shared use-and-write-data (run the app and write its data, but cannot edit the app source). `viewer` = shared read-only (open and read, but no edits or mutating ops). `team-admin` / `team-member` = owned by a team you belong to; admins get full control, members can edit + run.",
                    "enum": [
                      "owner",
                      "collaborator",
                      "end-user",
                      "viewer",
                      "team-admin",
                      "team-member"
                    ],
                    "type": "string"
                  },
                  "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 base URL (stable /app/<uuid> form, token-free). Append /api/<op> to its path for out-of-band operation calls; mint a per-app token via get_app for write access. When giving the user a link, use shareUrl instead.",
                    "type": "string"
                  }
                },
                "required": [
                  "id",
                  "role",
                  "displayName",
                  "revision",
                  "description",
                  "url",
                  "shareUrl",
                  "lastUpdatedAt",
                  "claimed",
                  "capabilities"
                ],
                "type": "object"
              },
              "type": "array"
            },
            "nextCursor": {
              "description": "Opaque cursor for the next page. Pass back as `cursor` on the next list_apps call. Absent on the final page.",
              "type": "string"
            },
            "ok": {
              "const": true,
              "description": "Indicates success. Errors arrive as content with isError:true.",
              "type": "boolean"
            }
          },
          "required": ["ok", "apps"],
          "type": "object"
        }
      },
      {
        "annotations": {
          "destructiveHint": false,
          "openWorldHint": false,
          "readOnlyHint": true,
          "title": "List App Feedback"
        },
        "description": "List feedback rows for the caller's apps. Use this to review feedback previously recorded for an app. When `app_id` is set, returns rows for that one app (owner-checked); when omitted, returns rows across every app the caller owns. Paginate with `cursor` from a prior call's `cursor` field. Newest rows come first.",
        "execution": {
          "taskSupport": "forbidden"
        },
        "inputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "properties": {
            "app_id": {
              "description": "Optional app ID (UUID) to restrict to. When omitted, returns feedback across every app the caller owns.",
              "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"
            },
            "category": {
              "description": "Optional category filter. One of: bug, crash, enhancement, praise, other.",
              "enum": ["bug", "crash", "enhancement", "praise", "other"],
              "type": "string"
            },
            "cursor": {
              "description": "Opaque cursor from a prior call's `cursor` to fetch the next page.",
              "type": "string"
            },
            "limit": {
              "description": "Max rows to return (1-50, default 20).",
              "maximum": 50,
              "minimum": 1,
              "type": "integer"
            },
            "since": {
              "description": "Optional inclusive lower bound on `created_at` as an ISO-8601 timestamp.",
              "format": "date-time",
              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
              "type": "string"
            }
          },
          "type": "object"
        },
        "name": "list_feedback",
        "outputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "additionalProperties": false,
          "properties": {
            "cursor": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Opaque cursor for the next page. Null when this page is the last. Pass back as `cursor` on the next list_feedback call."
            },
            "items": {
              "description": "Feedback rows ordered by `created_at DESC, id DESC` (newest-first).",
              "items": {
                "additionalProperties": false,
                "properties": {
                  "app_id": {
                    "description": "UUID of the app the feedback is attached to.",
                    "type": "string"
                  },
                  "category": {
                    "description": "Feedback category. One of: bug, crash, enhancement, praise, other.",
                    "type": "string"
                  },
                  "crash_data": {
                    "anyOf": [
                      {
                        "additionalProperties": {},
                        "propertyNames": {
                          "type": "string"
                        },
                        "type": "object"
                      },
                      {
                        "type": "null"
                      }
                    ],
                    "description": "Crash details (message + optional stack/url/userAgent/traceId). Null unless category=crash."
                  },
                  "created_at": {
                    "description": "ISO-8601 timestamp when the row was inserted.",
                    "type": "string"
                  },
                  "id": {
                    "description": "UUID of the feedback row.",
                    "type": "string"
                  },
                  "source": {
                    "description": "Origin of the row: \"user\", \"agent\", or \"auto-crash\". Server-enforced.",
                    "type": "string"
                  },
                  "structured_data": {
                    "anyOf": [
                      {
                        "additionalProperties": {},
                        "propertyNames": {
                          "type": "string"
                        },
                        "type": "object"
                      },
                      {
                        "type": "null"
                      }
                    ],
                    "description": "Caller-supplied JSON payload. Null when not supplied."
                  },
                  "text": {
                    "anyOf": [
                      {
                        "type": "string"
                      },
                      {
                        "type": "null"
                      }
                    ],
                    "description": "Free-form feedback body. Null when not supplied."
                  },
                  "user_id": {
                    "anyOf": [
                      {
                        "type": "string"
                      },
                      {
                        "type": "null"
                      }
                    ],
                    "description": "Owning user id, denormalised from the app row. Null for unclaimed apps."
                  }
                },
                "required": [
                  "id",
                  "app_id",
                  "user_id",
                  "category",
                  "text",
                  "structured_data",
                  "crash_data",
                  "source",
                  "created_at"
                ],
                "type": "object"
              },
              "type": "array"
            },
            "ok": {
              "const": true,
              "description": "Indicates success. Errors arrive as content with isError:true.",
              "type": "boolean"
            }
          },
          "required": ["ok", "items", "cursor"],
          "type": "object"
        }
      },
      {
        "annotations": {
          "destructiveHint": true,
          "openWorldHint": true,
          "readOnlyHint": false,
          "title": "Mutate App"
        },
        "description": "Run a mutating backend operation on a Charming app and return its result, without editing code. Use this for operations that change state — including operations whose read/write safety can't be determined. For read-only operations, use query_app. If you don't know an operation's name or which tool it needs, call get_app — each operation lists its tool.",
        "execution": {
          "taskSupport": "forbidden"
        },
        "inputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "properties": {
            "app_id": {
              "description": "The app ID (UUID)",
              "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"
            },
            "op": {
              "description": "Operation name, routed to /api/:op inside the app module",
              "type": "string"
            },
            "params": {
              "additionalProperties": {},
              "description": "Optional JSON params",
              "propertyNames": {
                "type": "string"
              },
              "type": "object"
            }
          },
          "required": ["app_id", "op"],
          "type": "object"
        },
        "name": "mutate_app",
        "outputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "additionalProperties": false,
          "properties": {
            "error": {
              "additionalProperties": false,
              "description": "Set when ok=false. Always inspect kind before retrying.",
              "properties": {
                "details": {
                  "additionalProperties": {},
                  "description": "Handler-supplied structured context for the error. Present on kind=\"handler_error\" when the route handler attached details.",
                  "propertyNames": {
                    "type": "string"
                  },
                  "type": "object"
                },
                "errors": {
                  "description": "Validation issues from the route input/output schema. Present on kind=\"invalid_input\" and kind=\"invalid_output\".",
                  "items": {},
                  "type": "array"
                },
                "kind": {
                  "description": "Error kind from the app envelope (e.g. \"operation_failed\", \"invalid_input\", \"invalid_output\", \"handler_error\") or a transport-level fallback.",
                  "type": "string"
                },
                "message": {
                  "description": "Human-readable error message. Secrets are redacted.",
                  "type": "string"
                }
              },
              "required": ["kind", "message"],
              "type": "object"
            },
            "ok": {
              "description": "True if the operation handler returned a successful envelope. False when the app reported an operation error — distinct from a transport/protocol error.",
              "type": "boolean"
            },
            "value": {
              "description": "Operation return value when ok=true. Shape depends on the app and op called."
            }
          },
          "required": ["ok"],
          "type": "object"
        }
      },
      {
        "annotations": {
          "destructiveHint": false,
          "openWorldHint": true,
          "readOnlyHint": true,
          "title": "Query App"
        },
        "description": "Run a read-only backend operation on a Charming app and return its result, without editing code. Use this for operations that only read state. For operations that change state, use mutate_app. If you don't know an operation's name or which tool it needs, call get_app — each operation lists its tool.",
        "execution": {
          "taskSupport": "forbidden"
        },
        "inputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "properties": {
            "app_id": {
              "description": "The app ID (UUID)",
              "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"
            },
            "op": {
              "description": "Operation name, routed to /api/:op inside the app module",
              "type": "string"
            },
            "params": {
              "additionalProperties": {},
              "description": "Optional JSON params",
              "propertyNames": {
                "type": "string"
              },
              "type": "object"
            }
          },
          "required": ["app_id", "op"],
          "type": "object"
        },
        "name": "query_app",
        "outputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "additionalProperties": false,
          "properties": {
            "error": {
              "additionalProperties": false,
              "description": "Set when ok=false. Always inspect kind before retrying.",
              "properties": {
                "details": {
                  "additionalProperties": {},
                  "description": "Handler-supplied structured context for the error. Present on kind=\"handler_error\" when the route handler attached details.",
                  "propertyNames": {
                    "type": "string"
                  },
                  "type": "object"
                },
                "errors": {
                  "description": "Validation issues from the route input/output schema. Present on kind=\"invalid_input\" and kind=\"invalid_output\".",
                  "items": {},
                  "type": "array"
                },
                "kind": {
                  "description": "Error kind from the app envelope (e.g. \"operation_failed\", \"invalid_input\", \"invalid_output\", \"handler_error\") or a transport-level fallback.",
                  "type": "string"
                },
                "message": {
                  "description": "Human-readable error message. Secrets are redacted.",
                  "type": "string"
                }
              },
              "required": ["kind", "message"],
              "type": "object"
            },
            "ok": {
              "description": "True if the operation handler returned a successful envelope. False when the app reported an operation error — distinct from a transport/protocol error.",
              "type": "boolean"
            },
            "value": {
              "description": "Operation return value when ok=true. Shape depends on the app and op called."
            }
          },
          "required": ["ok"],
          "type": "object"
        }
      },
      {
        "annotations": {
          "destructiveHint": false,
          "openWorldHint": true,
          "readOnlyHint": false,
          "title": "Rename App"
        },
        "description": "Change a Charming app's URL slug — the human-readable, URL-safe name, distinct from its title/displayName and from its machine manifest id. Use this to give an app a different public URL. Titles and slugs are independent: editing the title with update_app does not change the slug; this tool does. Renaming changes the public URL, and the old URL keeps working by redirecting to the new one. Slug rules: lowercase letters, digits, and hyphens; 2-48 chars; may start with a letter or digit; no leading, trailing, or consecutive hyphens. When telling the user where to open or share the renamed 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 rename.",
              "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"
            },
            "app_name": {
              "description": "The new URL slug. Lowercase letters, digits, and hyphens; 2-48 chars; may start with a letter or digit; no leading/trailing or consecutive hyphens. Input is lowercased and trimmed.",
              "type": "string"
            }
          },
          "required": ["app_id", "app_name"],
          "type": "object"
        },
        "name": "rename_app",
        "outputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "additionalProperties": false,
          "properties": {
            "appName": {
              "description": "The new live slug for the app.",
              "type": "string"
            },
            "id": {
              "description": "UUID of the renamed app.",
              "type": "string"
            },
            "ok": {
              "const": true,
              "description": "Indicates success. Errors arrive as content with isError:true.",
              "type": "boolean"
            },
            "previousAppName": {
              "description": "The slug the app had before this rename. Retained as an alias so old URLs keep resolving (a redirect to the new slug).",
              "type": "string"
            },
            "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). Reflects the new slug.. 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"
            }
          },
          "required": ["ok", "id", "url", "shareUrl", "appName", "previousAppName"],
          "type": "object"
        }
      },
      {
        "annotations": {
          "destructiveHint": false,
          "openWorldHint": true,
          "readOnlyHint": false,
          "title": "Set Your Handle"
        },
        "description": "Change the signed-in user's handle (their username) — the first segment of their friendly app URLs (`https://charm.ing/<handle>/<app-name>`), so changing it changes the prefix of every app URL they share. Use this to give the current user a different handle. The old handle keeps working by redirecting to the new one, so links already shared stay valid. Acts on the current signed-in user only and takes no user id. Handle rules: lowercase letters, digits, and hyphens; 3-32 chars; must start with a letter; no leading, trailing, or consecutive hyphens; some words are reserved. Rate-limited to 3 changes per rolling 30 days.",
        "execution": {
          "taskSupport": "forbidden"
        },
        "inputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "properties": {
            "handle": {
              "description": "The desired new handle. Lowercase letters, digits, and hyphens; 3-32 chars; must start with a letter; no leading/trailing or consecutive hyphens. Input is lowercased and trimmed.",
              "type": "string"
            }
          },
          "required": ["handle"],
          "type": "object"
        },
        "name": "set_handle",
        "outputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "additionalProperties": false,
          "properties": {
            "handle": {
              "description": "The new live handle for the signed-in user.",
              "type": "string"
            },
            "ok": {
              "const": true,
              "description": "Indicates success. Errors arrive as content with isError:true.",
              "type": "boolean"
            },
            "previousHandle": {
              "description": "The handle the user had before this change. Retained as an alias so old friendly URLs (`/<previousHandle>/<app>`) keep resolving (a redirect to the new handle). Empty string when the user had no prior handle.",
              "type": "string"
            }
          },
          "required": ["ok", "handle", "previousHandle"],
          "type": "object"
        }
      },
      {
        "annotations": {
          "destructiveHint": false,
          "openWorldHint": true,
          "readOnlyHint": false,
          "title": "Make App Public"
        },
        "description": "Make a Charming app PUBLIC: anyone who opens its URL can use it with no login and read AND write its SHARED data. Use this for a totally-open surface (a poll, an RSVP list, a guestbook) where requiring sign-in or per-person invites is not worth it. WARNING — this is a shared, unauthenticated write surface: every anonymous visitor reads and writes the SAME data pool with no per-visitor isolation, so anyone with the URL can overwrite or wipe the data. Surface the returned `message` verbatim so the user understands that before sharing. For per-person access that keeps an audit trail and write control, share with the `end-user` role instead (it requires login). Idempotent: calling on an already-public app returns the same URL without re-firing analytics. Owner-only; anonymous apps must be claimed first.",
        "execution": {
          "taskSupport": "forbidden"
        },
        "inputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "properties": {
            "app_id": {
              "description": "The app ID (UUID) to make public",
              "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"
            }
          },
          "required": ["app_id"],
          "type": "object"
        },
        "name": "set_public",
        "outputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "additionalProperties": false,
          "properties": {
            "message": {
              "description": "Consequence copy the agent should surface verbatim to the user. WARNS that anyone with the URL can read and overwrite (or wipe) the shared data, with no login and no per-visitor isolation.",
              "type": "string"
            },
            "ok": {
              "const": true,
              "description": "Indicates success. Errors arrive as content with isError:true.",
              "type": "boolean"
            },
            "public_url": {
              "description": "Public URL for the now-public app. Friendly `/<handle>/<app-name>` form when available, `/app/<uuid>` otherwise. Anyone can open this URL with no login and read AND write the app's SHARED data. Free of write-capable `?t=` tokens.",
              "type": "string"
            }
          },
          "required": ["ok", "public_url", "message"],
          "type": "object"
        }
      },
      {
        "annotations": {
          "destructiveHint": false,
          "openWorldHint": true,
          "readOnlyHint": false,
          "title": "Share App as Remixable"
        },
        "description": "Mark a Charming app as remixable. Use this to let anyone who opens the app's URL get their own brand-new editable copy, while the original is never mutated by visitors. Each remix is an independent app the visitor can keep and later claim by signing up. Idempotent: calling on an already-remixable app returns the same URL without re-firing analytics. Anonymous apps cannot be made remixable; the app must be claimed first.",
        "execution": {
          "taskSupport": "forbidden"
        },
        "inputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "properties": {
            "app_id": {
              "description": "The app ID (UUID) to share as remixable",
              "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"
            }
          },
          "required": ["app_id"],
          "type": "object"
        },
        "name": "set_remixable",
        "outputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "additionalProperties": false,
          "properties": {
            "message": {
              "description": "Consequence copy the agent should surface verbatim to the user when reporting that the share was created. Spells out that other people will get their own remix — the user's data is untouched.",
              "type": "string"
            },
            "ok": {
              "const": true,
              "description": "Indicates success. Errors arrive as content with isError:true.",
              "type": "boolean"
            },
            "public_url": {
              "description": "Public share URL for the now-remixable app. Friendly `/<handle>/<app-name>` form when available, `/app/<uuid>` otherwise. Visitors opening this URL get their own brand-new editable copy; the original is never mutated. Free of write-capable `?t=` tokens.",
              "type": "string"
            }
          },
          "required": ["ok", "public_url", "message"],
          "type": "object"
        }
      },
      {
        "annotations": {
          "destructiveHint": true,
          "openWorldHint": true,
          "readOnlyHint": false,
          "title": "Set App Starter Prompt"
        },
        "description": "Set or clear an app's authored starter prompt — a short getting-started instruction prefilled in the chat host when a visitor clicks \"Open in Claude\" or \"Open in ChatGPT\" on the app, replacing the generic \"I'm using this Charming app\" body. Use this to frame a shared/remixable app's intended first action for visitors. Write a generic instruction — do NOT embed the app's URL: the app name, description, and URL (and, for unclaimed apps, an access token) are appended automatically at render time, so a hardcoded URL would point every remixer at the template instead of their own copy. Pass `starter_prompt: null` (or an empty string) to clear and revert to the generic default. Owner-only; anonymous apps must be claimed first. Idempotent (re-setting the same value is a no-op). Length cap: 2000 characters.",
        "execution": {
          "taskSupport": "forbidden"
        },
        "inputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "properties": {
            "app_id": {
              "description": "The app ID (UUID) whose starter prompt should change.",
              "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"
            },
            "starter_prompt": {
              "anyOf": [
                {
                  "maxLength": 2000,
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Getting-started instruction the chat host receives when a visitor clicks \"Open in Claude\" / \"Open in ChatGPT\". The authored text leads; the app's name, description, URL, and (for unclaimed apps) an access token are appended automatically as context — so write a generic 1–3 sentence instruction (the first action you want the visitor to take, plus any persona/voice), NOT a self-contained prompt and NOT the app's URL. Pass null or \"\" to clear and revert to the generic default. Max 2000 characters."
            }
          },
          "required": ["app_id", "starter_prompt"],
          "type": "object"
        },
        "name": "set_starter_prompt",
        "outputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "additionalProperties": false,
          "properties": {
            "message": {
              "description": "Consequence copy the agent should surface to the user. Explains where the prompt is rendered (Open in Claude / Open in ChatGPT) and how to clear it.",
              "type": "string"
            },
            "ok": {
              "const": true,
              "description": "Indicates success. Errors arrive as content with isError:true.",
              "type": "boolean"
            },
            "starter_prompt": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "The current starter prompt on the app row after this call. Null when the prompt was cleared (or normalized away from whitespace-only input). Otherwise the verbatim authored string."
            }
          },
          "required": ["ok", "starter_prompt", "message"],
          "type": "object"
        }
      },
      {
        "annotations": {
          "destructiveHint": true,
          "openWorldHint": true,
          "readOnlyHint": false,
          "title": "Share App with a User"
        },
        "description": "Invite someone (by Charming handle or email) to one of your apps, OR change the role of someone you already invited. Use this when the user wants to give a specific person access to an app, or to change what an existing collaborator/end-user/viewer can do. Creates a PENDING invitation: the invitee gets no access and sees nothing in their workspace until they accept. Pick a role: `collaborator` (default) can open, edit, and run the app (shared state, same data); `end-user` can open the app and write its data (record entries, save state) but cannot edit the app source; `viewer` can only open and use the read-only surface — viewers cannot edit, run mutating ops, or change anything. If the grantee already has a share, calling this again WITH a `role` changes their role in place (status `updated`, no new invitation, no second email) — this is how you flip a collaborator to an end-user and back; re-sharing WITHOUT a `role` leaves the existing role untouched. No share role can ever share, delete, or transfer the app. The invitee does NOT need a Charming account first — inviting an email with no account stores a pending invitation and emails them a sign-up link; it activates once they register and verify that email (an unknown handle still errors, since there is no address to reach). Revoke anytime with `unshare_app`.",
        "execution": {
          "taskSupport": "forbidden"
        },
        "inputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "properties": {
            "app_id": {
              "description": "The app ID (UUID) to share",
              "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"
            },
            "grantee": {
              "description": "The invitee's Charming handle (e.g. `ada` or `@ada`) or account email",
              "minLength": 1,
              "type": "string"
            },
            "role": {
              "description": "The access level: `collaborator` (default — edit and run), `end-user` (run + write data, not edit source), or `viewer` (read-only)",
              "enum": ["collaborator", "end-user", "viewer"],
              "type": "string"
            }
          },
          "required": ["app_id", "grantee"],
          "type": "object"
        },
        "name": "share_app",
        "outputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "additionalProperties": false,
          "properties": {
            "grantee": {
              "description": "The resolved grantee (handle when set, else email) the invitation was sent to.",
              "type": "string"
            },
            "message": {
              "description": "Consequence copy the agent should surface verbatim: the invitee has no access until they accept, and what they will be able to do once they do.",
              "type": "string"
            },
            "ok": {
              "const": true,
              "description": "Indicates success. Errors arrive as content with isError:true.",
              "type": "boolean"
            },
            "status": {
              "description": "Sharing creates an invitation, not access. `pending` = the grantee has a Charming account and must accept (dashboard or invite email). `invited` = the email has no account yet; it becomes a pending share once they register and verify that address. `updated` = the grantee already had a share and re-sharing with a `role` changed it in place (no new invitation, no second email).",
              "enum": ["pending", "invited", "updated"],
              "type": "string"
            }
          },
          "required": ["ok", "status", "grantee", "message"],
          "type": "object"
        }
      },
      {
        "annotations": {
          "destructiveHint": false,
          "openWorldHint": false,
          "readOnlyHint": false,
          "title": "Submit App Feedback"
        },
        "description": "Record agent-authored feedback about a Charming app. Use this to log observed bugs, suggested enhancements, caught crash reports, or qualitative notes. Source is server-enforced to \"agent\" — agents cannot impersonate user or auto-crash sources. The caller must own the app.",
        "execution": {
          "taskSupport": "forbidden"
        },
        "inputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "properties": {
            "app_id": {
              "description": "The app ID (UUID) the feedback is attached to.",
              "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"
            },
            "category": {
              "description": "Feedback category. One of: bug, crash, enhancement, praise, other. Pass \"crash\" only when crash_data carries the captured error.",
              "enum": ["bug", "crash", "enhancement", "praise", "other"],
              "type": "string"
            },
            "crash_data": {
              "description": "Required when category=crash. Optional otherwise.",
              "properties": {
                "message": {
                  "description": "Error message; required when category=crash.",
                  "type": "string"
                },
                "stack": {
                  "type": "string"
                },
                "traceId": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "userAgent": {
                  "type": "string"
                }
              },
              "required": ["message"],
              "type": "object"
            },
            "structured_data": {
              "additionalProperties": {},
              "description": "Optional JSON object with caller-supplied structured context (e.g. route, repro inputs).",
              "propertyNames": {
                "type": "string"
              },
              "type": "object"
            },
            "text": {
              "description": "Free-form feedback body (up to 16384 chars). Optional, but at least one of text / structured_data / crash_data should usually be set so the row is actionable.",
              "maxLength": 16384,
              "type": "string"
            }
          },
          "required": ["app_id", "category"],
          "type": "object"
        },
        "name": "submit_feedback",
        "outputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "additionalProperties": false,
          "properties": {
            "app_id": {
              "description": "UUID of the app the feedback was attached to.",
              "type": "string"
            },
            "created_at": {
              "description": "ISO-8601 timestamp when the row was inserted.",
              "type": "string"
            },
            "id": {
              "description": "UUID of the newly-inserted feedback row.",
              "type": "string"
            },
            "ok": {
              "const": true,
              "description": "Indicates success. Errors arrive as content with isError:true.",
              "type": "boolean"
            }
          },
          "required": ["ok", "id", "app_id", "created_at"],
          "type": "object"
        }
      },
      {
        "annotations": {
          "destructiveHint": true,
          "openWorldHint": true,
          "readOnlyHint": false,
          "title": "Make App Private"
        },
        "description": "Make a public Charming app PRIVATE again. Use this to close anonymous access: visitors can no longer open the app without logging in (the URL requires login). Data already written by anonymous visitors is retained. Idempotent: calling on an already-private app is a no-op.",
        "execution": {
          "taskSupport": "forbidden"
        },
        "inputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "properties": {
            "app_id": {
              "description": "The app ID (UUID) to make private",
              "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"
            }
          },
          "required": ["app_id"],
          "type": "object"
        },
        "name": "unset_public",
        "outputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "additionalProperties": false,
          "properties": {
            "message": {
              "description": "Consequence copy the agent should surface verbatim. Tells the user the app now requires login again; data already written by anonymous visitors is retained.",
              "type": "string"
            },
            "ok": {
              "const": true,
              "description": "Indicates success. Errors arrive as content with isError:true.",
              "type": "boolean"
            }
          },
          "required": ["ok", "message"],
          "type": "object"
        }
      },
      {
        "annotations": {
          "destructiveHint": true,
          "openWorldHint": true,
          "readOnlyHint": false,
          "title": "Stop Allowing Remixes"
        },
        "description": "Stop allowing remixes of a Charming app. Use this to turn off auto-forking for new visitors; existing remixes survive untouched. Idempotent: calling on an already-non-remixable app is a no-op.",
        "execution": {
          "taskSupport": "forbidden"
        },
        "inputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "properties": {
            "app_id": {
              "description": "The app ID (UUID) to stop sharing as remixable",
              "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"
            }
          },
          "required": ["app_id"],
          "type": "object"
        },
        "name": "unset_remixable",
        "outputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "additionalProperties": false,
          "properties": {
            "message": {
              "description": "Consequence copy the agent should surface verbatim. Tells the user new visitors can no longer remix, while existing remixes are unaffected.",
              "type": "string"
            },
            "ok": {
              "const": true,
              "description": "Indicates success. Errors arrive as content with isError:true.",
              "type": "boolean"
            }
          },
          "required": ["ok", "message"],
          "type": "object"
        }
      },
      {
        "annotations": {
          "destructiveHint": true,
          "openWorldHint": true,
          "readOnlyHint": false,
          "title": "Revoke App Share"
        },
        "description": "Revoke another user's access to one of your apps — removes a pending invitation or an accepted collaborator grant. Use this when the user wants to withdraw an invitation or remove a collaborator. Takes effect immediately for new requests. Idempotent: revoking a grant that does not exist is a no-op.",
        "execution": {
          "taskSupport": "forbidden"
        },
        "inputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "properties": {
            "app_id": {
              "description": "The app ID (UUID) to revoke access on",
              "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"
            },
            "grantee": {
              "description": "The grantee's Charming handle or account email",
              "minLength": 1,
              "type": "string"
            }
          },
          "required": ["app_id", "grantee"],
          "type": "object"
        },
        "name": "unshare_app",
        "outputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "additionalProperties": false,
          "properties": {
            "ok": {
              "const": true,
              "description": "Indicates success. Errors arrive as content with isError:true.",
              "type": "boolean"
            },
            "removed": {
              "description": "True when a pending invitation or active grant was removed; false when there was nothing to remove (idempotent no-op).",
              "type": "boolean"
            }
          },
          "required": ["ok", "removed"],
          "type": "object"
        }
      },
      {
        "_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"
        }
      },
      {
        "annotations": {
          "destructiveHint": true,
          "openWorldHint": true,
          "readOnlyHint": false,
          "title": "Upload Asset"
        },
        "description": "UPLOAD A STATIC ASSET TO AN APP so app code stays small and the app reads it back same-origin. Use this when an app needs a large or static file — an image, PDF, audio clip, or a dataset bigger than a few KB — that would otherwise be inlined into `module`/`ui` and blow the 256 KiB source cap. Provide EXACTLY ONE of: `sourceUrl` (PREFERRED for any binary — the SERVER fetches the remote file, follows redirects, and the bytes never transit this tool call), `text` (UTF-8 dataset/JSON/CSV — no encoding needed), or `dataBase64` (LAST RESORT, tiny binaries only: large base64 arguments can stall inside some MCP clients before ever reaching the server, so keep it under ~16 KB and use `sourceUrl` for anything bigger). The app reads the asset via `window.charming.assets.getUrl(key)` (for <img>/<a>) or `env.assets.get(key)` in its backend (the app must declare `charming:storage/blob@1.0` in manifest.capabilities.imports to use env.assets). Caps: 10 MiB/asset, 50 assets/app, 100 MiB/app.",
        "execution": {
          "taskSupport": "forbidden"
        },
        "inputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "properties": {
            "appId": {
              "description": "The app id (app_modules.id) to attach the asset to. Must be an app you own.",
              "type": "string"
            },
            "contentType": {
              "description": "MIME type. Defaults: text/plain for text, sniffed for sourceUrl, application/octet-stream otherwise.",
              "type": "string"
            },
            "dataBase64": {
              "description": "Base64 of a SMALL binary. Last resort — prefer sourceUrl: large base64 tool arguments can stall in some MCP clients before reaching the server. Keep the encoded string under ~16 KB; the server-side hard cap is 64 KiB decoded.",
              "type": "string"
            },
            "key": {
              "description": "Asset name, e.g. \"dataset_v1.json\". URL-safe [a-zA-Z0-9._-], <=128 chars, no leading dot.",
              "type": "string"
            },
            "sourceUrl": {
              "description": "https URL the server fetches and stores. Bytes never pass through this tool call.",
              "format": "uri",
              "type": "string"
            },
            "text": {
              "description": "UTF-8 text payload (datasets, JSON, CSV). No base64.",
              "type": "string"
            }
          },
          "required": ["appId", "key"],
          "type": "object"
        },
        "name": "upload_asset",
        "outputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "additionalProperties": false,
          "properties": {
            "appId": {
              "description": "UUID of the asset-owning app.",
              "type": "string"
            },
            "bytes": {
              "description": "Stored byte length of the asset payload after decode/fetch.",
              "maximum": 9007199254740991,
              "minimum": -9007199254740991,
              "type": "integer"
            },
            "contentType": {
              "description": "Effective MIME type stored with the asset (supplied, sniffed from sourceUrl, or defaulted).",
              "type": "string"
            },
            "key": {
              "description": "The asset key it was stored under. Same value passed in.",
              "type": "string"
            },
            "ok": {
              "const": true,
              "description": "Indicates success. Errors arrive as content with isError:true.",
              "type": "boolean"
            },
            "url": {
              "description": "Same-origin URL serving the asset. Use in <img src>/<a href>/fetch, or read in the backend via env.assets.get(key). Equivalent to window.charming.assets.getUrl(key).",
              "type": "string"
            }
          },
          "required": ["ok", "appId", "key", "bytes", "contentType", "url"],
          "type": "object"
        }
      }
    ]
  },
  "openai": {
    "prompts": [
      {
        "description": "Charming dynamic-runtime app authoring contract.",
        "name": "charming:app-guide"
      },
      {
        "description": "Charming visual playbook — the default look for generated app UIs (override when the user asks for a different vibe). Use this when authoring or restyling an app's `ui`.",
        "name": "charming:design-guide"
      }
    ],
    "resourceTemplates": [],
    "resources": [
      {
        "_meta": {
          "openai/widgetCSP": {
            "connect_domains": ["https://charm.ing", "https://cdn.jsdelivr.net"],
            "frame_domains": ["https://charm.ing"],
            "resource_domains": [
              "https://fonts.googleapis.com",
              "https://fonts.gstatic.com",
              "https://cdn.jsdelivr.net"
            ]
          },
          "openai/widgetDescription": "Charming renders the interactive web app created or opened by the tool.",
          "openai/widgetDomain": "https://charm.ing",
          "ui": {
            "csp": {
              "connectDomains": ["https://charm.ing", "https://cdn.jsdelivr.net"],
              "frameDomains": ["https://charm.ing"],
              "resourceDomains": [
                "https://fonts.googleapis.com",
                "https://fonts.gstatic.com",
                "https://cdn.jsdelivr.net"
              ]
            },
            "domain": "https://charm.ing",
            "prefersBorder": false
          }
        },
        "description": "Interactive web app hosted by Charming",
        "mimeType": "text/html;profile=mcp-app",
        "name": "Buildy App Viewer",
        "uri": "ui://buildy/chatgpt/app-v1"
      },
      {
        "_meta": {
          "openai/widgetCSP": {
            "connect_domains": ["https://charm.ing", "https://cdn.jsdelivr.net"],
            "frame_domains": ["https://charm.ing"],
            "resource_domains": [
              "https://fonts.googleapis.com",
              "https://fonts.gstatic.com",
              "https://cdn.jsdelivr.net"
            ]
          },
          "openai/widgetDescription": "Charming renders the interactive web app created or opened by the tool.",
          "openai/widgetDomain": "https://charm.ing",
          "ui": {
            "csp": {
              "connectDomains": ["https://charm.ing", "https://cdn.jsdelivr.net"],
              "frameDomains": ["https://charm.ing"],
              "resourceDomains": [
                "https://fonts.googleapis.com",
                "https://fonts.gstatic.com",
                "https://cdn.jsdelivr.net"
              ]
            },
            "domain": "https://charm.ing",
            "prefersBorder": false
          }
        },
        "description": "Interactive web app hosted by Charming",
        "mimeType": "text/html;profile=mcp-app",
        "name": "Charming App Viewer",
        "uri": "ui://charming/chatgpt/app-v1"
      }
    ],
    "server": {
      "capabilities": {
        "logging": {},
        "prompts": {
          "listChanged": true
        },
        "resources": {
          "listChanged": true
        },
        "tools": {
          "listChanged": true
        }
      },
      "info": {
        "icons": [
          {
            "mimeType": "image/png",
            "sizes": ["512x512"],
            "src": "https://charm.ing/charming-mark.png"
          }
        ],
        "name": "charming",
        "title": "Charming",
        "version": "0.1.0",
        "websiteUrl": "https://usecharming.com"
      },
      "instructions": null
    },
    "tools": [
      {
        "_meta": {
          "openai/outputTemplate": "ui://charming/chatgpt/app-v1",
          "openai/toolInvocation/invoked": "App created",
          "openai/toolInvocation/invoking": "Creating app…",
          "ui": {
            "resourceUri": "ui://charming/chatgpt/app-v1"
          },
          "ui/resourceUri": "ui://charming/chatgpt/app-v1"
        },
        "annotations": {
          "destructiveHint": true,
          "openWorldHint": true,
          "readOnlyHint": false,
          "title": "Create App"
        },
        "description": "Create a new interactive web app and render it inline. Use this to build a new app from a request. This tool call returns only after the app is live at the response URL. Do not wait or poll for deployment. If `module` reuses a `manifest.id` the caller already owns, the new app replaces the existing app module, UI, and styles in place; use update_app to edit a known app ID by UUID. Apps persist state through a backend `env.storage` (Workers KV) binding and call backend operations from the frontend via `window.charming.api`. The `module`, `ui`, and `description` parameter docs carry the authoring rules — including the fill-viewport outer-container rule on `ui`; the charming:app-guide prompt has the full guide and a canonical example. If a request needs in-app AI calls, external integrations, native apps, scheduled jobs, SQL, notifications, or view-only sharing, do not fake support. Build the closest local-state version, then log the gap with `submit_feedback`. The charming:app-guide prompt has alternatives. The charming:design-guide prompt documents Charming's default visual style for generated `ui` (single accent, warm neutrals, real type hierarchy; no gradients or decorative emoji) — a default that yields to any user-requested aesthetic. 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": {
            "description": {
              "description": "Search-friendly summary of what this app does, surfaced by list_apps so a future session can match user intent (e.g. \"open my protein tracker\", \"log food\") to this app even when display_name is ambiguous. Write 1-2 sentences covering: (a) what the app tracks/does, (b) the key actions it supports, and (c) synonyms or alternative phrasings the user might say. Example: \"Tracks daily protein intake. Log meals, view weekly totals, set a daily goal. Synonyms: meals, food log, nutrition tracker, calorie counter.\" Limit 500 characters.",
              "maxLength": 500,
              "type": "string"
            },
            "display_name": {
              "description": "Optional display name override",
              "type": "string"
            },
            "module": {
              "description": "ES module source for the app backend. Export a strict canonical `manifest` with `$schema: \"https://charm.ing/schema/app-manifest/2026-07-31.json\"`, `id`, and `meta: { name, icon? }`. The server may insert the exact schema URL on create when the rest of the source is canonical. Unknown manifest keys are rejected. To persist any state, `capabilities.imports` must include \"charming:storage/kv@1.0\" (without it `env.storage` is undefined and every read/write throws `storage capability not granted`). All persistence goes through `env.storage` using Workers KV semantics — `get(key)`, `put(key, value)`, `delete(key)`, `list()`; `.set`/`.add`/`.write`/`.setItem`/`.removeItem` do not exist and throw `TypeError: env.storage.<x> is not a function`. env.storage stores JSON-compatible values directly; do not JSON.stringify before put or JSON.parse after get. Use env.storage for ALL persistence — it is the only storage that survives inside Claude/ChatGPT and syncs across devices. Do NOT keep app data or user state in localStorage/sessionStorage/IndexedDB: those APIs are empty inside chat hosts, so their data silently vanishes there (the most common cause of an app that appears not to save). The \"charming:browser/storage@1.0\" capability (claim-gated) unlocks them but only for throwaway, web-only caching; anything the user expects to keep belongs in env.storage. Export a `routes` array with unique `op` values and `handler` functions. Canonical route fields are `inputSchema`, `outputSchema`, and `annotations`; `method` defaults to `POST`, `path` defaults to `/api/<op>`, input defaults to a closed empty-object schema, and `public` defaults to true. Set all four MCP annotation hints when their defaults do not fit; Charming does not infer them from the HTTP method. A default `fetch` handler is an unmatched-request fallback only and is not discoverable. Apps that use a sensitive browser capability must declare its import in `capabilities.imports` — \"charming:browser/microphone@1.0\" (getUserMedia audio), \"charming:browser/camera@1.0\" (getUserMedia video), \"charming:browser/geolocation@1.0\", \"charming:browser/clipboard-read@1.0\" (reading the clipboard), \"charming:browser/display-capture@1.0\" (getDisplayMedia screen share), \"charming:browser/midi@1.0\" (Web MIDI, navigator.requestMIDIAccess), \"charming:browser/device-motion@1.0\" (device orientation/motion: DeviceOrientationEvent/DeviceMotionEvent + iOS requestPermission), \"charming:browser/ambient-light@1.0\" (ambient light, new AmbientLightSensor), or \"charming:browser/storage@1.0\" (native client storage — localStorage/sessionStorage/IndexedDB — web-only); access is granted only after the app is claimed/authenticated. To call external HTTPS APIs from backend code, declare \"charming:network/fetch@1.0\" and list each exact origin in `manifest.permissions.server.fetch`; both are required and public-only. For an endpoint that needs an API key, declare \"charming:secrets/fetch@1.0\" to get `env.fetch` (claimed apps only) — a sealed outbound fetch that substitutes `{{secret:NAME}}` references in request HEADER values host-side, so the key never enters app source or the sandbox; the app OWNER sets the value in the dashboard at `/app/<id>/secrets` and the agent only references the NAME. Never embed API keys in source. To render remote images, list each exact https origin in `manifest.permissions.browser[\"img-src\"]`. To make an image render in ANY host — standalone, ChatGPT, AND Claude inline (their injected CSP blocks a cross-origin `<img src>`) — set the src from `const src = await window.charming.images.load(remoteUrl)` (it fetches through Charming and returns a `data:` URL every embed CSP allows). `window.charming.images.proxy(remoteUrl)` returns a same-origin proxy URL that works standalone/ChatGPT but NOT in Claude inline; prefer `images.load(...)` when the app may be embedded. Both enforce the declared origins; neither bypasses them.",
              "type": "string"
            },
            "styles": {
              "description": "Optional CSS for the rendered app",
              "type": "string"
            },
            "ui": {
              "description": "Frontend JavaScript rendered into #app. The #app mount point starts empty, so (a) populate its 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(), which fails in the null-origin srcdoc iframe. 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 — the loudest \"AI-generated app\" tell. If the view is text-heavy (a note, an article, a form with long prose), cap the reading measure on an INNER wrapper only, e.g. `<main class=\"min-h-screen\"><div class=\"mx-auto max-w-2xl\">…</div></main>`. Dashboards, kanban, tables, canvases, galleries, and split views should use the full width. For live updates when an agent mutates state from another session, register `window.charming.onStateChange((e) => { ... })` and update the DOM surgically rather than wiping #app. See the charming:app-guide prompt for a canonical example.",
              "type": "string"
            }
          },
          "required": ["module", "description"],
          "type": "object"
        },
        "name": "create_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"
        }
      },
      {
        "annotations": {
          "destructiveHint": true,
          "openWorldHint": true,
          "readOnlyHint": false,
          "title": "Delete App"
        },
        "description": "Permanently delete a Charming app the caller owns, optionally also purging its stored data. Use this to remove an app the caller no longer wants. Deletion is irreversible and requires confirmation: the host is asked to show a confirmation prompt, and when it cannot, the call fails closed until re-invoked with confirm: true.",
        "execution": {
          "taskSupport": "forbidden"
        },
        "inputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "properties": {
            "app_id": {
              "description": "The app ID (UUID) to delete",
              "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"
            },
            "confirm": {
              "description": "Explicit confirmation that the caller intends to permanently delete this app. Fallback for hosts that cannot show a confirmation prompt: set true only after the deletion has been confirmed with the user through another channel. On hosts that do support a prompt, the prompt is authoritative and this field is ignored.",
              "type": "boolean"
            },
            "purge_storage": {
              "description": "Also delete stored app data",
              "type": "boolean"
            }
          },
          "required": ["app_id"],
          "type": "object"
        },
        "name": "delete_app",
        "outputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "additionalProperties": false,
          "properties": {
            "cancelled": {
              "description": "Set to true when the user declined the elicitation confirmation prompt.",
              "type": "boolean"
            },
            "deleted": {
              "description": "True if the app was deleted, false if deletion was cancelled by the user.",
              "type": "boolean"
            },
            "id": {
              "description": "UUID of the targeted app.",
              "type": "string"
            },
            "ok": {
              "const": true,
              "description": "Indicates success. Errors arrive as content with isError:true.",
              "type": "boolean"
            }
          },
          "required": ["ok", "id", "deleted"],
          "type": "object"
        }
      },
      {
        "_meta": {
          "openai/outputTemplate": "ui://charming/chatgpt/app-v1",
          "openai/toolInvocation/invoked": "App ready",
          "openai/toolInvocation/invoking": "Opening app…",
          "ui": {
            "resourceUri": "ui://charming/chatgpt/app-v1"
          },
          "ui/resourceUri": "ui://charming/chatgpt/app-v1"
        },
        "annotations": {
          "destructiveHint": false,
          "openWorldHint": false,
          "readOnlyHint": true,
          "title": "Get App"
        },
        "description": "Fetch an existing app by ID and render it inline. Use this to open, re-render, or continue working with an app the caller already has. Returns the live embedded UI plus its callable API operations, which another agent can invoke with query_app (read-only) or mutate_app (writes) to query or update saved app data. Unlike list_apps, which returns metadata including IDs and URLs, get_app renders the app; call list_apps first to find the ID. 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)",
              "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"
            }
          },
          "required": ["app_id"],
          "type": "object"
        },
        "name": "get_app",
        "outputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "additionalProperties": false,
          "properties": {
            "$schema": {
              "const": "https://charm.ing/schema/app-manifest/2026-07-31.json",
              "description": "Exact dated canonical manifest schema URL. Omitted for legacy apps.",
              "type": "string"
            },
            "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"
            },
            "api": {
              "additionalProperties": false,
              "description": "Full API surface — same shape as `_meta.charming.api`, but model-facing.",
              "properties": {
                "baseUrl": {
                  "description": "Origin + app prefix the operations live under.",
                  "type": "string"
                },
                "operations": {
                  "description": "Discovered routes in author order.",
                  "items": {
                    "additionalProperties": false,
                    "properties": {
                      "description": {
                        "description": "Author-supplied prose description.",
                        "type": "string"
                      },
                      "destructive": {
                        "description": "True when the route destroys data. Default false unless derived as such.",
                        "type": "boolean"
                      },
                      "discoveredFrom": {
                        "description": "Sources that contributed this operation (precedence order).",
                        "items": {
                          "enum": ["routes_export", "manifest_export", "module_route"],
                          "type": "string"
                        },
                        "type": "array"
                      },
                      "examples": {
                        "description": "Author-supplied calling examples that flow into the query_app/mutate_app snippet.",
                        "items": {
                          "additionalProperties": false,
                          "properties": {
                            "description": {
                              "type": "string"
                            },
                            "input": {},
                            "output": {}
                          },
                          "type": "object"
                        },
                        "type": "array"
                      },
                      "input": {
                        "description": "JSON Schema for the field. Canonical source declares it as `route.inputSchema` or `route.outputSchema`."
                      },
                      "manifestExport": {
                        "description": "Original capability-export string when discovered via `manifest_export`.",
                        "type": "string"
                      },
                      "method": {
                        "description": "HTTP method the route accepts.",
                        "enum": ["GET", "POST", "PUT", "PATCH", "DELETE"],
                        "type": "string"
                      },
                      "op": {
                        "description": "Operation name. Matches the `op` field on the corresponding Route.",
                        "type": "string"
                      },
                      "output": {
                        "description": "JSON Schema for the field. Canonical source declares it as `route.inputSchema` or `route.outputSchema`."
                      },
                      "path": {
                        "description": "Path under the app, e.g. `/api/increment`.",
                        "type": "string"
                      },
                      "public": {
                        "description": "True when the route is visible to discovery. Default true.",
                        "type": "boolean"
                      },
                      "readOnly": {
                        "description": "True when the route is a safe read (resolved from author intent + derivation).",
                        "type": "boolean"
                      },
                      "title": {
                        "description": "Short author-supplied title for the operation.",
                        "type": "string"
                      },
                      "tool": {
                        "description": "MCP tool to invoke this op: `query_app` for read-only, `mutate_app` otherwise.",
                        "enum": ["query_app", "mutate_app"],
                        "type": "string"
                      },
                      "url": {
                        "description": "Full URL (origin + app prefix + path).",
                        "type": "string"
                      }
                    },
                    "required": [
                      "op",
                      "method",
                      "path",
                      "url",
                      "readOnly",
                      "destructive",
                      "public",
                      "tool",
                      "discoveredFrom"
                    ],
                    "type": "object"
                  },
                  "type": "array"
                }
              },
              "required": ["baseUrl", "operations"],
              "type": "object"
            },
            "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)."
            },
            "appVersion": {
              "description": "Canonical apps return decimal String(revision). Legacy apps return their stored effective SemVer.",
              "type": "string"
            },
            "description": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Search-friendly summary of what the app does. Null on apps created before descriptions existed; agents are nudged to backfill on the next update_app."
            },
            "displayName": {
              "description": "Human-readable name shown in app listings and headers.",
              "type": "string"
            },
            "id": {
              "description": "UUID of the opened app.",
              "type": "string"
            },
            "manifestId": {
              "description": "Stable manifest identifier declared by the app module. Distinct from `id` (DB primary key) — surface this when discussing the app with the user.",
              "type": "string"
            },
            "manifestVersion": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Deprecated public contract marker. Canonical apps return null; legacy apps return their stored contract date."
            },
            "ok": {
              "const": true,
              "description": "Indicates success. Errors arrive as content with isError:true.",
              "type": "boolean"
            },
            "recentIssues": {
              "additionalProperties": false,
              "description": "Runtime failures recorded against the current app revision since it was published (#1133): contract misses, runtime JS errors, CSP violations, and failed API calls. Present only when the current revision has at least one such event — absence means a clean render. The same summary is appended to the text content. Fetch full detail with GET /app/:id/activity.",
              "properties": {
                "counts": {
                  "additionalProperties": false,
                  "description": "Issue counts per category.",
                  "properties": {
                    "apiFailures": {
                      "description": "Failed calls to the app API proxy.",
                      "maximum": 9007199254740991,
                      "minimum": -9007199254740991,
                      "type": "integer"
                    },
                    "appLoadErrors": {
                      "description": "Caught manifest-resolve / app load failures the bridge swallowed before they became an unhandled rejection.",
                      "maximum": 9007199254740991,
                      "minimum": -9007199254740991,
                      "type": "integer"
                    },
                    "contractMisses": {
                      "description": "Publish-time UI/backend contract validation failures.",
                      "maximum": 9007199254740991,
                      "minimum": -9007199254740991,
                      "type": "integer"
                    },
                    "cspViolations": {
                      "description": "Content-Security-Policy violations (e.g. an external image blocked by img-src).",
                      "maximum": 9007199254740991,
                      "minimum": -9007199254740991,
                      "type": "integer"
                    },
                    "runtimeErrors": {
                      "description": "Runtime JS errors and unhandled rejections reported by the rendered page.",
                      "maximum": 9007199254740991,
                      "minimum": -9007199254740991,
                      "type": "integer"
                    }
                  },
                  "required": [
                    "contractMisses",
                    "runtimeErrors",
                    "cspViolations",
                    "apiFailures",
                    "appLoadErrors"
                  ],
                  "type": "object"
                },
                "examples": {
                  "description": "Most recent example per non-empty category, newest first.",
                  "items": {
                    "additionalProperties": false,
                    "properties": {
                      "category": {
                        "description": "Which `counts` bucket this example illustrates.",
                        "enum": [
                          "contractMisses",
                          "runtimeErrors",
                          "cspViolations",
                          "apiFailures",
                          "appLoadErrors"
                        ],
                        "type": "string"
                      },
                      "kind": {
                        "description": "Underlying durable event kind: `diag_report` (runtime errors and CSP violations, distinguished by category), `contract_validation` (contract misses), `api_proxy_result` (API failures), or `app_load_error` (caught load failures).",
                        "type": "string"
                      },
                      "summary": {
                        "description": "Compact one-line description.",
                        "type": "string"
                      },
                      "ts": {
                        "description": "ISO-8601 timestamp of the event.",
                        "type": "string"
                      }
                    },
                    "required": ["ts", "category", "kind", "summary"],
                    "type": "object"
                  },
                  "type": "array"
                },
                "sinceRevision": {
                  "description": "The app revision the issues are attributed to (the current revision at call time). Legacy nulls normalize to 0.",
                  "maximum": 9007199254740991,
                  "minimum": 0,
                  "type": "integer"
                },
                "total": {
                  "description": "Issue events counted for the current revision, capped at 500 — a value of 500 means 500 or more. Use GET /app/:id/activity for the exact, paginated list.",
                  "maximum": 9007199254740991,
                  "minimum": -9007199254740991,
                  "type": "integer"
                }
              },
              "required": ["sinceRevision", "total", "counts", "examples"],
              "type": "object"
            },
            "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"
            }
          },
          "required": [
            "ok",
            "id",
            "url",
            "shareUrl",
            "displayName",
            "description",
            "manifestId",
            "revision",
            "appVersion",
            "manifestVersion",
            "api"
          ],
          "type": "object"
        }
      },
      {
        "annotations": {
          "destructiveHint": false,
          "openWorldHint": false,
          "readOnlyHint": true,
          "title": "Get App Source"
        },
        "description": "Return an app's raw persisted source (module, frontend ui script, and styles) plus its current revision for optimistic-concurrency edits. Pass that revision through update_app's `expected_revision` input. Use this when you need the source to inspect, debug, or edit an app the caller owns.",
        "execution": {
          "taskSupport": "forbidden"
        },
        "inputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "properties": {
            "app_id": {
              "description": "The app ID (UUID)",
              "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"
            }
          },
          "required": ["app_id"],
          "type": "object"
        },
        "name": "get_app_source",
        "outputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "additionalProperties": false,
          "properties": {
            "$schema": {
              "const": "https://charm.ing/schema/app-manifest/2026-07-31.json",
              "description": "Exact dated canonical manifest schema URL. Omitted for legacy apps.",
              "type": "string"
            },
            "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"
            },
            "appVersion": {
              "description": "Canonical apps return decimal String(revision). Legacy apps return their stored effective SemVer.",
              "type": "string"
            },
            "capabilities": {
              "additionalProperties": false,
              "description": "Capability declarations parsed from the app manifest.",
              "properties": {
                "exports": {
                  "description": "WIT-style capability ids the app exposes for cross-app calls.",
                  "items": {
                    "type": "string"
                  },
                  "type": "array"
                },
                "imageHosts": {
                  "description": "External https image hosts injected into the app CSP img-src (e.g. [\"covers.openlibrary.org\"]). Omitted when the app declares none.",
                  "items": {
                    "type": "string"
                  },
                  "type": "array"
                },
                "imports": {
                  "description": "WIT-style capability ids the app requests (e.g. \"charming:storage/kv@1.0\"). Empty if the app declares none.",
                  "items": {
                    "type": "string"
                  },
                  "type": "array"
                }
              },
              "required": ["imports", "exports"],
              "type": "object"
            },
            "claimed": {
              "description": "True if the app is owned by an authenticated account. False for anonymous apps still in the unclaimed grace window.",
              "type": "boolean"
            },
            "description": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Search-friendly summary set by the creating agent. Null on apps created before descriptions existed."
            },
            "displayName": {
              "description": "Human-readable name from the manifest.",
              "type": "string"
            },
            "expiresAt": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "ISO timestamp when an unclaimed app will be hard-deleted. Null for claimed apps."
            },
            "id": {
              "description": "UUID of the inspected app.",
              "type": "string"
            },
            "manifestId": {
              "description": "Effective manifest id. May differ from `sourceManifestId` when a conflicting id was rewritten on save.",
              "type": "string"
            },
            "manifestVersion": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Deprecated public contract marker. Canonical apps return null; legacy apps return their stored contract date."
            },
            "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"
            },
            "source": {
              "additionalProperties": false,
              "description": "Persisted source bundle. The same fields accepted by create_app/update_app.",
              "properties": {
                "module": {
                  "description": "Backend ES module source.",
                  "type": "string"
                },
                "styles": {
                  "anyOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "null"
                    }
                  ],
                  "description": "Optional CSS for the rendered app. Null if not set."
                },
                "ui": {
                  "anyOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "null"
                    }
                  ],
                  "description": "Frontend JavaScript rendered into the #app mount point. Null if not set."
                }
              },
              "required": ["module", "ui", "styles"],
              "type": "object"
            },
            "sourceManifestId": {
              "description": "Manifest id as authored in the persisted module source.",
              "type": "string"
            },
            "url": {
              "description": "Public app URL without an access token, built from the OWNER's handle when available (friendly form) and the UUID form otherwise. Safe to show the user. Browsers open via session cookie; agents should call get_app to mint a per-app token for write access.",
              "type": "string"
            }
          },
          "required": [
            "ok",
            "id",
            "url",
            "manifestId",
            "sourceManifestId",
            "displayName",
            "description",
            "revision",
            "appVersion",
            "manifestVersion",
            "capabilities",
            "claimed",
            "expiresAt",
            "source"
          ],
          "type": "object"
        }
      },
      {
        "annotations": {
          "destructiveHint": false,
          "openWorldHint": false,
          "readOnlyHint": true,
          "title": "List App Shares"
        },
        "description": "Use this to see who an app is shared with: lists every outstanding invitation and active grant, labeled by status (`pending` = invited, no access yet; `accepted` = active grantee; `invited` = email-only invite, no Charming account yet) and by role (`collaborator` = read + write; `end-user` = run + write data, not edit source; `viewer` = read only).",
        "execution": {
          "taskSupport": "forbidden"
        },
        "inputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "properties": {
            "app_id": {
              "description": "The app ID (UUID) to list shares for",
              "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"
            }
          },
          "required": ["app_id"],
          "type": "object"
        },
        "name": "list_app_shares",
        "outputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "additionalProperties": false,
          "properties": {
            "ok": {
              "const": true,
              "description": "Indicates success. Errors arrive as content with isError:true.",
              "type": "boolean"
            },
            "shares": {
              "description": "Every outstanding invitation and active grant on the app.",
              "items": {
                "additionalProperties": false,
                "properties": {
                  "accepted_at": {
                    "anyOf": [
                      {
                        "type": "string"
                      },
                      {
                        "type": "null"
                      }
                    ],
                    "description": "ISO timestamp the grant was accepted, or null while pending."
                  },
                  "grantee": {
                    "description": "Grantee identity (handle when set, else email).",
                    "type": "string"
                  },
                  "invited_at": {
                    "description": "ISO timestamp the invitation was created.",
                    "type": "string"
                  },
                  "role": {
                    "description": "Tier this grantee was invited at. `collaborator` = read + write (edit source). `end-user` = run the app and write its data, but cannot edit the source. `viewer` = read only. The role attaches to the grant at invite/share time and survives status transitions (pending → accepted, invited → pending on materialize), so it's safe to summarize access tiers from this field without consulting status.",
                    "enum": ["collaborator", "end-user", "viewer"],
                    "type": "string"
                  },
                  "status": {
                    "description": "`invited` = an email with no Charming account yet (materializes to `pending` once they register + verify). `pending` = a Charming user invited, not yet accepted, no access. `accepted` = active collaborator.",
                    "enum": ["pending", "accepted", "invited"],
                    "type": "string"
                  }
                },
                "required": ["grantee", "status", "role", "invited_at", "accepted_at"],
                "type": "object"
              },
              "type": "array"
            }
          },
          "required": ["ok", "shares"],
          "type": "object"
        }
      },
      {
        "annotations": {
          "destructiveHint": false,
          "openWorldHint": false,
          "readOnlyHint": true,
          "title": "List Apps"
        },
        "description": "List the caller's Charming apps — owned ones plus apps shared with them (accepted invites, marked `[shared with you]`; a read-only share also reads `(view-only)`). Use this to find an app's id before fetching, editing, or calling it. Returns per app: id, revision, role (owner | collaborator | end-user | viewer | team-admin | team-member), displayName, description (a search-friendly summary), url (machine API base), shareUrl (the link to give the user), lastUpdatedAt (ISO-8601 of the last write), claimed (always true for now — exposed for forward-compat), and capabilities (imports + exports). A `collaborator` can edit and run a shared app; an `end-user` can run it and write its data but cannot edit the app source; a `viewer` can only open and read it. The description disambiguates apps with similar display names; capabilities.exports lists each app's callable operations, exposing the cross-app integration surface without N+1 get_app_source calls. Paginate with `cursor` from a prior call's `nextCursor` line. Apps with `description: null` predate descriptions and have not yet been backfilled. When telling the user where to open or share an app, always give them `shareUrl` — never construct links for humans from the `url` field.",
        "execution": {
          "taskSupport": "forbidden"
        },
        "inputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "properties": {
            "cursor": {
              "description": "Opaque cursor from a prior call's `nextCursor` to fetch the next page.",
              "type": "string"
            },
            "limit": {
              "description": "Max apps to return (1-100, default 50).",
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          },
          "type": "object"
        },
        "name": "list_apps",
        "outputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "additionalProperties": false,
          "properties": {
            "apps": {
              "description": "Apps on this page, ordered by creation time newest-first.",
              "items": {
                "additionalProperties": false,
                "properties": {
                  "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)."
                  },
                  "capabilities": {
                    "additionalProperties": false,
                    "description": "Declared capability surface from the app manifest. The `exports` array is the discoverability hook for cross-app calls.",
                    "properties": {
                      "exports": {
                        "description": "Operations this app exposes to other apps via `window.charming.api(<id>).<export>()`. Use to plan cross-app integrations without round-tripping through get_app_source.",
                        "items": {
                          "type": "string"
                        },
                        "type": "array"
                      },
                      "imageHosts": {
                        "description": "External https image hosts injected into the app CSP img-src (e.g. [\"covers.openlibrary.org\"]). Omitted when the app declares none.",
                        "items": {
                          "type": "string"
                        },
                        "type": "array"
                      },
                      "imports": {
                        "description": "Capability tokens the app depends on (e.g. \"charming:storage/kv@1.0\", \"charming:browser/microphone@1.0\").",
                        "items": {
                          "type": "string"
                        },
                        "type": "array"
                      }
                    },
                    "required": ["imports", "exports"],
                    "type": "object"
                  },
                  "claimed": {
                    "description": "True if the app is owned by an authenticated user (i.e. has been claimed). list_apps only ever returns apps owned by the caller, so this is always true today; exposed for forward-compatibility with shared/discoverable listings.",
                    "type": "boolean"
                  },
                  "description": {
                    "anyOf": [
                      {
                        "type": "string"
                      },
                      {
                        "type": "null"
                      }
                    ],
                    "description": "Search-friendly summary written by the creating agent. Match user intent (\"open my protein tracker\", \"log food\") against this rather than display name alone. Null on apps created before descriptions existed and not yet backfilled."
                  },
                  "displayName": {
                    "description": "Human-readable name.",
                    "type": "string"
                  },
                  "id": {
                    "description": "App UUID. Pass to get_app to render or get_app_source to read.",
                    "type": "string"
                  },
                  "lastUpdatedAt": {
                    "description": "ISO-8601 timestamp of the last write to this app's row (create, update_app, set_remixable, unset_remixable, claim, version pointer edits, etc.). Use for recency cues and \"what did I work on most recently\" surfaces. Not the sort key — apps are ordered by creation time, so sort client-side by this field if you need recency order.",
                    "type": "string"
                  },
                  "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"
                  },
                  "role": {
                    "description": "`owner` = your personal app. `collaborator` = shared with you via an accepted invite (edit + run, but not share/delete/transfer). `end-user` = shared use-and-write-data (run the app and write its data, but cannot edit the app source). `viewer` = shared read-only (open and read, but no edits or mutating ops). `team-admin` / `team-member` = owned by a team you belong to; admins get full control, members can edit + run.",
                    "enum": [
                      "owner",
                      "collaborator",
                      "end-user",
                      "viewer",
                      "team-admin",
                      "team-member"
                    ],
                    "type": "string"
                  },
                  "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 base URL (stable /app/<uuid> form, token-free). Append /api/<op> to its path for out-of-band operation calls; mint a per-app token via get_app for write access. When giving the user a link, use shareUrl instead.",
                    "type": "string"
                  }
                },
                "required": [
                  "id",
                  "role",
                  "displayName",
                  "revision",
                  "description",
                  "url",
                  "shareUrl",
                  "lastUpdatedAt",
                  "claimed",
                  "capabilities"
                ],
                "type": "object"
              },
              "type": "array"
            },
            "nextCursor": {
              "description": "Opaque cursor for the next page. Pass back as `cursor` on the next list_apps call. Absent on the final page.",
              "type": "string"
            },
            "ok": {
              "const": true,
              "description": "Indicates success. Errors arrive as content with isError:true.",
              "type": "boolean"
            }
          },
          "required": ["ok", "apps"],
          "type": "object"
        }
      },
      {
        "annotations": {
          "destructiveHint": false,
          "openWorldHint": false,
          "readOnlyHint": true,
          "title": "List App Feedback"
        },
        "description": "List feedback rows for the caller's apps. Use this to review feedback previously recorded for an app. When `app_id` is set, returns rows for that one app (owner-checked); when omitted, returns rows across every app the caller owns. Paginate with `cursor` from a prior call's `cursor` field. Newest rows come first.",
        "execution": {
          "taskSupport": "forbidden"
        },
        "inputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "properties": {
            "app_id": {
              "description": "Optional app ID (UUID) to restrict to. When omitted, returns feedback across every app the caller owns.",
              "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"
            },
            "category": {
              "description": "Optional category filter. One of: bug, crash, enhancement, praise, other.",
              "enum": ["bug", "crash", "enhancement", "praise", "other"],
              "type": "string"
            },
            "cursor": {
              "description": "Opaque cursor from a prior call's `cursor` to fetch the next page.",
              "type": "string"
            },
            "limit": {
              "description": "Max rows to return (1-50, default 20).",
              "maximum": 50,
              "minimum": 1,
              "type": "integer"
            },
            "since": {
              "description": "Optional inclusive lower bound on `created_at` as an ISO-8601 timestamp.",
              "format": "date-time",
              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
              "type": "string"
            }
          },
          "type": "object"
        },
        "name": "list_feedback",
        "outputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "additionalProperties": false,
          "properties": {
            "cursor": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Opaque cursor for the next page. Null when this page is the last. Pass back as `cursor` on the next list_feedback call."
            },
            "items": {
              "description": "Feedback rows ordered by `created_at DESC, id DESC` (newest-first).",
              "items": {
                "additionalProperties": false,
                "properties": {
                  "app_id": {
                    "description": "UUID of the app the feedback is attached to.",
                    "type": "string"
                  },
                  "category": {
                    "description": "Feedback category. One of: bug, crash, enhancement, praise, other.",
                    "type": "string"
                  },
                  "crash_data": {
                    "anyOf": [
                      {
                        "additionalProperties": {},
                        "propertyNames": {
                          "type": "string"
                        },
                        "type": "object"
                      },
                      {
                        "type": "null"
                      }
                    ],
                    "description": "Crash details (message + optional stack/url/userAgent/traceId). Null unless category=crash."
                  },
                  "created_at": {
                    "description": "ISO-8601 timestamp when the row was inserted.",
                    "type": "string"
                  },
                  "id": {
                    "description": "UUID of the feedback row.",
                    "type": "string"
                  },
                  "source": {
                    "description": "Origin of the row: \"user\", \"agent\", or \"auto-crash\". Server-enforced.",
                    "type": "string"
                  },
                  "structured_data": {
                    "anyOf": [
                      {
                        "additionalProperties": {},
                        "propertyNames": {
                          "type": "string"
                        },
                        "type": "object"
                      },
                      {
                        "type": "null"
                      }
                    ],
                    "description": "Caller-supplied JSON payload. Null when not supplied."
                  },
                  "text": {
                    "anyOf": [
                      {
                        "type": "string"
                      },
                      {
                        "type": "null"
                      }
                    ],
                    "description": "Free-form feedback body. Null when not supplied."
                  },
                  "user_id": {
                    "anyOf": [
                      {
                        "type": "string"
                      },
                      {
                        "type": "null"
                      }
                    ],
                    "description": "Owning user id, denormalised from the app row. Null for unclaimed apps."
                  }
                },
                "required": [
                  "id",
                  "app_id",
                  "user_id",
                  "category",
                  "text",
                  "structured_data",
                  "crash_data",
                  "source",
                  "created_at"
                ],
                "type": "object"
              },
              "type": "array"
            },
            "ok": {
              "const": true,
              "description": "Indicates success. Errors arrive as content with isError:true.",
              "type": "boolean"
            }
          },
          "required": ["ok", "items", "cursor"],
          "type": "object"
        }
      },
      {
        "annotations": {
          "destructiveHint": true,
          "openWorldHint": true,
          "readOnlyHint": false,
          "title": "Mutate App"
        },
        "description": "Run a mutating backend operation on a Charming app and return its result, without editing code. Use this for operations that change state — including operations whose read/write safety can't be determined. For read-only operations, use query_app. If you don't know an operation's name or which tool it needs, call get_app — each operation lists its tool.",
        "execution": {
          "taskSupport": "forbidden"
        },
        "inputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "properties": {
            "app_id": {
              "description": "The app ID (UUID)",
              "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"
            },
            "op": {
              "description": "Operation name, routed to /api/:op inside the app module",
              "type": "string"
            },
            "params": {
              "additionalProperties": {},
              "description": "Optional JSON params",
              "propertyNames": {
                "type": "string"
              },
              "type": "object"
            }
          },
          "required": ["app_id", "op"],
          "type": "object"
        },
        "name": "mutate_app",
        "outputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "additionalProperties": false,
          "properties": {
            "error": {
              "additionalProperties": false,
              "description": "Set when ok=false. Always inspect kind before retrying.",
              "properties": {
                "details": {
                  "additionalProperties": {},
                  "description": "Handler-supplied structured context for the error. Present on kind=\"handler_error\" when the route handler attached details.",
                  "propertyNames": {
                    "type": "string"
                  },
                  "type": "object"
                },
                "errors": {
                  "description": "Validation issues from the route input/output schema. Present on kind=\"invalid_input\" and kind=\"invalid_output\".",
                  "items": {},
                  "type": "array"
                },
                "kind": {
                  "description": "Error kind from the app envelope (e.g. \"operation_failed\", \"invalid_input\", \"invalid_output\", \"handler_error\") or a transport-level fallback.",
                  "type": "string"
                },
                "message": {
                  "description": "Human-readable error message. Secrets are redacted.",
                  "type": "string"
                }
              },
              "required": ["kind", "message"],
              "type": "object"
            },
            "ok": {
              "description": "True if the operation handler returned a successful envelope. False when the app reported an operation error — distinct from a transport/protocol error.",
              "type": "boolean"
            },
            "value": {
              "description": "Operation return value when ok=true. Shape depends on the app and op called."
            }
          },
          "required": ["ok"],
          "type": "object"
        }
      },
      {
        "annotations": {
          "destructiveHint": false,
          "openWorldHint": true,
          "readOnlyHint": true,
          "title": "Query App"
        },
        "description": "Run a read-only backend operation on a Charming app and return its result, without editing code. Use this for operations that only read state. For operations that change state, use mutate_app. If you don't know an operation's name or which tool it needs, call get_app — each operation lists its tool.",
        "execution": {
          "taskSupport": "forbidden"
        },
        "inputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "properties": {
            "app_id": {
              "description": "The app ID (UUID)",
              "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"
            },
            "op": {
              "description": "Operation name, routed to /api/:op inside the app module",
              "type": "string"
            },
            "params": {
              "additionalProperties": {},
              "description": "Optional JSON params",
              "propertyNames": {
                "type": "string"
              },
              "type": "object"
            }
          },
          "required": ["app_id", "op"],
          "type": "object"
        },
        "name": "query_app",
        "outputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "additionalProperties": false,
          "properties": {
            "error": {
              "additionalProperties": false,
              "description": "Set when ok=false. Always inspect kind before retrying.",
              "properties": {
                "details": {
                  "additionalProperties": {},
                  "description": "Handler-supplied structured context for the error. Present on kind=\"handler_error\" when the route handler attached details.",
                  "propertyNames": {
                    "type": "string"
                  },
                  "type": "object"
                },
                "errors": {
                  "description": "Validation issues from the route input/output schema. Present on kind=\"invalid_input\" and kind=\"invalid_output\".",
                  "items": {},
                  "type": "array"
                },
                "kind": {
                  "description": "Error kind from the app envelope (e.g. \"operation_failed\", \"invalid_input\", \"invalid_output\", \"handler_error\") or a transport-level fallback.",
                  "type": "string"
                },
                "message": {
                  "description": "Human-readable error message. Secrets are redacted.",
                  "type": "string"
                }
              },
              "required": ["kind", "message"],
              "type": "object"
            },
            "ok": {
              "description": "True if the operation handler returned a successful envelope. False when the app reported an operation error — distinct from a transport/protocol error.",
              "type": "boolean"
            },
            "value": {
              "description": "Operation return value when ok=true. Shape depends on the app and op called."
            }
          },
          "required": ["ok"],
          "type": "object"
        }
      },
      {
        "annotations": {
          "destructiveHint": false,
          "openWorldHint": true,
          "readOnlyHint": false,
          "title": "Rename App"
        },
        "description": "Change a Charming app's URL slug — the human-readable, URL-safe name, distinct from its title/displayName and from its machine manifest id. Use this to give an app a different public URL. Titles and slugs are independent: editing the title with update_app does not change the slug; this tool does. Renaming changes the public URL, and the old URL keeps working by redirecting to the new one. Slug rules: lowercase letters, digits, and hyphens; 2-48 chars; may start with a letter or digit; no leading, trailing, or consecutive hyphens. When telling the user where to open or share the renamed 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 rename.",
              "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"
            },
            "app_name": {
              "description": "The new URL slug. Lowercase letters, digits, and hyphens; 2-48 chars; may start with a letter or digit; no leading/trailing or consecutive hyphens. Input is lowercased and trimmed.",
              "type": "string"
            }
          },
          "required": ["app_id", "app_name"],
          "type": "object"
        },
        "name": "rename_app",
        "outputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "additionalProperties": false,
          "properties": {
            "appName": {
              "description": "The new live slug for the app.",
              "type": "string"
            },
            "id": {
              "description": "UUID of the renamed app.",
              "type": "string"
            },
            "ok": {
              "const": true,
              "description": "Indicates success. Errors arrive as content with isError:true.",
              "type": "boolean"
            },
            "previousAppName": {
              "description": "The slug the app had before this rename. Retained as an alias so old URLs keep resolving (a redirect to the new slug).",
              "type": "string"
            },
            "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). Reflects the new slug.. 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"
            }
          },
          "required": ["ok", "id", "url", "shareUrl", "appName", "previousAppName"],
          "type": "object"
        }
      },
      {
        "annotations": {
          "destructiveHint": false,
          "openWorldHint": true,
          "readOnlyHint": false,
          "title": "Set Your Handle"
        },
        "description": "Change the signed-in user's handle (their username) — the first segment of their friendly app URLs (`https://charm.ing/<handle>/<app-name>`), so changing it changes the prefix of every app URL they share. Use this to give the current user a different handle. The old handle keeps working by redirecting to the new one, so links already shared stay valid. Acts on the current signed-in user only and takes no user id. Handle rules: lowercase letters, digits, and hyphens; 3-32 chars; must start with a letter; no leading, trailing, or consecutive hyphens; some words are reserved. Rate-limited to 3 changes per rolling 30 days.",
        "execution": {
          "taskSupport": "forbidden"
        },
        "inputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "properties": {
            "handle": {
              "description": "The desired new handle. Lowercase letters, digits, and hyphens; 3-32 chars; must start with a letter; no leading/trailing or consecutive hyphens. Input is lowercased and trimmed.",
              "type": "string"
            }
          },
          "required": ["handle"],
          "type": "object"
        },
        "name": "set_handle",
        "outputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "additionalProperties": false,
          "properties": {
            "handle": {
              "description": "The new live handle for the signed-in user.",
              "type": "string"
            },
            "ok": {
              "const": true,
              "description": "Indicates success. Errors arrive as content with isError:true.",
              "type": "boolean"
            },
            "previousHandle": {
              "description": "The handle the user had before this change. Retained as an alias so old friendly URLs (`/<previousHandle>/<app>`) keep resolving (a redirect to the new handle). Empty string when the user had no prior handle.",
              "type": "string"
            }
          },
          "required": ["ok", "handle", "previousHandle"],
          "type": "object"
        }
      },
      {
        "annotations": {
          "destructiveHint": false,
          "openWorldHint": true,
          "readOnlyHint": false,
          "title": "Make App Public"
        },
        "description": "Make a Charming app PUBLIC: anyone who opens its URL can use it with no login and read AND write its SHARED data. Use this for a totally-open surface (a poll, an RSVP list, a guestbook) where requiring sign-in or per-person invites is not worth it. WARNING — this is a shared, unauthenticated write surface: every anonymous visitor reads and writes the SAME data pool with no per-visitor isolation, so anyone with the URL can overwrite or wipe the data. Surface the returned `message` verbatim so the user understands that before sharing. For per-person access that keeps an audit trail and write control, share with the `end-user` role instead (it requires login). Idempotent: calling on an already-public app returns the same URL without re-firing analytics. Owner-only; anonymous apps must be claimed first.",
        "execution": {
          "taskSupport": "forbidden"
        },
        "inputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "properties": {
            "app_id": {
              "description": "The app ID (UUID) to make public",
              "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"
            }
          },
          "required": ["app_id"],
          "type": "object"
        },
        "name": "set_public",
        "outputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "additionalProperties": false,
          "properties": {
            "message": {
              "description": "Consequence copy the agent should surface verbatim to the user. WARNS that anyone with the URL can read and overwrite (or wipe) the shared data, with no login and no per-visitor isolation.",
              "type": "string"
            },
            "ok": {
              "const": true,
              "description": "Indicates success. Errors arrive as content with isError:true.",
              "type": "boolean"
            },
            "public_url": {
              "description": "Public URL for the now-public app. Friendly `/<handle>/<app-name>` form when available, `/app/<uuid>` otherwise. Anyone can open this URL with no login and read AND write the app's SHARED data. Free of write-capable `?t=` tokens.",
              "type": "string"
            }
          },
          "required": ["ok", "public_url", "message"],
          "type": "object"
        }
      },
      {
        "annotations": {
          "destructiveHint": false,
          "openWorldHint": true,
          "readOnlyHint": false,
          "title": "Share App as Remixable"
        },
        "description": "Mark a Charming app as remixable. Use this to let anyone who opens the app's URL get their own brand-new editable copy, while the original is never mutated by visitors. Each remix is an independent app the visitor can keep and later claim by signing up. Idempotent: calling on an already-remixable app returns the same URL without re-firing analytics. Anonymous apps cannot be made remixable; the app must be claimed first.",
        "execution": {
          "taskSupport": "forbidden"
        },
        "inputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "properties": {
            "app_id": {
              "description": "The app ID (UUID) to share as remixable",
              "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"
            }
          },
          "required": ["app_id"],
          "type": "object"
        },
        "name": "set_remixable",
        "outputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "additionalProperties": false,
          "properties": {
            "message": {
              "description": "Consequence copy the agent should surface verbatim to the user when reporting that the share was created. Spells out that other people will get their own remix — the user's data is untouched.",
              "type": "string"
            },
            "ok": {
              "const": true,
              "description": "Indicates success. Errors arrive as content with isError:true.",
              "type": "boolean"
            },
            "public_url": {
              "description": "Public share URL for the now-remixable app. Friendly `/<handle>/<app-name>` form when available, `/app/<uuid>` otherwise. Visitors opening this URL get their own brand-new editable copy; the original is never mutated. Free of write-capable `?t=` tokens.",
              "type": "string"
            }
          },
          "required": ["ok", "public_url", "message"],
          "type": "object"
        }
      },
      {
        "annotations": {
          "destructiveHint": true,
          "openWorldHint": true,
          "readOnlyHint": false,
          "title": "Set App Starter Prompt"
        },
        "description": "Set or clear an app's authored starter prompt — a short getting-started instruction prefilled in the chat host when a visitor clicks \"Open in Claude\" or \"Open in ChatGPT\" on the app, replacing the generic \"I'm using this Charming app\" body. Use this to frame a shared/remixable app's intended first action for visitors. Write a generic instruction — do NOT embed the app's URL: the app name, description, and URL (and, for unclaimed apps, an access token) are appended automatically at render time, so a hardcoded URL would point every remixer at the template instead of their own copy. Pass `starter_prompt: null` (or an empty string) to clear and revert to the generic default. Owner-only; anonymous apps must be claimed first. Idempotent (re-setting the same value is a no-op). Length cap: 2000 characters.",
        "execution": {
          "taskSupport": "forbidden"
        },
        "inputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "properties": {
            "app_id": {
              "description": "The app ID (UUID) whose starter prompt should change.",
              "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"
            },
            "starter_prompt": {
              "anyOf": [
                {
                  "maxLength": 2000,
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Getting-started instruction the chat host receives when a visitor clicks \"Open in Claude\" / \"Open in ChatGPT\". The authored text leads; the app's name, description, URL, and (for unclaimed apps) an access token are appended automatically as context — so write a generic 1–3 sentence instruction (the first action you want the visitor to take, plus any persona/voice), NOT a self-contained prompt and NOT the app's URL. Pass null or \"\" to clear and revert to the generic default. Max 2000 characters."
            }
          },
          "required": ["app_id", "starter_prompt"],
          "type": "object"
        },
        "name": "set_starter_prompt",
        "outputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "additionalProperties": false,
          "properties": {
            "message": {
              "description": "Consequence copy the agent should surface to the user. Explains where the prompt is rendered (Open in Claude / Open in ChatGPT) and how to clear it.",
              "type": "string"
            },
            "ok": {
              "const": true,
              "description": "Indicates success. Errors arrive as content with isError:true.",
              "type": "boolean"
            },
            "starter_prompt": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "The current starter prompt on the app row after this call. Null when the prompt was cleared (or normalized away from whitespace-only input). Otherwise the verbatim authored string."
            }
          },
          "required": ["ok", "starter_prompt", "message"],
          "type": "object"
        }
      },
      {
        "annotations": {
          "destructiveHint": true,
          "openWorldHint": true,
          "readOnlyHint": false,
          "title": "Share App with a User"
        },
        "description": "Invite someone (by Charming handle or email) to one of your apps, OR change the role of someone you already invited. Use this when the user wants to give a specific person access to an app, or to change what an existing collaborator/end-user/viewer can do. Creates a PENDING invitation: the invitee gets no access and sees nothing in their workspace until they accept. Pick a role: `collaborator` (default) can open, edit, and run the app (shared state, same data); `end-user` can open the app and write its data (record entries, save state) but cannot edit the app source; `viewer` can only open and use the read-only surface — viewers cannot edit, run mutating ops, or change anything. If the grantee already has a share, calling this again WITH a `role` changes their role in place (status `updated`, no new invitation, no second email) — this is how you flip a collaborator to an end-user and back; re-sharing WITHOUT a `role` leaves the existing role untouched. No share role can ever share, delete, or transfer the app. The invitee does NOT need a Charming account first — inviting an email with no account stores a pending invitation and emails them a sign-up link; it activates once they register and verify that email (an unknown handle still errors, since there is no address to reach). Revoke anytime with `unshare_app`.",
        "execution": {
          "taskSupport": "forbidden"
        },
        "inputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "properties": {
            "app_id": {
              "description": "The app ID (UUID) to share",
              "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"
            },
            "grantee": {
              "description": "The invitee's Charming handle (e.g. `ada` or `@ada`) or account email",
              "minLength": 1,
              "type": "string"
            },
            "role": {
              "description": "The access level: `collaborator` (default — edit and run), `end-user` (run + write data, not edit source), or `viewer` (read-only)",
              "enum": ["collaborator", "end-user", "viewer"],
              "type": "string"
            }
          },
          "required": ["app_id", "grantee"],
          "type": "object"
        },
        "name": "share_app",
        "outputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "additionalProperties": false,
          "properties": {
            "grantee": {
              "description": "The resolved grantee (handle when set, else email) the invitation was sent to.",
              "type": "string"
            },
            "message": {
              "description": "Consequence copy the agent should surface verbatim: the invitee has no access until they accept, and what they will be able to do once they do.",
              "type": "string"
            },
            "ok": {
              "const": true,
              "description": "Indicates success. Errors arrive as content with isError:true.",
              "type": "boolean"
            },
            "status": {
              "description": "Sharing creates an invitation, not access. `pending` = the grantee has a Charming account and must accept (dashboard or invite email). `invited` = the email has no account yet; it becomes a pending share once they register and verify that address. `updated` = the grantee already had a share and re-sharing with a `role` changed it in place (no new invitation, no second email).",
              "enum": ["pending", "invited", "updated"],
              "type": "string"
            }
          },
          "required": ["ok", "status", "grantee", "message"],
          "type": "object"
        }
      },
      {
        "annotations": {
          "destructiveHint": false,
          "openWorldHint": false,
          "readOnlyHint": false,
          "title": "Submit App Feedback"
        },
        "description": "Record agent-authored feedback about a Charming app. Use this to log observed bugs, suggested enhancements, caught crash reports, or qualitative notes. Source is server-enforced to \"agent\" — agents cannot impersonate user or auto-crash sources. The caller must own the app.",
        "execution": {
          "taskSupport": "forbidden"
        },
        "inputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "properties": {
            "app_id": {
              "description": "The app ID (UUID) the feedback is attached to.",
              "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"
            },
            "category": {
              "description": "Feedback category. One of: bug, crash, enhancement, praise, other. Pass \"crash\" only when crash_data carries the captured error.",
              "enum": ["bug", "crash", "enhancement", "praise", "other"],
              "type": "string"
            },
            "crash_data": {
              "description": "Required when category=crash. Optional otherwise.",
              "properties": {
                "message": {
                  "description": "Error message; required when category=crash.",
                  "type": "string"
                },
                "stack": {
                  "type": "string"
                },
                "traceId": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "userAgent": {
                  "type": "string"
                }
              },
              "required": ["message"],
              "type": "object"
            },
            "structured_data": {
              "additionalProperties": {},
              "description": "Optional JSON object with caller-supplied structured context (e.g. route, repro inputs).",
              "propertyNames": {
                "type": "string"
              },
              "type": "object"
            },
            "text": {
              "description": "Free-form feedback body (up to 16384 chars). Optional, but at least one of text / structured_data / crash_data should usually be set so the row is actionable.",
              "maxLength": 16384,
              "type": "string"
            }
          },
          "required": ["app_id", "category"],
          "type": "object"
        },
        "name": "submit_feedback",
        "outputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "additionalProperties": false,
          "properties": {
            "app_id": {
              "description": "UUID of the app the feedback was attached to.",
              "type": "string"
            },
            "created_at": {
              "description": "ISO-8601 timestamp when the row was inserted.",
              "type": "string"
            },
            "id": {
              "description": "UUID of the newly-inserted feedback row.",
              "type": "string"
            },
            "ok": {
              "const": true,
              "description": "Indicates success. Errors arrive as content with isError:true.",
              "type": "boolean"
            }
          },
          "required": ["ok", "id", "app_id", "created_at"],
          "type": "object"
        }
      },
      {
        "annotations": {
          "destructiveHint": true,
          "openWorldHint": true,
          "readOnlyHint": false,
          "title": "Make App Private"
        },
        "description": "Make a public Charming app PRIVATE again. Use this to close anonymous access: visitors can no longer open the app without logging in (the URL requires login). Data already written by anonymous visitors is retained. Idempotent: calling on an already-private app is a no-op.",
        "execution": {
          "taskSupport": "forbidden"
        },
        "inputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "properties": {
            "app_id": {
              "description": "The app ID (UUID) to make private",
              "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"
            }
          },
          "required": ["app_id"],
          "type": "object"
        },
        "name": "unset_public",
        "outputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "additionalProperties": false,
          "properties": {
            "message": {
              "description": "Consequence copy the agent should surface verbatim. Tells the user the app now requires login again; data already written by anonymous visitors is retained.",
              "type": "string"
            },
            "ok": {
              "const": true,
              "description": "Indicates success. Errors arrive as content with isError:true.",
              "type": "boolean"
            }
          },
          "required": ["ok", "message"],
          "type": "object"
        }
      },
      {
        "annotations": {
          "destructiveHint": true,
          "openWorldHint": true,
          "readOnlyHint": false,
          "title": "Stop Allowing Remixes"
        },
        "description": "Stop allowing remixes of a Charming app. Use this to turn off auto-forking for new visitors; existing remixes survive untouched. Idempotent: calling on an already-non-remixable app is a no-op.",
        "execution": {
          "taskSupport": "forbidden"
        },
        "inputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "properties": {
            "app_id": {
              "description": "The app ID (UUID) to stop sharing as remixable",
              "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"
            }
          },
          "required": ["app_id"],
          "type": "object"
        },
        "name": "unset_remixable",
        "outputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "additionalProperties": false,
          "properties": {
            "message": {
              "description": "Consequence copy the agent should surface verbatim. Tells the user new visitors can no longer remix, while existing remixes are unaffected.",
              "type": "string"
            },
            "ok": {
              "const": true,
              "description": "Indicates success. Errors arrive as content with isError:true.",
              "type": "boolean"
            }
          },
          "required": ["ok", "message"],
          "type": "object"
        }
      },
      {
        "annotations": {
          "destructiveHint": true,
          "openWorldHint": true,
          "readOnlyHint": false,
          "title": "Revoke App Share"
        },
        "description": "Revoke another user's access to one of your apps — removes a pending invitation or an accepted collaborator grant. Use this when the user wants to withdraw an invitation or remove a collaborator. Takes effect immediately for new requests. Idempotent: revoking a grant that does not exist is a no-op.",
        "execution": {
          "taskSupport": "forbidden"
        },
        "inputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "properties": {
            "app_id": {
              "description": "The app ID (UUID) to revoke access on",
              "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"
            },
            "grantee": {
              "description": "The grantee's Charming handle or account email",
              "minLength": 1,
              "type": "string"
            }
          },
          "required": ["app_id", "grantee"],
          "type": "object"
        },
        "name": "unshare_app",
        "outputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "additionalProperties": false,
          "properties": {
            "ok": {
              "const": true,
              "description": "Indicates success. Errors arrive as content with isError:true.",
              "type": "boolean"
            },
            "removed": {
              "description": "True when a pending invitation or active grant was removed; false when there was nothing to remove (idempotent no-op).",
              "type": "boolean"
            }
          },
          "required": ["ok", "removed"],
          "type": "object"
        }
      },
      {
        "_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"
        },
        "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"
        }
      },
      {
        "annotations": {
          "destructiveHint": true,
          "openWorldHint": true,
          "readOnlyHint": false,
          "title": "Upload Asset"
        },
        "description": "UPLOAD A STATIC ASSET TO AN APP so app code stays small and the app reads it back same-origin. Use this when an app needs a large or static file — an image, PDF, audio clip, or a dataset bigger than a few KB — that would otherwise be inlined into `module`/`ui` and blow the 256 KiB source cap. Provide EXACTLY ONE of: `sourceUrl` (PREFERRED for any binary — the SERVER fetches the remote file, follows redirects, and the bytes never transit this tool call), `text` (UTF-8 dataset/JSON/CSV — no encoding needed), or `dataBase64` (LAST RESORT, tiny binaries only: large base64 arguments can stall inside some MCP clients before ever reaching the server, so keep it under ~16 KB and use `sourceUrl` for anything bigger). The app reads the asset via `window.charming.assets.getUrl(key)` (for <img>/<a>) or `env.assets.get(key)` in its backend (the app must declare `charming:storage/blob@1.0` in manifest.capabilities.imports to use env.assets). Caps: 10 MiB/asset, 50 assets/app, 100 MiB/app.",
        "execution": {
          "taskSupport": "forbidden"
        },
        "inputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "properties": {
            "appId": {
              "description": "The app id (app_modules.id) to attach the asset to. Must be an app you own.",
              "type": "string"
            },
            "contentType": {
              "description": "MIME type. Defaults: text/plain for text, sniffed for sourceUrl, application/octet-stream otherwise.",
              "type": "string"
            },
            "dataBase64": {
              "description": "Base64 of a SMALL binary. Last resort — prefer sourceUrl: large base64 tool arguments can stall in some MCP clients before reaching the server. Keep the encoded string under ~16 KB; the server-side hard cap is 64 KiB decoded.",
              "type": "string"
            },
            "key": {
              "description": "Asset name, e.g. \"dataset_v1.json\". URL-safe [a-zA-Z0-9._-], <=128 chars, no leading dot.",
              "type": "string"
            },
            "sourceUrl": {
              "description": "https URL the server fetches and stores. Bytes never pass through this tool call.",
              "format": "uri",
              "type": "string"
            },
            "text": {
              "description": "UTF-8 text payload (datasets, JSON, CSV). No base64.",
              "type": "string"
            }
          },
          "required": ["appId", "key"],
          "type": "object"
        },
        "name": "upload_asset",
        "outputSchema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "additionalProperties": false,
          "properties": {
            "appId": {
              "description": "UUID of the asset-owning app.",
              "type": "string"
            },
            "bytes": {
              "description": "Stored byte length of the asset payload after decode/fetch.",
              "maximum": 9007199254740991,
              "minimum": -9007199254740991,
              "type": "integer"
            },
            "contentType": {
              "description": "Effective MIME type stored with the asset (supplied, sniffed from sourceUrl, or defaulted).",
              "type": "string"
            },
            "key": {
              "description": "The asset key it was stored under. Same value passed in.",
              "type": "string"
            },
            "ok": {
              "const": true,
              "description": "Indicates success. Errors arrive as content with isError:true.",
              "type": "boolean"
            },
            "url": {
              "description": "Same-origin URL serving the asset. Use in <img src>/<a href>/fetch, or read in the backend via env.assets.get(key). Equivalent to window.charming.assets.getUrl(key).",
              "type": "string"
            }
          },
          "required": ["ok", "appId", "key", "bytes", "contentType", "url"],
          "type": "object"
        }
      }
    ]
  }
}
