How to fix: For tools that render a UI resource, add openai/toolInvocation with string fields invoking and invoked (present-tense labels shown while the tool runs and after it finishes, e.g. "Searching…" / "Searched").
How to fix: Use a ui:// URI and text/html;profile=mcp-app. For mcp-use v1, use widgetMetadata.metadata.widgetDescription; for v2, use the bound tool's view.description. The current description check reads _meta.ui.widgetDescription or _meta["openai/widgetDescription"] on resource contents or linked tools, not resource.description. Consequently, v2 view.description alone may leave this warning: report the checker compatibility gap instead of adding unsupported v1 fields or claiming the warning is fixed.
How to fix: For ChatGPT submission, set one exact HTTPS origin such as "https://app.example.com" on every view via _meta.ui.domain (or openai/widgetDomain). Paths are not valid and all views in one app must share the origin.
The title clearly communicates the tool’s action and subject. Keep it concise.
Description84/100
Use this to inspect an accepted app build without running or publishing it. Use include_source to recover the exact submitted source. Status remains available after authoring access to the ESM cohort is removed.
The description explains the capability, when to use it, and what to expect.
Inputs88/100
Explain what happens when this optional value is omitted.
build_id
Build ID returned by create_app or update_app.
Clear enough to supply this argument.
include_source
Include the immutable source accepted for this build.
Explain what happens when this optional value is omitted.
Annotations75/100
Declare idempotentHint based on the tool’s actual behavior.
The title clearly communicates the tool’s action and subject. Keep it concise.
Description96/100
Use this to cancel an accepted app build before it publishes. Repeating cancellation returns the same terminal status. Cancellation leaves accepted desired source editable and keeps the last active app running.
The description explains the capability, when to use it, and what to expect.
Inputs98/100
Input documentation gives an agent enough context to populate the arguments.
The title clearly communicates the tool’s action and subject. Keep it concise.
Description95/100
Create a new interactive web app and render it inline. Use this to build a new app from a request. Existing-contract apps return after publication. Eligible authors can select the ESM manifest schema with an idempotency_key to accept a background build; that result contains a buildId to poll with get_app_build, and has no app URL before publication. 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. Read the public docs without a browser using read_docs({ path: "build-mcp.md" }); read_docs({}) lists available pages. 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.
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
module
Eligible ESM authors may instead select `$schema: "https://charm.ing/schema/app-manifest/2026-09-05.json"`, declare target-specific `dependencies.server` and `dependencies.client`, and use ordinary package imports plus exact versioned platform imports. For ESM persistence, use `import { kv } from 'charming:storage/[email protected]'` and call `kv.get`/`kv.put` inside route handlers; omit `capabilities`. ESM creation takes its name from `manifest.meta.name`; omit `display_name`. For the existing contract, 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 state in the existing contract, `capabilities.imports` must include "charming:storage/[email protected]" (without it `env.storage` is undefined and every read/write throws `storage capability not granted`). A route handler in `export const routes = [...]` receives `(input, { env, ctx, request })` and returns exactly the value declared by `outputSchema`; for an array schema, use `handler: async (_input, { env }) => (await env.storage.get("key")) ?? []`. Charming creates the transport envelope. Do not add a `{ ok, value }` or `{ value }` envelope unless those fields belong to `outputSchema` itself. A named context reads storage as `context.env.storage`. The optional unmatched-request fallback has the separate signature `export default { fetch(request, env, ctx) { ... } }`, where the second argument is the environment itself; it adds no discoverable route metadata. `env.user` is always present, not gated by any import: the caller's public identity (`{ id, handle?, name?, image? }`) or `null`. It lives only on `env` — read `env.user` (or `context.env.user`); there is no `ctx.user`. Full reference: call read_docs with path `llms-full.txt` (https://charm.ing/docs/llms-full.txt). For the existing contract, 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 a chat host 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/[email protected]" capability 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. The following capability declarations apply only to the existing contract. Camera, microphone, geolocation, screen capture (getDisplayMedia), clipboard reading, and Web MIDI need no import and no claim: Charming finds these calls in `ui` at publish and, on the web, asks each viewer before the app gets the device, so handle NotAllowedError. Other sensitive browser capabilities must declare their import in `capabilities.imports` — "charming:browser/[email protected]" (device orientation/motion: DeviceOrientationEvent/DeviceMotionEvent + iOS requestPermission) or "charming:browser/[email protected]" (ambient light, new AmbientLightSensor), both granted only after the app is claimed, or "charming:browser/[email protected]" (native client storage — localStorage/sessionStorage/IndexedDB — web-only). To call external HTTPS APIs from backend code, declare "charming:network/[email protected]" 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/[email protected]" to get `env.fetch` (claimed apps only) — a sealed outbound fetch that substitutes `{{secret:NAME}}` references in request HEADER values or query-parameter VALUES host-side (never a parameter name, the host, path, fragment, or body), so the key never enters app source or the sandbox; write the placeholder literally in the URL string — `URLSearchParams.set(...)` or `encodeURIComponent(...)` percent-encodes it first and it will NOT resolve; the app OWNER opens App settings, then Secrets, at `/<owner-handle>/~/apps/<app-name>/settings/secrets`, while `/app/<id>/secrets` remains the machine HTTP API. The agent only references the NAME. Never embed API keys in source. To call a public API or stream from browser code with fetch, EventSource, or WebSocket, list each exact https:// or wss:// origin in `manifest.permissions.browser["connect-src"]` (web page only; chat embeds block it). To render remote images, list each exact https origin in `manifest.permissions.browser["img-src"]`. To make an image render in ANY host — standalone AND embedded inline, where the host's 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 and in a host that injects no such CSP, but not in every embed; prefer `images.load(...)` whenever the app may be embedded. Both enforce the declared origins; neither bypasses them.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
ui
Under the explicit ESM contract, this is an ES module: import declared client packages and import { api, onStateChange } from "charming:ui/[email protected]" to call this app and subscribe to its state changes. Under the existing contract, follow the classic JavaScript rules below. 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. To keep the view current, register `window.charming.onStateChange(() => refresh())`, where `refresh` refetches your read operation and updates the DOM surgically rather than wiping #app. It fires after every write from any caller (this tab, another browser, an agent, a Routine, a webhook) and after a reconnect, so never poll with setInterval or a setTimeout loop. See the charming:app-guide prompt for a canonical example.
Clear enough to supply this argument.
styles
Optional CSS for the rendered app
Explain the meaning of this value, not just its name.
display_name
Optional display name override
Explain the meaning of this value, not just its name.
expected_revision
Required when ESM source reuses an existing manifest.id. Pass the desired revision from get_app_source.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
migrate_contract
Set true to migrate an existing legacy manifest.id to ESM, together with expected_revision. Source submissions do not roll back contracts. History can explicitly restore a retained validated existing-contract revision.
Clear enough to supply this argument.
idempotency_key
Required for ESM builds: 8–128 visible ASCII characters. Retry the exact request with the same key to recover its build; use a new key for changed source.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
team_id
Optional destination team id. Only a team owner or admin can create an App there. Omit it to create a personal App.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
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.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
Annotations75/100
Declare idempotentHint based on the tool’s actual behavior.
Align the output schema with the tool’s documented result.
View output schema
{
"type": "object",
"anyOf": [
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"ok",
"id",
"url",
"shareUrl",
"revision",
"icon"
],
"properties": {
"id": {
"type": "string",
"description": "UUID of the created or updated app."
},
"ok": {
"type": "boolean",
"const": true,
"description": "Indicates success. Errors arrive as content with isError:true."
},
"url": {
"type": "string",
"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."
},
"icon": {
"type": "object",
"required": [
"emoji",
"bg"
],
"properties": {
"bg": {
"type": "string",
"description": "The icon background as a hex color (e.g. \"#1d8a4e\")."
},
"emoji": {
"type": "string",
"description": "The single emoji rendered on the icon."
}
},
"description": "The effective home-screen icon stored for the app. Canonical source declares it at `manifest.meta.icon`.",
"additionalProperties": false
},
"appName": {
"type": [
"string",
"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)."
},
"revision": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0,
"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."
},
"shareUrl": {
"type": "string",
"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."
},
"warnings": {
"type": "array",
"items": {
"type": "string"
},
"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."
},
"advisories": {
"type": "array",
"items": {
"type": "object",
"required": [
"kind",
"summary"
],
"properties": {
"data": {
"type": "object",
"description": "Kind-specific structured payload. Shape varies per advisory kind.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"kind": {
"type": "string",
"description": "Stable advisory identifier (e.g. \"legacy-bridge\")."
},
"doc_url": {
"type": "string",
"description": "Optional docs pointer for this advisory kind."
},
"summary": {
"type": "string",
"description": "Agent-facing summary. Self-sufficient; no extra context required."
},
"severity": {
"enum": [
"info",
"warn"
],
"type": "string",
"description": "Severity; omitted advisories are treated as 'info'."
},
"userSummary": {
"type": "string",
"description": "End-user-facing summary. Set when the advisory should render in-app."
}
},
"additionalProperties": false
},
"description": "Structured non-fatal advisories, including authoring issues and unread staff feedback responses. Each advisory.summary is also appended to the text content for the LLM path."
}
},
"additionalProperties": false
},
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"ok",
"buildId",
"intent",
"state",
"sourceEtag",
"statusUrl",
"attempts",
"acceptedAt",
"updatedAt",
"finishedAt",
"queueDeadline",
"deadline",
"inspectionExpiresAt",
"idempotencyExpiresAt",
"elapsedMs",
"lockState",
"inputDigest"
],
"properties": {
"ok": {
"type": "boolean",
"const": true
},
"url": {
"type": "string",
"description": "App URL. Present only after successful publication."
},
"appId": {
"type": "string",
"description": "Existing target app ID, or the created app ID after publication."
},
"error": {
"type": "object",
"required": [
"kind",
"message",
"retryable"
],
"properties": {
"kind": {
"type": "string"
},
"line": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991
},
"column": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991
},
"target": {
"type": "string"
},
"details": {
"anyOf": [
{
"type": "object",
"required": [
"execution",
"operationId"
],
"properties": {
"execution": {
"enum": [
"not_started",
"may_have_run"
],
"type": "string"
},
"operationId": {
"type": "string",
"minLength": 1
},
"reservationId": {
"type": "string",
"minLength": 1
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"execution"
],
"properties": {
"execution": {
"type": "string",
"const": "may_have_run"
}
},
"additionalProperties": false
}
]
},
"message": {
"type": "string"
},
"retryable": {
"type": "boolean"
},
"specifier": {
"type": "string"
}
},
"additionalProperties": false
},
"state": {
"enum": [
"queued",
"resolving",
"building",
"validating",
"published",
"failed",
"superseded",
"canceled",
"expired"
],
"type": "string"
},
"intent": {
"type": "string",
"description": "The accepted operation: create, update, migrate, restore, or copy."
},
"source": {
"type": "object",
"required": [
"module",
"ui",
"styles",
"description"
],
"properties": {
"ui": {
"type": [
"string",
"null"
]
},
"module": {
"type": "string"
},
"styles": {
"type": [
"string",
"null"
]
},
"description": {
"type": [
"string",
"null"
]
}
},
"description": "Exact accepted source, returned only when include_source is true.",
"additionalProperties": false
},
"buildId": {
"type": "string",
"description": "Durable build ID. Use get_app_build to inspect progress or source."
},
"attempts": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0
},
"deadline": {
"type": "string"
},
"revision": {
"type": "integer",
"maximum": 9007199254740991,
"description": "Published app source revision.",
"exclusiveMinimum": 0
},
"elapsedMs": {
"type": "number",
"minimum": 0
},
"lockState": {
"enum": [
"locked",
"unresolved"
],
"type": "string"
},
"statusUrl": {
"type": "string",
"description": "Authenticated HTTP status URL for this build."
},
"updatedAt": {
"type": "string"
},
"acceptedAt": {
"type": "string"
},
"finishedAt": {
"type": [
"string",
"null"
]
},
"lockDigest": {
"type": "string"
},
"sourceEtag": {
"type": "string",
"description": "ETag identifying the immutable input accepted for this build."
},
"inputDigest": {
"type": "string"
},
"queueDeadline": {
"type": "string"
},
"activeRevision": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0
},
"desiredRevision": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0
},
"retryAfterSeconds": {
"type": "integer",
"maximum": 9007199254740991,
"description": "Wait at least this many seconds before polling again.",
"exclusiveMinimum": 0
},
"inspectionExpiresAt": {
"type": [
"string",
"null"
]
},
"idempotencyExpiresAt": {
"type": [
"string",
"null"
]
},
"resolvedDependencies": {
"type": "object",
"required": [
"server",
"client"
],
"properties": {
"client": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"type": "string"
}
},
"server": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"type": "string"
}
}
},
"additionalProperties": false
}
},
"additionalProperties": false
}
],
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"ok"
],
"properties": {
"id": {
"type": "string",
"description": "UUID of the created or updated app."
},
"ok": {
"type": "boolean",
"const": true
},
"url": {
"type": "string",
"description": "App URL. Present only after successful publication."
},
"icon": {
"type": "object",
"required": [
"emoji",
"bg"
],
"properties": {
"bg": {
"type": "string",
"description": "The icon background as a hex color (e.g. \"#1d8a4e\")."
},
"emoji": {
"type": "string",
"description": "The single emoji rendered on the icon."
}
},
"description": "The effective home-screen icon stored for the app. Canonical source declares it at `manifest.meta.icon`.",
"additionalProperties": false
},
"appId": {
"type": "string",
"description": "Existing target app ID, or the created app ID after publication."
},
"error": {
"type": "object",
"required": [
"kind",
"message",
"retryable"
],
"properties": {
"kind": {
"type": "string"
},
"line": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991
},
"column": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991
},
"target": {
"type": "string"
},
"details": {
"anyOf": [
{
"type": "object",
"required": [
"execution",
"operationId"
],
"properties": {
"execution": {
"enum": [
"not_started",
"may_have_run"
],
"type": "string"
},
"operationId": {
"type": "string",
"minLength": 1
},
"reservationId": {
"type": "string",
"minLength": 1
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"execution"
],
"properties": {
"execution": {
"type": "string",
"const": "may_have_run"
}
},
"additionalProperties": false
}
]
},
"message": {
"type": "string"
},
"retryable": {
"type": "boolean"
},
"specifier": {
"type": "string"
}
},
"additionalProperties": false
},
"state": {
"enum": [
"queued",
"resolving",
"building",
"validating",
"published",
"failed",
"superseded",
"canceled",
"expired"
],
"type": "string"
},
"intent": {
"type": "string",
"description": "The accepted operation: create, update, migrate, restore, or copy."
},
"source": {
"type": "object",
"required": [
"module",
"ui",
"styles",
"description"
],
"properties": {
"ui": {
"type": [
"string",
"null"
]
},
"module": {
"type": "string"
},
"styles": {
"type": [
"string",
"null"
]
},
"description": {
"type": [
"string",
"null"
]
}
},
"description": "Exact accepted source, returned only when include_source is true.",
"additionalProperties": false
},
"appName": {
"type": [
"string",
"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)."
},
"buildId": {
"type": "string",
"description": "Durable build ID. Use get_app_build to inspect progress or source."
},
"attempts": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0
},
"deadline": {
"type": "string"
},
"revision": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0,
"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."
},
"shareUrl": {
"type": "string",
"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."
},
"warnings": {
"type": "array",
"items": {
"type": "string"
},
"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."
},
"elapsedMs": {
"type": "number",
"minimum": 0
},
"lockState": {
"enum": [
"locked",
"unresolved"
],
"type": "string"
},
"statusUrl": {
"type": "string",
"description": "Authenticated HTTP status URL for this build."
},
"updatedAt": {
"type": "string"
},
"acceptedAt": {
"type": "string"
},
"advisories": {
"type": "array",
"items": {
"type": "object",
"required": [
"kind",
"summary"
],
"properties": {
"data": {
"type": "object",
"description": "Kind-specific structured payload. Shape varies per advisory kind.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"kind": {
"type": "string",
"description": "Stable advisory identifier (e.g. \"legacy-bridge\")."
},
"doc_url": {
"type": "string",
"description": "Optional docs pointer for this advisory kind."
},
"summary": {
"type": "string",
"description": "Agent-facing summary. Self-sufficient; no extra context required."
},
"severity": {
"enum": [
"info",
"warn"
],
"type": "string",
"description": "Severity; omitted advisories are treated as 'info'."
},
"userSummary": {
"type": "string",
"description": "End-user-facing summary. Set when the advisory should render in-app."
}
},
"additionalProperties": false
},
"description": "Structured non-fatal advisories, including authoring issues and unread staff feedback responses. Each advisory.summary is also appended to the text content for the LLM path."
},
"finishedAt": {
"type": [
"string",
"null"
]
},
"lockDigest": {
"type": "string"
},
"sourceEtag": {
"type": "string",
"description": "ETag identifying the immutable input accepted for this build."
},
"inputDigest": {
"type": "string"
},
"queueDeadline": {
"type": "string"
},
"activeRevision": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0
},
"desiredRevision": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0
},
"retryAfterSeconds": {
"type": "integer",
"maximum": 9007199254740991,
"description": "Wait at least this many seconds before polling again.",
"exclusiveMinimum": 0
},
"inspectionExpiresAt": {
"type": [
"string",
"null"
]
},
"idempotencyExpiresAt": {
"type": [
"string",
"null"
]
},
"resolvedDependencies": {
"type": "object",
"required": [
"server",
"client"
],
"properties": {
"client": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"type": "string"
}
},
"server": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"type": "string"
}
}
},
"additionalProperties": false
}
},
"additionalProperties": false
}
Original tool metadata
{
"name": "create_app",
"description": "Create a new interactive web app and render it inline. Use this to build a new app from a request. Existing-contract apps return after publication. Eligible authors can select the ESM manifest schema with an idempotency_key to accept a background build; that result contains a buildId to poll with get_app_build, and has no app URL before publication. 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. Read the public docs without a browser using read_docs({ path: \"build-mcp.md\" }); read_docs({}) lists available pages. 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.",
"inputSchema": {
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"module",
"description"
],
"properties": {
"ui": {
"type": "string",
"description": "Under the explicit ESM contract, this is an ES module: import declared client packages and import { api, onStateChange } from \"charming:ui/[email protected]\" to call this app and subscribe to its state changes. Under the existing contract, follow the classic JavaScript rules below. 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. To keep the view current, register `window.charming.onStateChange(() => refresh())`, where `refresh` refetches your read operation and updates the DOM surgically rather than wiping #app. It fires after every write from any caller (this tab, another browser, an agent, a Routine, a webhook) and after a reconnect, so never poll with setInterval or a setTimeout loop. See the charming:app-guide prompt for a canonical example."
},
"module": {
"type": "string",
"description": "Eligible ESM authors may instead select `$schema: \"https://charm.ing/schema/app-manifest/2026-09-05.json\"`, declare target-specific `dependencies.server` and `dependencies.client`, and use ordinary package imports plus exact versioned platform imports. For ESM persistence, use `import { kv } from 'charming:storage/[email protected]'` and call `kv.get`/`kv.put` inside route handlers; omit `capabilities`. ESM creation takes its name from `manifest.meta.name`; omit `display_name`. For the existing contract, 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 state in the existing contract, `capabilities.imports` must include \"charming:storage/[email protected]\" (without it `env.storage` is undefined and every read/write throws `storage capability not granted`). A route handler in `export const routes = [...]` receives `(input, { env, ctx, request })` and returns exactly the value declared by `outputSchema`; for an array schema, use `handler: async (_input, { env }) => (await env.storage.get(\"key\")) ?? []`. Charming creates the transport envelope. Do not add a `{ ok, value }` or `{ value }` envelope unless those fields belong to `outputSchema` itself. A named context reads storage as `context.env.storage`. The optional unmatched-request fallback has the separate signature `export default { fetch(request, env, ctx) { ... } }`, where the second argument is the environment itself; it adds no discoverable route metadata. `env.user` is always present, not gated by any import: the caller's public identity (`{ id, handle?, name?, image? }`) or `null`. It lives only on `env` — read `env.user` (or `context.env.user`); there is no `ctx.user`. Full reference: call read_docs with path `llms-full.txt` (https://charm.ing/docs/llms-full.txt). For the existing contract, 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 a chat host 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/[email protected]\" capability 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. The following capability declarations apply only to the existing contract. Camera, microphone, geolocation, screen capture (getDisplayMedia), clipboard reading, and Web MIDI need no import and no claim: Charming finds these calls in `ui` at publish and, on the web, asks each viewer before the app gets the device, so handle NotAllowedError. Other sensitive browser capabilities must declare their import in `capabilities.imports` — \"charming:browser/[email protected]\" (device orientation/motion: DeviceOrientationEvent/DeviceMotionEvent + iOS requestPermission) or \"charming:browser/[email protected]\" (ambient light, new AmbientLightSensor), both granted only after the app is claimed, or \"charming:browser/[email protected]\" (native client storage — localStorage/sessionStorage/IndexedDB — web-only). To call external HTTPS APIs from backend code, declare \"charming:network/[email protected]\" 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/[email protected]\" to get `env.fetch` (claimed apps only) — a sealed outbound fetch that substitutes `{{secret:NAME}}` references in request HEADER values or query-parameter VALUES host-side (never a parameter name, the host, path, fragment, or body), so the key never enters app source or the sandbox; write the placeholder literally in the URL string — `URLSearchParams.set(...)` or `encodeURIComponent(...)` percent-encodes it first and it will NOT resolve; the app OWNER opens App settings, then Secrets, at `/<owner-handle>/~/apps/<app-name>/settings/secrets`, while `/app/<id>/secrets` remains the machine HTTP API. The agent only references the NAME. Never embed API keys in source. To call a public API or stream from browser code with fetch, EventSource, or WebSocket, list each exact https:// or wss:// origin in `manifest.permissions.browser[\"connect-src\"]` (web page only; chat embeds block it). To render remote images, list each exact https origin in `manifest.permissions.browser[\"img-src\"]`. To make an image render in ANY host — standalone AND embedded inline, where the host's 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 and in a host that injects no such CSP, but not in every embed; prefer `images.load(...)` whenever the app may be embedded. Both enforce the declared origins; neither bypasses them."
},
"styles": {
"type": "string",
"description": "Optional CSS for the rendered app"
},
"team_id": {
"type": "string",
"description": "Optional destination team id. Only a team owner or admin can create an App there. Omit it to create a personal App."
},
"description": {
"type": "string",
"maxLength": 500,
"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."
},
"display_name": {
"type": "string",
"description": "Optional display name override"
},
"idempotency_key": {
"type": "string",
"description": "Required for ESM builds: 8–128 visible ASCII characters. Retry the exact request with the same key to recover its build; use a new key for changed source."
},
"migrate_contract": {
"type": "boolean",
"description": "Set true to migrate an existing legacy manifest.id to ESM, together with expected_revision. Source submissions do not roll back contracts. History can explicitly restore a retained validated existing-contract revision."
},
"expected_revision": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991,
"description": "Required when ESM source reuses an existing manifest.id. Pass the desired revision from get_app_source."
}
}
},
"outputSchema": {
"type": "object",
"anyOf": [
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"ok",
"id",
"url",
"shareUrl",
"revision",
"icon"
],
"properties": {
"id": {
"type": "string",
"description": "UUID of the created or updated app."
},
"ok": {
"type": "boolean",
"const": true,
"description": "Indicates success. Errors arrive as content with isError:true."
},
"url": {
"type": "string",
"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."
},
"icon": {
"type": "object",
"required": [
"emoji",
"bg"
],
"properties": {
"bg": {
"type": "string",
"description": "The icon background as a hex color (e.g. \"#1d8a4e\")."
},
"emoji": {
"type": "string",
"description": "The single emoji rendered on the icon."
}
},
"description": "The effective home-screen icon stored for the app. Canonical source declares it at `manifest.meta.icon`.",
"additionalProperties": false
},
"appName": {
"type": [
"string",
"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)."
},
"revision": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0,
"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."
},
"shareUrl": {
"type": "string",
"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."
},
"warnings": {
"type": "array",
"items": {
"type": "string"
},
"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."
},
"advisories": {
"type": "array",
"items": {
"type": "object",
"required": [
"kind",
"summary"
],
"properties": {
"data": {
"type": "object",
"description": "Kind-specific structured payload. Shape varies per advisory kind.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"kind": {
"type": "string",
"description": "Stable advisory identifier (e.g. \"legacy-bridge\")."
},
"doc_url": {
"type": "string",
"description": "Optional docs pointer for this advisory kind."
},
"summary": {
"type": "string",
"description": "Agent-facing summary. Self-sufficient; no extra context required."
},
"severity": {
"enum": [
"info",
"warn"
],
"type": "string",
"description": "Severity; omitted advisories are treated as 'info'."
},
"userSummary": {
"type": "string",
"description": "End-user-facing summary. Set when the advisory should render in-app."
}
},
"additionalProperties": false
},
"description": "Structured non-fatal advisories, including authoring issues and unread staff feedback responses. Each advisory.summary is also appended to the text content for the LLM path."
}
},
"additionalProperties": false
},
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"ok",
"buildId",
"intent",
"state",
"sourceEtag",
"statusUrl",
"attempts",
"acceptedAt",
"updatedAt",
"finishedAt",
"queueDeadline",
"deadline",
"inspectionExpiresAt",
"idempotencyExpiresAt",
"elapsedMs",
"lockState",
"inputDigest"
],
"properties": {
"ok": {
"type": "boolean",
"const": true
},
"url": {
"type": "string",
"description": "App URL. Present only after successful publication."
},
"appId": {
"type": "string",
"description": "Existing target app ID, or the created app ID after publication."
},
"error": {
"type": "object",
"required": [
"kind",
"message",
"retryable"
],
"properties": {
"kind": {
"type": "string"
},
"line": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991
},
"column": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991
},
"target": {
"type": "string"
},
"details": {
"anyOf": [
{
"type": "object",
"required": [
"execution",
"operationId"
],
"properties": {
"execution": {
"enum": [
"not_started",
"may_have_run"
],
"type": "string"
},
"operationId": {
"type": "string",
"minLength": 1
},
"reservationId": {
"type": "string",
"minLength": 1
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"execution"
],
"properties": {
"execution": {
"type": "string",
"const": "may_have_run"
}
},
"additionalProperties": false
}
]
},
"message": {
"type": "string"
},
"retryable": {
"type": "boolean"
},
"specifier": {
"type": "string"
}
},
"additionalProperties": false
},
"state": {
"enum": [
"queued",
"resolving",
"building",
"validating",
"published",
"failed",
"superseded",
"canceled",
"expired"
],
"type": "string"
},
"intent": {
"type": "string",
"description": "The accepted operation: create, update, migrate, restore, or copy."
},
"source": {
"type": "object",
"required": [
"module",
"ui",
"styles",
"description"
],
"properties": {
"ui": {
"type": [
"string",
"null"
]
},
"module": {
"type": "string"
},
"styles": {
"type": [
"string",
"null"
]
},
"description": {
"type": [
"string",
"null"
]
}
},
"description": "Exact accepted source, returned only when include_source is true.",
"additionalProperties": false
},
"buildId": {
"type": "string",
"description": "Durable build ID. Use get_app_build to inspect progress or source."
},
"attempts": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0
},
"deadline": {
"type": "string"
},
"revision": {
"type": "integer",
"maximum": 9007199254740991,
"description": "Published app source revision.",
"exclusiveMinimum": 0
},
"elapsedMs": {
"type": "number",
"minimum": 0
},
"lockState": {
"enum": [
"locked",
"unresolved"
],
"type": "string"
},
"statusUrl": {
"type": "string",
"description": "Authenticated HTTP status URL for this build."
},
"updatedAt": {
"type": "string"
},
"acceptedAt": {
"type": "string"
},
"finishedAt": {
"type": [
"string",
"null"
]
},
"lockDigest": {
"type": "string"
},
"sourceEtag": {
"type": "string",
"description": "ETag identifying the immutable input accepted for this build."
},
"inputDigest": {
"type": "string"
},
"queueDeadline": {
"type": "string"
},
"activeRevision": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0
},
"desiredRevision": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0
},
"retryAfterSeconds": {
"type": "integer",
"maximum": 9007199254740991,
"description": "Wait at least this many seconds before polling again.",
"exclusiveMinimum": 0
},
"inspectionExpiresAt": {
"type": [
"string",
"null"
]
},
"idempotencyExpiresAt": {
"type": [
"string",
"null"
]
},
"resolvedDependencies": {
"type": "object",
"required": [
"server",
"client"
],
"properties": {
"client": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"type": "string"
}
},
"server": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"type": "string"
}
}
},
"additionalProperties": false
}
},
"additionalProperties": false
}
],
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"ok"
],
"properties": {
"id": {
"type": "string",
"description": "UUID of the created or updated app."
},
"ok": {
"type": "boolean",
"const": true
},
"url": {
"type": "string",
"description": "App URL. Present only after successful publication."
},
"icon": {
"type": "object",
"required": [
"emoji",
"bg"
],
"properties": {
"bg": {
"type": "string",
"description": "The icon background as a hex color (e.g. \"#1d8a4e\")."
},
"emoji": {
"type": "string",
"description": "The single emoji rendered on the icon."
}
},
"description": "The effective home-screen icon stored for the app. Canonical source declares it at `manifest.meta.icon`.",
"additionalProperties": false
},
"appId": {
"type": "string",
"description": "Existing target app ID, or the created app ID after publication."
},
"error": {
"type": "object",
"required": [
"kind",
"message",
"retryable"
],
"properties": {
"kind": {
"type": "string"
},
"line": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991
},
"column": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991
},
"target": {
"type": "string"
},
"details": {
"anyOf": [
{
"type": "object",
"required": [
"execution",
"operationId"
],
"properties": {
"execution": {
"enum": [
"not_started",
"may_have_run"
],
"type": "string"
},
"operationId": {
"type": "string",
"minLength": 1
},
"reservationId": {
"type": "string",
"minLength": 1
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"execution"
],
"properties": {
"execution": {
"type": "string",
"const": "may_have_run"
}
},
"additionalProperties": false
}
]
},
"message": {
"type": "string"
},
"retryable": {
"type": "boolean"
},
"specifier": {
"type": "string"
}
},
"additionalProperties": false
},
"state": {
"enum": [
"queued",
"resolving",
"building",
"validating",
"published",
"failed",
"superseded",
"canceled",
"expired"
],
"type": "string"
},
"intent": {
"type": "string",
"description": "The accepted operation: create, update, migrate, restore, or copy."
},
"source": {
"type": "object",
"required": [
"module",
"ui",
"styles",
"description"
],
"properties": {
"ui": {
"type": [
"string",
"null"
]
},
"module": {
"type": "string"
},
"styles": {
"type": [
"string",
"null"
]
},
"description": {
"type": [
"string",
"null"
]
}
},
"description": "Exact accepted source, returned only when include_source is true.",
"additionalProperties": false
},
"appName": {
"type": [
"string",
"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)."
},
"buildId": {
"type": "string",
"description": "Durable build ID. Use get_app_build to inspect progress or source."
},
"attempts": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0
},
"deadline": {
"type": "string"
},
"revision": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0,
"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."
},
"shareUrl": {
"type": "string",
"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."
},
"warnings": {
"type": "array",
"items": {
"type": "string"
},
"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."
},
"elapsedMs": {
"type": "number",
"minimum": 0
},
"lockState": {
"enum": [
"locked",
"unresolved"
],
"type": "string"
},
"statusUrl": {
"type": "string",
"description": "Authenticated HTTP status URL for this build."
},
"updatedAt": {
"type": "string"
},
"acceptedAt": {
"type": "string"
},
"advisories": {
"type": "array",
"items": {
"type": "object",
"required": [
"kind",
"summary"
],
"properties": {
"data": {
"type": "object",
"description": "Kind-specific structured payload. Shape varies per advisory kind.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"kind": {
"type": "string",
"description": "Stable advisory identifier (e.g. \"legacy-bridge\")."
},
"doc_url": {
"type": "string",
"description": "Optional docs pointer for this advisory kind."
},
"summary": {
"type": "string",
"description": "Agent-facing summary. Self-sufficient; no extra context required."
},
"severity": {
"enum": [
"info",
"warn"
],
"type": "string",
"description": "Severity; omitted advisories are treated as 'info'."
},
"userSummary": {
"type": "string",
"description": "End-user-facing summary. Set when the advisory should render in-app."
}
},
"additionalProperties": false
},
"description": "Structured non-fatal advisories, including authoring issues and unread staff feedback responses. Each advisory.summary is also appended to the text content for the LLM path."
},
"finishedAt": {
"type": [
"string",
"null"
]
},
"lockDigest": {
"type": "string"
},
"sourceEtag": {
"type": "string",
"description": "ETag identifying the immutable input accepted for this build."
},
"inputDigest": {
"type": "string"
},
"queueDeadline": {
"type": "string"
},
"activeRevision": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0
},
"desiredRevision": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0
},
"retryAfterSeconds": {
"type": "integer",
"maximum": 9007199254740991,
"description": "Wait at least this many seconds before polling again.",
"exclusiveMinimum": 0
},
"inspectionExpiresAt": {
"type": [
"string",
"null"
]
},
"idempotencyExpiresAt": {
"type": [
"string",
"null"
]
},
"resolvedDependencies": {
"type": "object",
"required": [
"server",
"client"
],
"properties": {
"client": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"type": "string"
}
},
"server": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"type": "string"
}
}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
"annotations": {
"title": "Create App",
"readOnlyHint": false,
"openWorldHint": true,
"destructiveHint": true
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description92/100
Use this to update an existing Charming app identified by UUID. Existing-contract saves publish synchronously. ESM saves require idempotency_key and expected_revision, return a durable buildId promptly, and keep the last active app running while the build completes. Poll with get_app_build. Two mutually exclusive input shapes: (A) full-source — pass module/ui/styles to replace those buckets, following the same authoring rules as create_app; (B) edits[] — exact-string find/replace operations plus expected_revision from your last source read. If the app's behavior changed materially (new operation, new data tracked, new use-case), also pass an updated `description` so list_apps stays accurate; backfill one if an older app has none. For synchronous results, give the user `shareUrl` to open or share the app; the `url` field is machine-only and embeds a write-capable access token.
Describe the returned information so the agent knows whether it will answer the request.
Inputs92/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
app_id
The app ID (UUID) to update
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
idempotency_key
Required for ESM saves. Retry the exact request with the same key; use a new key and current expected_revision for a new save.
Clear enough to supply this argument.
module
Full-source path only. Optional replacement ES module source. Same selected contract as create_app. For the existing contract, declare capabilities.imports including "charming:storage/[email protected]" for persistence. For ESM, keep the exact ESM schema and import { kv } from "charming:storage/[email protected]"; omit capabilities. A route handler in `export const routes = [...]` receives `(input, { env, ctx, request })` and returns exactly the value declared by `outputSchema`; for an array schema, use `handler: async (_input, { env }) => (await env.storage.get("key")) ?? []`. Charming creates the transport envelope. Do not add a `{ ok, value }` or `{ value }` envelope unless those fields belong to `outputSchema` itself. A named context reads storage as `context.env.storage`. The optional unmatched-request fallback has the separate signature `export default { fetch(request, env, ctx) { ... } }`, where the second argument is the environment itself; Charming supplies a generic 404 handler when it is absent. `env.user` is always present, not gated by any import: the caller's public identity (`{ id, handle?, name?, image? }`) or `null`. It lives only on `env` — read `env.user` (or `context.env.user`); there is no `ctx.user`. Full reference: call read_docs with path `llms-full.txt` (https://charm.ing/docs/llms-full.txt). Keep persisted state in backend storage (env.storage for the existing contract, imported kv for ESM); do not move it into localStorage/sessionStorage/IndexedDB, which are empty inside a chat host and lose the data. env.storage stores JSON-compatible values directly; do not JSON.stringify before put or JSON.parse after get. Mutually exclusive with `edits`. Migrating a legacy app to the existing dated contract requires the complete canonical manifest with the exact dated `$schema: "https://charm.ing/schema/app-manifest/2026-07-31.json"` plus `migrate_contract: true`; on this full-source path only, an omitted `$schema` is inserted automatically, but a wrong one still fails. ESM migration instead selects `https://charm.ing/schema/app-manifest/2026-09-05.json` through full-source or edits and requires `migrate_contract: true`, `expected_revision`, and `idempotency_key`.
Clear enough to supply this argument.
ui
Full-source path only. Optional replacement frontend JavaScript. Follow the create_app rules for the selected contract. Existing-contract UI must (a) populate #app innerHTML BEFORE attaching event listeners, and (b) call operations as `window.charming.api('<manifest-id>').<opName>(input)` (operation name = method name, strips the { ok, value } envelope — you receive the value only, never ok) — NOT `api.operation(name, params)` and NOT raw fetch(). The signed-in caller is exposed synchronously as `window.charming.user` (`{ id, handle?, name?, image? }`, or `null` for an anonymous visitor) — read `user.name` to greet, attribute, or personalize, instead of asking the user to type their name. PUBLIC fields only (never email); it is a convenience signal, not enforcement. The outermost container MUST fill the viewport — use `<main class="min-h-screen">` (or a grid/flex layout that spans width) as the root shell. Do NOT wrap the root in `max-w-md`, `max-w-2xl`, or `container mx-auto`: those cap the entire app to a narrow central column with wide empty margins on 2K+ monitors. Cap the reading measure on an INNER wrapper only for text-heavy views, e.g. `<main class="min-h-screen"><div class="mx-auto max-w-2xl">…</div></main>`. Mutually exclusive with `edits`.
Clear enough to supply this argument.
styles
Full-source path only. Optional replacement CSS. Mutually exclusive with `edits`.
Explain the meaning of this value, not just its name.
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.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
edits › items › bucket
Which source bucket the edit applies to.
Clear enough to supply this argument.
edits › items › old_string
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.
Clear enough to supply this argument.
edits › items › new_string
Replacement string. Empty string deletes the matched region.
Clear enough to supply this argument.
edits › items › replace_all
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.
Clear enough to supply this argument.
expected_revision
Revision precondition. Required for every ESM save and for existing-contract edits[]. Pass revision from your last get_app_source response. A new request against a stale desired revision fails with revision_mismatch; an identical idempotent retry returns its original build.
Clear enough to supply this argument.
expected_version
Deprecated compatibility input. Use `expected_revision`; while legacy hosts remain active, expected_version maps to the same revision precondition.
Clear enough to supply this argument.
migrate_contract
Set true only when replacing a legacy app with a complete valid dated manifest and routes contract. Source submissions do not roll back contracts. History can explicitly restore a retained validated existing-contract revision.
Clear enough to supply this argument.
Annotations75/100
Declare idempotentHint based on the tool’s actual behavior.
Align the output schema with the tool’s documented result.
View output schema
{
"type": "object",
"anyOf": [
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"ok",
"id",
"url",
"shareUrl",
"revision",
"icon"
],
"properties": {
"id": {
"type": "string",
"description": "UUID of the created or updated app."
},
"ok": {
"type": "boolean",
"const": true,
"description": "Indicates success. Errors arrive as content with isError:true."
},
"url": {
"type": "string",
"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."
},
"icon": {
"type": "object",
"required": [
"emoji",
"bg"
],
"properties": {
"bg": {
"type": "string",
"description": "The icon background as a hex color (e.g. \"#1d8a4e\")."
},
"emoji": {
"type": "string",
"description": "The single emoji rendered on the icon."
}
},
"description": "The effective home-screen icon stored for the app. Canonical source declares it at `manifest.meta.icon`.",
"additionalProperties": false
},
"appName": {
"type": [
"string",
"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)."
},
"revision": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0,
"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."
},
"shareUrl": {
"type": "string",
"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."
},
"warnings": {
"type": "array",
"items": {
"type": "string"
},
"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."
},
"advisories": {
"type": "array",
"items": {
"type": "object",
"required": [
"kind",
"summary"
],
"properties": {
"data": {
"type": "object",
"description": "Kind-specific structured payload. Shape varies per advisory kind.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"kind": {
"type": "string",
"description": "Stable advisory identifier (e.g. \"legacy-bridge\")."
},
"doc_url": {
"type": "string",
"description": "Optional docs pointer for this advisory kind."
},
"summary": {
"type": "string",
"description": "Agent-facing summary. Self-sufficient; no extra context required."
},
"severity": {
"enum": [
"info",
"warn"
],
"type": "string",
"description": "Severity; omitted advisories are treated as 'info'."
},
"userSummary": {
"type": "string",
"description": "End-user-facing summary. Set when the advisory should render in-app."
}
},
"additionalProperties": false
},
"description": "Structured non-fatal advisories, including authoring issues and unread staff feedback responses. Each advisory.summary is also appended to the text content for the LLM path."
}
},
"additionalProperties": false
},
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"ok",
"buildId",
"intent",
"state",
"sourceEtag",
"statusUrl",
"attempts",
"acceptedAt",
"updatedAt",
"finishedAt",
"queueDeadline",
"deadline",
"inspectionExpiresAt",
"idempotencyExpiresAt",
"elapsedMs",
"lockState",
"inputDigest"
],
"properties": {
"ok": {
"type": "boolean",
"const": true
},
"url": {
"type": "string",
"description": "App URL. Present only after successful publication."
},
"appId": {
"type": "string",
"description": "Existing target app ID, or the created app ID after publication."
},
"error": {
"type": "object",
"required": [
"kind",
"message",
"retryable"
],
"properties": {
"kind": {
"type": "string"
},
"line": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991
},
"column": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991
},
"target": {
"type": "string"
},
"details": {
"anyOf": [
{
"type": "object",
"required": [
"execution",
"operationId"
],
"properties": {
"execution": {
"enum": [
"not_started",
"may_have_run"
],
"type": "string"
},
"operationId": {
"type": "string",
"minLength": 1
},
"reservationId": {
"type": "string",
"minLength": 1
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"execution"
],
"properties": {
"execution": {
"type": "string",
"const": "may_have_run"
}
},
"additionalProperties": false
}
]
},
"message": {
"type": "string"
},
"retryable": {
"type": "boolean"
},
"specifier": {
"type": "string"
}
},
"additionalProperties": false
},
"state": {
"enum": [
"queued",
"resolving",
"building",
"validating",
"published",
"failed",
"superseded",
"canceled",
"expired"
],
"type": "string"
},
"intent": {
"type": "string",
"description": "The accepted operation: create, update, migrate, restore, or copy."
},
"source": {
"type": "object",
"required": [
"module",
"ui",
"styles",
"description"
],
"properties": {
"ui": {
"type": [
"string",
"null"
]
},
"module": {
"type": "string"
},
"styles": {
"type": [
"string",
"null"
]
},
"description": {
"type": [
"string",
"null"
]
}
},
"description": "Exact accepted source, returned only when include_source is true.",
"additionalProperties": false
},
"buildId": {
"type": "string",
"description": "Durable build ID. Use get_app_build to inspect progress or source."
},
"attempts": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0
},
"deadline": {
"type": "string"
},
"revision": {
"type": "integer",
"maximum": 9007199254740991,
"description": "Published app source revision.",
"exclusiveMinimum": 0
},
"elapsedMs": {
"type": "number",
"minimum": 0
},
"lockState": {
"enum": [
"locked",
"unresolved"
],
"type": "string"
},
"statusUrl": {
"type": "string",
"description": "Authenticated HTTP status URL for this build."
},
"updatedAt": {
"type": "string"
},
"acceptedAt": {
"type": "string"
},
"finishedAt": {
"type": [
"string",
"null"
]
},
"lockDigest": {
"type": "string"
},
"sourceEtag": {
"type": "string",
"description": "ETag identifying the immutable input accepted for this build."
},
"inputDigest": {
"type": "string"
},
"queueDeadline": {
"type": "string"
},
"activeRevision": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0
},
"desiredRevision": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0
},
"retryAfterSeconds": {
"type": "integer",
"maximum": 9007199254740991,
"description": "Wait at least this many seconds before polling again.",
"exclusiveMinimum": 0
},
"inspectionExpiresAt": {
"type": [
"string",
"null"
]
},
"idempotencyExpiresAt": {
"type": [
"string",
"null"
]
},
"resolvedDependencies": {
"type": "object",
"required": [
"server",
"client"
],
"properties": {
"client": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"type": "string"
}
},
"server": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"type": "string"
}
}
},
"additionalProperties": false
}
},
"additionalProperties": false
}
],
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"ok"
],
"properties": {
"id": {
"type": "string",
"description": "UUID of the created or updated app."
},
"ok": {
"type": "boolean",
"const": true
},
"url": {
"type": "string",
"description": "App URL. Present only after successful publication."
},
"icon": {
"type": "object",
"required": [
"emoji",
"bg"
],
"properties": {
"bg": {
"type": "string",
"description": "The icon background as a hex color (e.g. \"#1d8a4e\")."
},
"emoji": {
"type": "string",
"description": "The single emoji rendered on the icon."
}
},
"description": "The effective home-screen icon stored for the app. Canonical source declares it at `manifest.meta.icon`.",
"additionalProperties": false
},
"appId": {
"type": "string",
"description": "Existing target app ID, or the created app ID after publication."
},
"error": {
"type": "object",
"required": [
"kind",
"message",
"retryable"
],
"properties": {
"kind": {
"type": "string"
},
"line": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991
},
"column": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991
},
"target": {
"type": "string"
},
"details": {
"anyOf": [
{
"type": "object",
"required": [
"execution",
"operationId"
],
"properties": {
"execution": {
"enum": [
"not_started",
"may_have_run"
],
"type": "string"
},
"operationId": {
"type": "string",
"minLength": 1
},
"reservationId": {
"type": "string",
"minLength": 1
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"execution"
],
"properties": {
"execution": {
"type": "string",
"const": "may_have_run"
}
},
"additionalProperties": false
}
]
},
"message": {
"type": "string"
},
"retryable": {
"type": "boolean"
},
"specifier": {
"type": "string"
}
},
"additionalProperties": false
},
"state": {
"enum": [
"queued",
"resolving",
"building",
"validating",
"published",
"failed",
"superseded",
"canceled",
"expired"
],
"type": "string"
},
"intent": {
"type": "string",
"description": "The accepted operation: create, update, migrate, restore, or copy."
},
"source": {
"type": "object",
"required": [
"module",
"ui",
"styles",
"description"
],
"properties": {
"ui": {
"type": [
"string",
"null"
]
},
"module": {
"type": "string"
},
"styles": {
"type": [
"string",
"null"
]
},
"description": {
"type": [
"string",
"null"
]
}
},
"description": "Exact accepted source, returned only when include_source is true.",
"additionalProperties": false
},
"appName": {
"type": [
"string",
"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)."
},
"buildId": {
"type": "string",
"description": "Durable build ID. Use get_app_build to inspect progress or source."
},
"attempts": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0
},
"deadline": {
"type": "string"
},
"revision": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0,
"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."
},
"shareUrl": {
"type": "string",
"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."
},
"warnings": {
"type": "array",
"items": {
"type": "string"
},
"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."
},
"elapsedMs": {
"type": "number",
"minimum": 0
},
"lockState": {
"enum": [
"locked",
"unresolved"
],
"type": "string"
},
"statusUrl": {
"type": "string",
"description": "Authenticated HTTP status URL for this build."
},
"updatedAt": {
"type": "string"
},
"acceptedAt": {
"type": "string"
},
"advisories": {
"type": "array",
"items": {
"type": "object",
"required": [
"kind",
"summary"
],
"properties": {
"data": {
"type": "object",
"description": "Kind-specific structured payload. Shape varies per advisory kind.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"kind": {
"type": "string",
"description": "Stable advisory identifier (e.g. \"legacy-bridge\")."
},
"doc_url": {
"type": "string",
"description": "Optional docs pointer for this advisory kind."
},
"summary": {
"type": "string",
"description": "Agent-facing summary. Self-sufficient; no extra context required."
},
"severity": {
"enum": [
"info",
"warn"
],
"type": "string",
"description": "Severity; omitted advisories are treated as 'info'."
},
"userSummary": {
"type": "string",
"description": "End-user-facing summary. Set when the advisory should render in-app."
}
},
"additionalProperties": false
},
"description": "Structured non-fatal advisories, including authoring issues and unread staff feedback responses. Each advisory.summary is also appended to the text content for the LLM path."
},
"finishedAt": {
"type": [
"string",
"null"
]
},
"lockDigest": {
"type": "string"
},
"sourceEtag": {
"type": "string",
"description": "ETag identifying the immutable input accepted for this build."
},
"inputDigest": {
"type": "string"
},
"queueDeadline": {
"type": "string"
},
"activeRevision": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0
},
"desiredRevision": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0
},
"retryAfterSeconds": {
"type": "integer",
"maximum": 9007199254740991,
"description": "Wait at least this many seconds before polling again.",
"exclusiveMinimum": 0
},
"inspectionExpiresAt": {
"type": [
"string",
"null"
]
},
"idempotencyExpiresAt": {
"type": [
"string",
"null"
]
},
"resolvedDependencies": {
"type": "object",
"required": [
"server",
"client"
],
"properties": {
"client": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"type": "string"
}
},
"server": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"type": "string"
}
}
},
"additionalProperties": false
}
},
"additionalProperties": false
}
Original tool metadata
{
"name": "update_app",
"description": "Use this to update an existing Charming app identified by UUID. Existing-contract saves publish synchronously. ESM saves require idempotency_key and expected_revision, return a durable buildId promptly, and keep the last active app running while the build completes. Poll with get_app_build. Two mutually exclusive input shapes: (A) full-source — pass module/ui/styles to replace those buckets, following the same authoring rules as create_app; (B) edits[] — exact-string find/replace operations plus expected_revision from your last source read. If the app's behavior changed materially (new operation, new data tracked, new use-case), also pass an updated `description` so list_apps stays accurate; backfill one if an older app has none. For synchronous results, give the user `shareUrl` to open or share the app; the `url` field is machine-only and embeds a write-capable access token.",
"inputSchema": {
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"app_id"
],
"properties": {
"ui": {
"type": "string",
"description": "Full-source path only. Optional replacement frontend JavaScript. Follow the create_app rules for the selected contract. Existing-contract UI must (a) populate #app innerHTML BEFORE attaching event listeners, and (b) call operations as `window.charming.api('<manifest-id>').<opName>(input)` (operation name = method name, strips the { ok, value } envelope — you receive the value only, never ok) — NOT `api.operation(name, params)` and NOT raw fetch(). The signed-in caller is exposed synchronously as `window.charming.user` (`{ id, handle?, name?, image? }`, or `null` for an anonymous visitor) — read `user.name` to greet, attribute, or personalize, instead of asking the user to type their name. PUBLIC fields only (never email); it is a convenience signal, not enforcement. The outermost container MUST fill the viewport — use `<main class=\"min-h-screen\">` (or a grid/flex layout that spans width) as the root shell. Do NOT wrap the root in `max-w-md`, `max-w-2xl`, or `container mx-auto`: those cap the entire app to a narrow central column with wide empty margins on 2K+ monitors. Cap the reading measure on an INNER wrapper only for text-heavy views, e.g. `<main class=\"min-h-screen\"><div class=\"mx-auto max-w-2xl\">…</div></main>`. Mutually exclusive with `edits`."
},
"edits": {
"type": "array",
"items": {
"type": "object",
"required": [
"bucket",
"old_string",
"new_string"
],
"properties": {
"bucket": {
"enum": [
"module",
"ui",
"styles"
],
"type": "string",
"description": "Which source bucket the edit applies to."
},
"new_string": {
"type": "string",
"description": "Replacement string. Empty string deletes the matched region."
},
"old_string": {
"type": "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."
},
"replace_all": {
"type": "boolean",
"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."
}
}
},
"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`)."
},
"app_id": {
"type": "string",
"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)$",
"description": "The app ID (UUID) to update"
},
"module": {
"type": "string",
"description": "Full-source path only. Optional replacement ES module source. Same selected contract as create_app. For the existing contract, declare capabilities.imports including \"charming:storage/[email protected]\" for persistence. For ESM, keep the exact ESM schema and import { kv } from \"charming:storage/[email protected]\"; omit capabilities. A route handler in `export const routes = [...]` receives `(input, { env, ctx, request })` and returns exactly the value declared by `outputSchema`; for an array schema, use `handler: async (_input, { env }) => (await env.storage.get(\"key\")) ?? []`. Charming creates the transport envelope. Do not add a `{ ok, value }` or `{ value }` envelope unless those fields belong to `outputSchema` itself. A named context reads storage as `context.env.storage`. The optional unmatched-request fallback has the separate signature `export default { fetch(request, env, ctx) { ... } }`, where the second argument is the environment itself; Charming supplies a generic 404 handler when it is absent. `env.user` is always present, not gated by any import: the caller's public identity (`{ id, handle?, name?, image? }`) or `null`. It lives only on `env` — read `env.user` (or `context.env.user`); there is no `ctx.user`. Full reference: call read_docs with path `llms-full.txt` (https://charm.ing/docs/llms-full.txt). Keep persisted state in backend storage (env.storage for the existing contract, imported kv for ESM); do not move it into localStorage/sessionStorage/IndexedDB, which are empty inside a chat host and lose the data. env.storage stores JSON-compatible values directly; do not JSON.stringify before put or JSON.parse after get. Mutually exclusive with `edits`. Migrating a legacy app to the existing dated contract requires the complete canonical manifest with the exact dated `$schema: \"https://charm.ing/schema/app-manifest/2026-07-31.json\"` plus `migrate_contract: true`; on this full-source path only, an omitted `$schema` is inserted automatically, but a wrong one still fails. ESM migration instead selects `https://charm.ing/schema/app-manifest/2026-09-05.json` through full-source or edits and requires `migrate_contract: true`, `expected_revision`, and `idempotency_key`."
},
"styles": {
"type": "string",
"description": "Full-source path only. Optional replacement CSS. Mutually exclusive with `edits`."
},
"description": {
"anyOf": [
{
"type": "string",
"maxLength": 500
},
{
"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."
},
"idempotency_key": {
"type": "string",
"description": "Required for ESM saves. Retry the exact request with the same key; use a new key and current expected_revision for a new save."
},
"expected_version": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991,
"description": "Deprecated compatibility input. Use `expected_revision`; while legacy hosts remain active, expected_version maps to the same revision precondition."
},
"migrate_contract": {
"type": "boolean",
"description": "Set true only when replacing a legacy app with a complete valid dated manifest and routes contract. Source submissions do not roll back contracts. History can explicitly restore a retained validated existing-contract revision."
},
"expected_revision": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991,
"description": "Revision precondition. Required for every ESM save and for existing-contract edits[]. Pass revision from your last get_app_source response. A new request against a stale desired revision fails with revision_mismatch; an identical idempotent retry returns its original build."
}
}
},
"outputSchema": {
"type": "object",
"anyOf": [
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"ok",
"id",
"url",
"shareUrl",
"revision",
"icon"
],
"properties": {
"id": {
"type": "string",
"description": "UUID of the created or updated app."
},
"ok": {
"type": "boolean",
"const": true,
"description": "Indicates success. Errors arrive as content with isError:true."
},
"url": {
"type": "string",
"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."
},
"icon": {
"type": "object",
"required": [
"emoji",
"bg"
],
"properties": {
"bg": {
"type": "string",
"description": "The icon background as a hex color (e.g. \"#1d8a4e\")."
},
"emoji": {
"type": "string",
"description": "The single emoji rendered on the icon."
}
},
"description": "The effective home-screen icon stored for the app. Canonical source declares it at `manifest.meta.icon`.",
"additionalProperties": false
},
"appName": {
"type": [
"string",
"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)."
},
"revision": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0,
"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."
},
"shareUrl": {
"type": "string",
"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."
},
"warnings": {
"type": "array",
"items": {
"type": "string"
},
"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."
},
"advisories": {
"type": "array",
"items": {
"type": "object",
"required": [
"kind",
"summary"
],
"properties": {
"data": {
"type": "object",
"description": "Kind-specific structured payload. Shape varies per advisory kind.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"kind": {
"type": "string",
"description": "Stable advisory identifier (e.g. \"legacy-bridge\")."
},
"doc_url": {
"type": "string",
"description": "Optional docs pointer for this advisory kind."
},
"summary": {
"type": "string",
"description": "Agent-facing summary. Self-sufficient; no extra context required."
},
"severity": {
"enum": [
"info",
"warn"
],
"type": "string",
"description": "Severity; omitted advisories are treated as 'info'."
},
"userSummary": {
"type": "string",
"description": "End-user-facing summary. Set when the advisory should render in-app."
}
},
"additionalProperties": false
},
"description": "Structured non-fatal advisories, including authoring issues and unread staff feedback responses. Each advisory.summary is also appended to the text content for the LLM path."
}
},
"additionalProperties": false
},
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"ok",
"buildId",
"intent",
"state",
"sourceEtag",
"statusUrl",
"attempts",
"acceptedAt",
"updatedAt",
"finishedAt",
"queueDeadline",
"deadline",
"inspectionExpiresAt",
"idempotencyExpiresAt",
"elapsedMs",
"lockState",
"inputDigest"
],
"properties": {
"ok": {
"type": "boolean",
"const": true
},
"url": {
"type": "string",
"description": "App URL. Present only after successful publication."
},
"appId": {
"type": "string",
"description": "Existing target app ID, or the created app ID after publication."
},
"error": {
"type": "object",
"required": [
"kind",
"message",
"retryable"
],
"properties": {
"kind": {
"type": "string"
},
"line": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991
},
"column": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991
},
"target": {
"type": "string"
},
"details": {
"anyOf": [
{
"type": "object",
"required": [
"execution",
"operationId"
],
"properties": {
"execution": {
"enum": [
"not_started",
"may_have_run"
],
"type": "string"
},
"operationId": {
"type": "string",
"minLength": 1
},
"reservationId": {
"type": "string",
"minLength": 1
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"execution"
],
"properties": {
"execution": {
"type": "string",
"const": "may_have_run"
}
},
"additionalProperties": false
}
]
},
"message": {
"type": "string"
},
"retryable": {
"type": "boolean"
},
"specifier": {
"type": "string"
}
},
"additionalProperties": false
},
"state": {
"enum": [
"queued",
"resolving",
"building",
"validating",
"published",
"failed",
"superseded",
"canceled",
"expired"
],
"type": "string"
},
"intent": {
"type": "string",
"description": "The accepted operation: create, update, migrate, restore, or copy."
},
"source": {
"type": "object",
"required": [
"module",
"ui",
"styles",
"description"
],
"properties": {
"ui": {
"type": [
"string",
"null"
]
},
"module": {
"type": "string"
},
"styles": {
"type": [
"string",
"null"
]
},
"description": {
"type": [
"string",
"null"
]
}
},
"description": "Exact accepted source, returned only when include_source is true.",
"additionalProperties": false
},
"buildId": {
"type": "string",
"description": "Durable build ID. Use get_app_build to inspect progress or source."
},
"attempts": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0
},
"deadline": {
"type": "string"
},
"revision": {
"type": "integer",
"maximum": 9007199254740991,
"description": "Published app source revision.",
"exclusiveMinimum": 0
},
"elapsedMs": {
"type": "number",
"minimum": 0
},
"lockState": {
"enum": [
"locked",
"unresolved"
],
"type": "string"
},
"statusUrl": {
"type": "string",
"description": "Authenticated HTTP status URL for this build."
},
"updatedAt": {
"type": "string"
},
"acceptedAt": {
"type": "string"
},
"finishedAt": {
"type": [
"string",
"null"
]
},
"lockDigest": {
"type": "string"
},
"sourceEtag": {
"type": "string",
"description": "ETag identifying the immutable input accepted for this build."
},
"inputDigest": {
"type": "string"
},
"queueDeadline": {
"type": "string"
},
"activeRevision": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0
},
"desiredRevision": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0
},
"retryAfterSeconds": {
"type": "integer",
"maximum": 9007199254740991,
"description": "Wait at least this many seconds before polling again.",
"exclusiveMinimum": 0
},
"inspectionExpiresAt": {
"type": [
"string",
"null"
]
},
"idempotencyExpiresAt": {
"type": [
"string",
"null"
]
},
"resolvedDependencies": {
"type": "object",
"required": [
"server",
"client"
],
"properties": {
"client": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"type": "string"
}
},
"server": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"type": "string"
}
}
},
"additionalProperties": false
}
},
"additionalProperties": false
}
],
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"ok"
],
"properties": {
"id": {
"type": "string",
"description": "UUID of the created or updated app."
},
"ok": {
"type": "boolean",
"const": true
},
"url": {
"type": "string",
"description": "App URL. Present only after successful publication."
},
"icon": {
"type": "object",
"required": [
"emoji",
"bg"
],
"properties": {
"bg": {
"type": "string",
"description": "The icon background as a hex color (e.g. \"#1d8a4e\")."
},
"emoji": {
"type": "string",
"description": "The single emoji rendered on the icon."
}
},
"description": "The effective home-screen icon stored for the app. Canonical source declares it at `manifest.meta.icon`.",
"additionalProperties": false
},
"appId": {
"type": "string",
"description": "Existing target app ID, or the created app ID after publication."
},
"error": {
"type": "object",
"required": [
"kind",
"message",
"retryable"
],
"properties": {
"kind": {
"type": "string"
},
"line": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991
},
"column": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991
},
"target": {
"type": "string"
},
"details": {
"anyOf": [
{
"type": "object",
"required": [
"execution",
"operationId"
],
"properties": {
"execution": {
"enum": [
"not_started",
"may_have_run"
],
"type": "string"
},
"operationId": {
"type": "string",
"minLength": 1
},
"reservationId": {
"type": "string",
"minLength": 1
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"execution"
],
"properties": {
"execution": {
"type": "string",
"const": "may_have_run"
}
},
"additionalProperties": false
}
]
},
"message": {
"type": "string"
},
"retryable": {
"type": "boolean"
},
"specifier": {
"type": "string"
}
},
"additionalProperties": false
},
"state": {
"enum": [
"queued",
"resolving",
"building",
"validating",
"published",
"failed",
"superseded",
"canceled",
"expired"
],
"type": "string"
},
"intent": {
"type": "string",
"description": "The accepted operation: create, update, migrate, restore, or copy."
},
"source": {
"type": "object",
"required": [
"module",
"ui",
"styles",
"description"
],
"properties": {
"ui": {
"type": [
"string",
"null"
]
},
"module": {
"type": "string"
},
"styles": {
"type": [
"string",
"null"
]
},
"description": {
"type": [
"string",
"null"
]
}
},
"description": "Exact accepted source, returned only when include_source is true.",
"additionalProperties": false
},
"appName": {
"type": [
"string",
"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)."
},
"buildId": {
"type": "string",
"description": "Durable build ID. Use get_app_build to inspect progress or source."
},
"attempts": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0
},
"deadline": {
"type": "string"
},
"revision": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0,
"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."
},
"shareUrl": {
"type": "string",
"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."
},
"warnings": {
"type": "array",
"items": {
"type": "string"
},
"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."
},
"elapsedMs": {
"type": "number",
"minimum": 0
},
"lockState": {
"enum": [
"locked",
"unresolved"
],
"type": "string"
},
"statusUrl": {
"type": "string",
"description": "Authenticated HTTP status URL for this build."
},
"updatedAt": {
"type": "string"
},
"acceptedAt": {
"type": "string"
},
"advisories": {
"type": "array",
"items": {
"type": "object",
"required": [
"kind",
"summary"
],
"properties": {
"data": {
"type": "object",
"description": "Kind-specific structured payload. Shape varies per advisory kind.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"kind": {
"type": "string",
"description": "Stable advisory identifier (e.g. \"legacy-bridge\")."
},
"doc_url": {
"type": "string",
"description": "Optional docs pointer for this advisory kind."
},
"summary": {
"type": "string",
"description": "Agent-facing summary. Self-sufficient; no extra context required."
},
"severity": {
"enum": [
"info",
"warn"
],
"type": "string",
"description": "Severity; omitted advisories are treated as 'info'."
},
"userSummary": {
"type": "string",
"description": "End-user-facing summary. Set when the advisory should render in-app."
}
},
"additionalProperties": false
},
"description": "Structured non-fatal advisories, including authoring issues and unread staff feedback responses. Each advisory.summary is also appended to the text content for the LLM path."
},
"finishedAt": {
"type": [
"string",
"null"
]
},
"lockDigest": {
"type": "string"
},
"sourceEtag": {
"type": "string",
"description": "ETag identifying the immutable input accepted for this build."
},
"inputDigest": {
"type": "string"
},
"queueDeadline": {
"type": "string"
},
"activeRevision": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0
},
"desiredRevision": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0
},
"retryAfterSeconds": {
"type": "integer",
"maximum": 9007199254740991,
"description": "Wait at least this many seconds before polling again.",
"exclusiveMinimum": 0
},
"inspectionExpiresAt": {
"type": [
"string",
"null"
]
},
"idempotencyExpiresAt": {
"type": [
"string",
"null"
]
},
"resolvedDependencies": {
"type": "object",
"required": [
"server",
"client"
],
"properties": {
"client": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"type": "string"
}
},
"server": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"type": "string"
}
}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
"annotations": {
"title": "Update App",
"readOnlyHint": false,
"openWorldHint": true,
"destructiveHint": true
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description99/100
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. For synchronous results, give the user `shareUrl` to open or share the app; the `url` field is machine-only and embeds a write-capable access token.
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
app_id
The app ID (UUID)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
Annotations75/100
Declare idempotentHint based on the tool’s actual behavior.
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"ok",
"id",
"url",
"shareUrl",
"displayName",
"description",
"manifestId",
"revision",
"appVersion",
"manifestVersion",
"api"
],
"properties": {
"id": {
"type": "string",
"description": "UUID of the opened app."
},
"ok": {
"type": "boolean",
"const": true,
"description": "Indicates success. Errors arrive as content with isError:true."
},
"api": {
"type": "object",
"required": [
"baseUrl",
"operations"
],
"properties": {
"baseUrl": {
"type": "string",
"description": "Origin + app prefix the operations live under."
},
"operations": {
"type": "array",
"items": {
"type": "object",
"required": [
"op",
"method",
"path",
"url",
"readOnly",
"destructive",
"idempotent",
"openWorld",
"public",
"tool",
"discoveredFrom"
],
"properties": {
"op": {
"type": "string",
"description": "Operation name. Matches the `op` field on the corresponding Route."
},
"url": {
"type": "string",
"description": "Full URL (origin + app prefix + path)."
},
"path": {
"type": "string",
"description": "Path under the app, e.g. `/api/increment`."
},
"tool": {
"enum": [
"query_app",
"mutate_app"
],
"type": "string",
"description": "MCP tool to invoke this op: `query_app` for read-only, `mutate_app` otherwise."
},
"input": {
"description": "JSON Schema for the field. Canonical source declares it as `route.inputSchema` or `route.outputSchema`."
},
"title": {
"type": "string",
"description": "Short author-supplied title for the operation."
},
"method": {
"enum": [
"GET",
"POST",
"PUT",
"PATCH",
"DELETE"
],
"type": "string",
"description": "HTTP method the route accepts."
},
"output": {
"description": "JSON Schema for the field. Canonical source declares it as `route.inputSchema` or `route.outputSchema`."
},
"public": {
"type": "boolean",
"description": "True when the route is visible to discovery. Default true."
},
"examples": {
"type": "array",
"items": {
"type": "object",
"properties": {
"input": {},
"output": {},
"description": {
"type": "string"
}
},
"additionalProperties": false
},
"description": "Author-supplied calling examples that flow into the query_app/mutate_app snippet."
},
"readOnly": {
"type": "boolean",
"description": "True when the route is a safe read (resolved from author intent + derivation)."
},
"openWorld": {
"type": "boolean",
"description": "True when the route may reach beyond the app, such as the network. Default true."
},
"idempotent": {
"type": "boolean",
"description": "True when repeating the call with the same input has no further effect."
},
"description": {
"type": "string",
"description": "Author-supplied prose description."
},
"destructive": {
"type": "boolean",
"description": "True when the route destroys data. Default false unless derived as such."
},
"discoveredFrom": {
"type": "array",
"items": {
"enum": [
"routes_export",
"manifest_export",
"module_route"
],
"type": "string"
},
"description": "Sources that contributed this operation (precedence order)."
},
"manifestExport": {
"type": "string",
"description": "Original capability-export string when discovered via `manifest_export`."
}
},
"additionalProperties": false
},
"description": "Discovered routes in author order."
}
},
"description": "Full API surface — same shape as `_meta.charming.api`, but model-facing.",
"additionalProperties": false
},
"url": {
"type": "string",
"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."
},
"$schema": {
"enum": [
"https://charm.ing/schema/app-manifest/2026-07-31.json",
"https://charm.ing/schema/app-manifest/2026-09-05.json"
],
"type": "string",
"description": "Exact dated canonical manifest schema URL. Omitted for legacy apps."
},
"appName": {
"type": [
"string",
"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)."
},
"revision": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0,
"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."
},
"shareUrl": {
"type": "string",
"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."
},
"advisories": {
"type": "array",
"items": {
"type": "object",
"required": [
"kind",
"summary"
],
"properties": {
"data": {
"type": "object",
"description": "Kind-specific structured payload. Shape varies per advisory kind.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"kind": {
"type": "string",
"description": "Stable advisory identifier (e.g. \"legacy-bridge\")."
},
"doc_url": {
"type": "string",
"description": "Optional docs pointer for this advisory kind."
},
"summary": {
"type": "string",
"description": "Agent-facing summary. Self-sufficient; no extra context required."
},
"severity": {
"enum": [
"info",
"warn"
],
"type": "string",
"description": "Severity; omitted advisories are treated as 'info'."
},
"userSummary": {
"type": "string",
"description": "End-user-facing summary. Set when the advisory should render in-app."
}
},
"additionalProperties": false
},
"description": "Structured non-fatal advisories, including authoring issues and unread staff feedback responses. Each advisory.summary is also appended to the text content for the LLM path."
},
"appVersion": {
"type": "string",
"description": "Canonical apps return decimal String(revision). Legacy apps return their stored effective SemVer."
},
"manifestId": {
"type": "string",
"description": "Stable manifest identifier declared by the app module. Distinct from `id` (DB primary key) — surface this when discussing the app with the user."
},
"description": {
"type": [
"string",
"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": {
"type": "string",
"description": "Human-readable name shown in app listings and headers."
},
"recentIssues": {
"type": "object",
"required": [
"sinceRevision",
"total",
"counts",
"examples"
],
"properties": {
"since": {
"type": "string",
"description": "Set when the plan's window hid older issues: the cutoff the counts start from (ISO-8601)."
},
"total": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991,
"description": "Failed calls counted for the current revision, read from the newest 1000 issue rows. One row can stand for a burst of refused calls, so this can exceed 1000. Use GET /app/:id/activity for the exact, paginated list."
},
"counts": {
"type": "object",
"required": [
"contractMisses",
"runtimeErrors",
"cspViolations",
"apiFailures",
"externalResourceFailures",
"appLoadErrors"
],
"properties": {
"apiFailures": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991,
"description": "Failed calls to the app API proxy, including calls refused at the app run ceiling, which are recorded one row per 10-second window."
},
"appLoadErrors": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991,
"description": "Caught manifest-resolve / app load failures the bridge swallowed before they became an unhandled rejection."
},
"cspViolations": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991,
"description": "Content-Security-Policy violations (e.g. an external image blocked by img-src)."
},
"runtimeErrors": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991,
"description": "Runtime JS errors and unhandled rejections reported by the rendered page."
},
"contractMisses": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991,
"description": "Publish-time UI/backend contract validation failures."
},
"externalResourceFailures": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991,
"description": "Failed external image requests, including failures caught by app code."
}
},
"description": "Issue counts per category.",
"additionalProperties": false
},
"examples": {
"type": "array",
"items": {
"type": "object",
"required": [
"ts",
"category",
"kind",
"summary"
],
"properties": {
"ts": {
"type": "string",
"description": "ISO-8601 timestamp of the event."
},
"kind": {
"type": "string",
"description": "Underlying durable event kind: `diag_report` (runtime errors and CSP violations, distinguished by category), `contract_validation` (contract misses), `api_proxy_result` (API failures), `app_load_error` (caught load failures), or `image_proxy_result` (external image failures)."
},
"summary": {
"type": "string",
"description": "Compact one-line description."
},
"category": {
"enum": [
"contractMisses",
"runtimeErrors",
"cspViolations",
"apiFailures",
"appLoadErrors",
"externalResourceFailures"
],
"type": "string",
"description": "Which `counts` bucket this example illustrates."
}
},
"additionalProperties": false
},
"description": "Most recent example per non-empty category, newest first."
},
"sinceRevision": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0,
"description": "The app revision the issues are attributed to (the current revision at call time). Legacy nulls normalize to 0."
}
},
"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.",
"additionalProperties": false
},
"manifestVersion": {
"type": [
"string",
"null"
],
"description": "Deprecated public contract marker. Canonical apps return null; legacy apps return their stored contract date."
}
},
"additionalProperties": false
}
Original tool metadata
{
"name": "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. For synchronous results, give the user `shareUrl` to open or share the app; the `url` field is machine-only and embeds a write-capable access token.",
"inputSchema": {
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"app_id"
],
"properties": {
"app_id": {
"type": "string",
"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)$",
"description": "The app ID (UUID)"
}
}
},
"outputSchema": {
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"ok",
"id",
"url",
"shareUrl",
"displayName",
"description",
"manifestId",
"revision",
"appVersion",
"manifestVersion",
"api"
],
"properties": {
"id": {
"type": "string",
"description": "UUID of the opened app."
},
"ok": {
"type": "boolean",
"const": true,
"description": "Indicates success. Errors arrive as content with isError:true."
},
"api": {
"type": "object",
"required": [
"baseUrl",
"operations"
],
"properties": {
"baseUrl": {
"type": "string",
"description": "Origin + app prefix the operations live under."
},
"operations": {
"type": "array",
"items": {
"type": "object",
"required": [
"op",
"method",
"path",
"url",
"readOnly",
"destructive",
"idempotent",
"openWorld",
"public",
"tool",
"discoveredFrom"
],
"properties": {
"op": {
"type": "string",
"description": "Operation name. Matches the `op` field on the corresponding Route."
},
"url": {
"type": "string",
"description": "Full URL (origin + app prefix + path)."
},
"path": {
"type": "string",
"description": "Path under the app, e.g. `/api/increment`."
},
"tool": {
"enum": [
"query_app",
"mutate_app"
],
"type": "string",
"description": "MCP tool to invoke this op: `query_app` for read-only, `mutate_app` otherwise."
},
"input": {
"description": "JSON Schema for the field. Canonical source declares it as `route.inputSchema` or `route.outputSchema`."
},
"title": {
"type": "string",
"description": "Short author-supplied title for the operation."
},
"method": {
"enum": [
"GET",
"POST",
"PUT",
"PATCH",
"DELETE"
],
"type": "string",
"description": "HTTP method the route accepts."
},
"output": {
"description": "JSON Schema for the field. Canonical source declares it as `route.inputSchema` or `route.outputSchema`."
},
"public": {
"type": "boolean",
"description": "True when the route is visible to discovery. Default true."
},
"examples": {
"type": "array",
"items": {
"type": "object",
"properties": {
"input": {},
"output": {},
"description": {
"type": "string"
}
},
"additionalProperties": false
},
"description": "Author-supplied calling examples that flow into the query_app/mutate_app snippet."
},
"readOnly": {
"type": "boolean",
"description": "True when the route is a safe read (resolved from author intent + derivation)."
},
"openWorld": {
"type": "boolean",
"description": "True when the route may reach beyond the app, such as the network. Default true."
},
"idempotent": {
"type": "boolean",
"description": "True when repeating the call with the same input has no further effect."
},
"description": {
"type": "string",
"description": "Author-supplied prose description."
},
"destructive": {
"type": "boolean",
"description": "True when the route destroys data. Default false unless derived as such."
},
"discoveredFrom": {
"type": "array",
"items": {
"enum": [
"routes_export",
"manifest_export",
"module_route"
],
"type": "string"
},
"description": "Sources that contributed this operation (precedence order)."
},
"manifestExport": {
"type": "string",
"description": "Original capability-export string when discovered via `manifest_export`."
}
},
"additionalProperties": false
},
"description": "Discovered routes in author order."
}
},
"description": "Full API surface — same shape as `_meta.charming.api`, but model-facing.",
"additionalProperties": false
},
"url": {
"type": "string",
"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."
},
"$schema": {
"enum": [
"https://charm.ing/schema/app-manifest/2026-07-31.json",
"https://charm.ing/schema/app-manifest/2026-09-05.json"
],
"type": "string",
"description": "Exact dated canonical manifest schema URL. Omitted for legacy apps."
},
"appName": {
"type": [
"string",
"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)."
},
"revision": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0,
"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."
},
"shareUrl": {
"type": "string",
"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."
},
"advisories": {
"type": "array",
"items": {
"type": "object",
"required": [
"kind",
"summary"
],
"properties": {
"data": {
"type": "object",
"description": "Kind-specific structured payload. Shape varies per advisory kind.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"kind": {
"type": "string",
"description": "Stable advisory identifier (e.g. \"legacy-bridge\")."
},
"doc_url": {
"type": "string",
"description": "Optional docs pointer for this advisory kind."
},
"summary": {
"type": "string",
"description": "Agent-facing summary. Self-sufficient; no extra context required."
},
"severity": {
"enum": [
"info",
"warn"
],
"type": "string",
"description": "Severity; omitted advisories are treated as 'info'."
},
"userSummary": {
"type": "string",
"description": "End-user-facing summary. Set when the advisory should render in-app."
}
},
"additionalProperties": false
},
"description": "Structured non-fatal advisories, including authoring issues and unread staff feedback responses. Each advisory.summary is also appended to the text content for the LLM path."
},
"appVersion": {
"type": "string",
"description": "Canonical apps return decimal String(revision). Legacy apps return their stored effective SemVer."
},
"manifestId": {
"type": "string",
"description": "Stable manifest identifier declared by the app module. Distinct from `id` (DB primary key) — surface this when discussing the app with the user."
},
"description": {
"type": [
"string",
"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": {
"type": "string",
"description": "Human-readable name shown in app listings and headers."
},
"recentIssues": {
"type": "object",
"required": [
"sinceRevision",
"total",
"counts",
"examples"
],
"properties": {
"since": {
"type": "string",
"description": "Set when the plan's window hid older issues: the cutoff the counts start from (ISO-8601)."
},
"total": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991,
"description": "Failed calls counted for the current revision, read from the newest 1000 issue rows. One row can stand for a burst of refused calls, so this can exceed 1000. Use GET /app/:id/activity for the exact, paginated list."
},
"counts": {
"type": "object",
"required": [
"contractMisses",
"runtimeErrors",
"cspViolations",
"apiFailures",
"externalResourceFailures",
"appLoadErrors"
],
"properties": {
"apiFailures": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991,
"description": "Failed calls to the app API proxy, including calls refused at the app run ceiling, which are recorded one row per 10-second window."
},
"appLoadErrors": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991,
"description": "Caught manifest-resolve / app load failures the bridge swallowed before they became an unhandled rejection."
},
"cspViolations": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991,
"description": "Content-Security-Policy violations (e.g. an external image blocked by img-src)."
},
"runtimeErrors": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991,
"description": "Runtime JS errors and unhandled rejections reported by the rendered page."
},
"contractMisses": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991,
"description": "Publish-time UI/backend contract validation failures."
},
"externalResourceFailures": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991,
"description": "Failed external image requests, including failures caught by app code."
}
},
"description": "Issue counts per category.",
"additionalProperties": false
},
"examples": {
"type": "array",
"items": {
"type": "object",
"required": [
"ts",
"category",
"kind",
"summary"
],
"properties": {
"ts": {
"type": "string",
"description": "ISO-8601 timestamp of the event."
},
"kind": {
"type": "string",
"description": "Underlying durable event kind: `diag_report` (runtime errors and CSP violations, distinguished by category), `contract_validation` (contract misses), `api_proxy_result` (API failures), `app_load_error` (caught load failures), or `image_proxy_result` (external image failures)."
},
"summary": {
"type": "string",
"description": "Compact one-line description."
},
"category": {
"enum": [
"contractMisses",
"runtimeErrors",
"cspViolations",
"apiFailures",
"appLoadErrors",
"externalResourceFailures"
],
"type": "string",
"description": "Which `counts` bucket this example illustrates."
}
},
"additionalProperties": false
},
"description": "Most recent example per non-empty category, newest first."
},
"sinceRevision": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0,
"description": "The app revision the issues are attributed to (the current revision at call time). Legacy nulls normalize to 0."
}
},
"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.",
"additionalProperties": false
},
"manifestVersion": {
"type": [
"string",
"null"
],
"description": "Deprecated public contract marker. Canonical apps return null; legacy apps return their stored contract date."
}
},
"additionalProperties": false
},
"annotations": {
"title": "Get App",
"readOnlyHint": true,
"openWorldHint": false,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description99/100
Return an app's raw persisted source (module, frontend ui script, and styles), each bucket's size in characters (`sourceSize`), and the 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. Without `bucket`, the whole source comes back in `source`. When the source is too large for your client (check `sourceSize`), read one bucket at a time instead: `bucket` returns up to 20000 characters of whole lines in `chunk`; pass `chunk.next_offset` as `offset` until it is null, and pass `expected_revision` on each page so every page comes from one revision.
The description explains the capability, when to use it, and what to expect.
Inputs88/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
app_id
The app ID (UUID)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
bucket
Read one source bucket in chunks of up to 20000 characters instead of the whole source. Use it when the whole source is too large for your client. An absent bucket reads as empty text.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
offset
Character offset into `bucket`, from the prior read's `chunk.next_offset`. Defaults to 0. Requires `bucket`.
Clear enough to supply this argument.
expected_revision
Revision from your first read. The read fails with revision_mismatch if the app has changed since, so paged chunks all come from one revision.
Clear enough to supply this argument.
Annotations75/100
Declare idempotentHint based on the tool’s actual behavior.
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"ok",
"id",
"url",
"manifestId",
"sourceManifestId",
"displayName",
"description",
"revision",
"appVersion",
"manifestVersion",
"capabilities",
"claimed",
"expiresAt",
"sourceSize"
],
"properties": {
"id": {
"type": "string",
"description": "UUID of the inspected app."
},
"ok": {
"type": "boolean",
"const": true,
"description": "Indicates success. Errors arrive as content with isError:true."
},
"url": {
"type": "string",
"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."
},
"chunk": {
"type": "object",
"required": [
"bucket",
"offset",
"text",
"next_offset"
],
"properties": {
"text": {
"type": "string",
"description": "Up to 20000 characters of the bucket, ending on a line boundary when the window holds one. Empty for an absent bucket."
},
"bucket": {
"enum": [
"module",
"ui",
"styles"
],
"type": "string",
"description": "The bucket this chunk was read from."
},
"offset": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991,
"description": "Character offset where this chunk starts."
},
"next_offset": {
"anyOf": [
{
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991
},
{
"type": "null"
}
],
"description": "Offset for the next read of this bucket. Null when this chunk reaches the end."
}
},
"description": "Present only when `bucket` was requested.",
"additionalProperties": false
},
"source": {
"type": "object",
"required": [
"module",
"ui",
"styles"
],
"properties": {
"ui": {
"type": [
"string",
"null"
],
"description": "Frontend JavaScript rendered into the #app mount point. Null if not set."
},
"module": {
"type": "string",
"description": "Backend ES module source."
},
"styles": {
"type": [
"string",
"null"
],
"description": "Optional CSS for the rendered app. Null if not set."
}
},
"description": "Persisted source bundle. The same fields accepted by create_app/update_app. Present unless `bucket` was requested; a bucket read returns `chunk` instead.",
"additionalProperties": false
},
"$schema": {
"enum": [
"https://charm.ing/schema/app-manifest/2026-07-31.json",
"https://charm.ing/schema/app-manifest/2026-09-05.json"
],
"type": "string",
"description": "Exact dated canonical manifest schema URL. Omitted for legacy apps."
},
"claimed": {
"type": "boolean",
"description": "True if the app is owned by an authenticated account. False for anonymous apps still in the unclaimed grace window."
},
"revision": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0,
"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."
},
"expiresAt": {
"type": [
"string",
"null"
],
"description": "ISO timestamp when an unclaimed app will be hard-deleted. Null for claimed apps."
},
"advisories": {
"type": "array",
"items": {
"type": "object",
"required": [
"kind",
"summary"
],
"properties": {
"data": {
"type": "object",
"description": "Kind-specific structured payload. Shape varies per advisory kind.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"kind": {
"type": "string",
"description": "Stable advisory identifier (e.g. \"legacy-bridge\")."
},
"doc_url": {
"type": "string",
"description": "Optional docs pointer for this advisory kind."
},
"summary": {
"type": "string",
"description": "Agent-facing summary. Self-sufficient; no extra context required."
},
"severity": {
"enum": [
"info",
"warn"
],
"type": "string",
"description": "Severity; omitted advisories are treated as 'info'."
},
"userSummary": {
"type": "string",
"description": "End-user-facing summary. Set when the advisory should render in-app."
}
},
"additionalProperties": false
},
"description": "Structured non-fatal advisories, including authoring issues and unread staff feedback responses. Each advisory.summary is also appended to the text content for the LLM path."
},
"appVersion": {
"type": "string",
"description": "Canonical apps return decimal String(revision). Legacy apps return their stored effective SemVer."
},
"manifestId": {
"type": "string",
"description": "Effective manifest id. May differ from `sourceManifestId` when a conflicting id was rewritten on save."
},
"sourceSize": {
"type": "object",
"required": [
"module",
"ui",
"styles"
],
"properties": {
"ui": {
"anyOf": [
{
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991
},
{
"type": "null"
}
],
"description": "Characters in the ui script. Null if not set."
},
"module": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991,
"description": "Characters in the module."
},
"styles": {
"anyOf": [
{
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991
},
{
"type": "null"
}
],
"description": "Characters in the styles. Null if not set."
}
},
"description": "Length of each source bucket in characters.",
"additionalProperties": false
},
"description": {
"type": [
"string",
"null"
],
"description": "Search-friendly summary set by the creating agent. Null on apps created before descriptions existed."
},
"displayName": {
"type": "string",
"description": "Human-readable name from the manifest."
},
"capabilities": {
"type": "object",
"required": [
"imports",
"exports"
],
"properties": {
"exports": {
"type": "array",
"items": {
"type": "string"
},
"description": "WIT-style capability ids the app exposes for cross-app calls."
},
"imports": {
"type": "array",
"items": {
"type": "string"
},
"description": "WIT-style capability ids the app requests (e.g. \"charming:storage/[email protected]\"). Empty if the app declares none."
},
"frameHosts": {
"type": "array",
"items": {
"type": "string"
},
"description": "External https origins the app may embed, added to the app CSP frame-src (e.g. [\"https://www.youtube.com\"]). Omitted when the app declares none."
},
"imageHosts": {
"type": "array",
"items": {
"type": "string"
},
"description": "External https image hosts injected into the app CSP img-src (e.g. [\"covers.openlibrary.org\"]). Omitted when the app declares none."
},
"connectOrigins": {
"type": "array",
"items": {
"type": "string"
},
"description": "Exact https and wss origins injected into the app CSP connect-src so browser fetch, WebSocket, and EventSource can reach them (e.g. [\"https://api.example.com\", \"wss://stream.example.com\"]). Omitted when the app declares none."
}
},
"description": "Capability declarations parsed from the app manifest.",
"additionalProperties": false
},
"manifestVersion": {
"type": [
"string",
"null"
],
"description": "Deprecated public contract marker. Canonical apps return null; legacy apps return their stored contract date."
},
"sourceManifestId": {
"type": "string",
"description": "Manifest id as authored in the persisted module source."
}
},
"additionalProperties": false
}
Original tool metadata
{
"name": "get_app_source",
"description": "Return an app's raw persisted source (module, frontend ui script, and styles), each bucket's size in characters (`sourceSize`), and the 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. Without `bucket`, the whole source comes back in `source`. When the source is too large for your client (check `sourceSize`), read one bucket at a time instead: `bucket` returns up to 20000 characters of whole lines in `chunk`; pass `chunk.next_offset` as `offset` until it is null, and pass `expected_revision` on each page so every page comes from one revision.",
"inputSchema": {
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"app_id"
],
"properties": {
"app_id": {
"type": "string",
"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)$",
"description": "The app ID (UUID)"
},
"bucket": {
"enum": [
"module",
"ui",
"styles"
],
"type": "string",
"description": "Read one source bucket in chunks of up to 20000 characters instead of the whole source. Use it when the whole source is too large for your client. An absent bucket reads as empty text."
},
"offset": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0,
"description": "Character offset into `bucket`, from the prior read's `chunk.next_offset`. Defaults to 0. Requires `bucket`."
},
"expected_revision": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991,
"description": "Revision from your first read. The read fails with revision_mismatch if the app has changed since, so paged chunks all come from one revision."
}
}
},
"outputSchema": {
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"ok",
"id",
"url",
"manifestId",
"sourceManifestId",
"displayName",
"description",
"revision",
"appVersion",
"manifestVersion",
"capabilities",
"claimed",
"expiresAt",
"sourceSize"
],
"properties": {
"id": {
"type": "string",
"description": "UUID of the inspected app."
},
"ok": {
"type": "boolean",
"const": true,
"description": "Indicates success. Errors arrive as content with isError:true."
},
"url": {
"type": "string",
"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."
},
"chunk": {
"type": "object",
"required": [
"bucket",
"offset",
"text",
"next_offset"
],
"properties": {
"text": {
"type": "string",
"description": "Up to 20000 characters of the bucket, ending on a line boundary when the window holds one. Empty for an absent bucket."
},
"bucket": {
"enum": [
"module",
"ui",
"styles"
],
"type": "string",
"description": "The bucket this chunk was read from."
},
"offset": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991,
"description": "Character offset where this chunk starts."
},
"next_offset": {
"anyOf": [
{
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991
},
{
"type": "null"
}
],
"description": "Offset for the next read of this bucket. Null when this chunk reaches the end."
}
},
"description": "Present only when `bucket` was requested.",
"additionalProperties": false
},
"source": {
"type": "object",
"required": [
"module",
"ui",
"styles"
],
"properties": {
"ui": {
"type": [
"string",
"null"
],
"description": "Frontend JavaScript rendered into the #app mount point. Null if not set."
},
"module": {
"type": "string",
"description": "Backend ES module source."
},
"styles": {
"type": [
"string",
"null"
],
"description": "Optional CSS for the rendered app. Null if not set."
}
},
"description": "Persisted source bundle. The same fields accepted by create_app/update_app. Present unless `bucket` was requested; a bucket read returns `chunk` instead.",
"additionalProperties": false
},
"$schema": {
"enum": [
"https://charm.ing/schema/app-manifest/2026-07-31.json",
"https://charm.ing/schema/app-manifest/2026-09-05.json"
],
"type": "string",
"description": "Exact dated canonical manifest schema URL. Omitted for legacy apps."
},
"claimed": {
"type": "boolean",
"description": "True if the app is owned by an authenticated account. False for anonymous apps still in the unclaimed grace window."
},
"revision": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0,
"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."
},
"expiresAt": {
"type": [
"string",
"null"
],
"description": "ISO timestamp when an unclaimed app will be hard-deleted. Null for claimed apps."
},
"advisories": {
"type": "array",
"items": {
"type": "object",
"required": [
"kind",
"summary"
],
"properties": {
"data": {
"type": "object",
"description": "Kind-specific structured payload. Shape varies per advisory kind.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"kind": {
"type": "string",
"description": "Stable advisory identifier (e.g. \"legacy-bridge\")."
},
"doc_url": {
"type": "string",
"description": "Optional docs pointer for this advisory kind."
},
"summary": {
"type": "string",
"description": "Agent-facing summary. Self-sufficient; no extra context required."
},
"severity": {
"enum": [
"info",
"warn"
],
"type": "string",
"description": "Severity; omitted advisories are treated as 'info'."
},
"userSummary": {
"type": "string",
"description": "End-user-facing summary. Set when the advisory should render in-app."
}
},
"additionalProperties": false
},
"description": "Structured non-fatal advisories, including authoring issues and unread staff feedback responses. Each advisory.summary is also appended to the text content for the LLM path."
},
"appVersion": {
"type": "string",
"description": "Canonical apps return decimal String(revision). Legacy apps return their stored effective SemVer."
},
"manifestId": {
"type": "string",
"description": "Effective manifest id. May differ from `sourceManifestId` when a conflicting id was rewritten on save."
},
"sourceSize": {
"type": "object",
"required": [
"module",
"ui",
"styles"
],
"properties": {
"ui": {
"anyOf": [
{
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991
},
{
"type": "null"
}
],
"description": "Characters in the ui script. Null if not set."
},
"module": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991,
"description": "Characters in the module."
},
"styles": {
"anyOf": [
{
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991
},
{
"type": "null"
}
],
"description": "Characters in the styles. Null if not set."
}
},
"description": "Length of each source bucket in characters.",
"additionalProperties": false
},
"description": {
"type": [
"string",
"null"
],
"description": "Search-friendly summary set by the creating agent. Null on apps created before descriptions existed."
},
"displayName": {
"type": "string",
"description": "Human-readable name from the manifest."
},
"capabilities": {
"type": "object",
"required": [
"imports",
"exports"
],
"properties": {
"exports": {
"type": "array",
"items": {
"type": "string"
},
"description": "WIT-style capability ids the app exposes for cross-app calls."
},
"imports": {
"type": "array",
"items": {
"type": "string"
},
"description": "WIT-style capability ids the app requests (e.g. \"charming:storage/[email protected]\"). Empty if the app declares none."
},
"frameHosts": {
"type": "array",
"items": {
"type": "string"
},
"description": "External https origins the app may embed, added to the app CSP frame-src (e.g. [\"https://www.youtube.com\"]). Omitted when the app declares none."
},
"imageHosts": {
"type": "array",
"items": {
"type": "string"
},
"description": "External https image hosts injected into the app CSP img-src (e.g. [\"covers.openlibrary.org\"]). Omitted when the app declares none."
},
"connectOrigins": {
"type": "array",
"items": {
"type": "string"
},
"description": "Exact https and wss origins injected into the app CSP connect-src so browser fetch, WebSocket, and EventSource can reach them (e.g. [\"https://api.example.com\", \"wss://stream.example.com\"]). Omitted when the app declares none."
}
},
"description": "Capability declarations parsed from the app manifest.",
"additionalProperties": false
},
"manifestVersion": {
"type": [
"string",
"null"
],
"description": "Deprecated public contract marker. Canonical apps return null; legacy apps return their stored contract date."
},
"sourceManifestId": {
"type": "string",
"description": "Manifest id as authored in the persisted module source."
}
},
"additionalProperties": false
},
"annotations": {
"title": "Get App Source",
"readOnlyHint": true,
"openWorldHint": false,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description99/100
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.
The description explains the capability, when to use it, and what to expect.
Inputs88/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
limit
Max apps to return (1-100, default 50).
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
cursor
Opaque cursor from a prior call's `nextCursor` to fetch the next page.
Clear enough to supply this argument.
Annotations75/100
Declare idempotentHint based on the tool’s actual behavior.
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"ok",
"apps"
],
"properties": {
"ok": {
"type": "boolean",
"const": true,
"description": "Indicates success. Errors arrive as content with isError:true."
},
"apps": {
"type": "array",
"items": {
"type": "object",
"required": [
"id",
"role",
"displayName",
"revision",
"description",
"url",
"shareUrl",
"lastUpdatedAt",
"claimed",
"capabilities"
],
"properties": {
"id": {
"type": "string",
"description": "App UUID. Pass to get_app to render or get_app_source to read."
},
"url": {
"type": "string",
"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."
},
"role": {
"enum": [
"owner",
"collaborator",
"end-user",
"viewer",
"team-admin",
"team-member"
],
"type": "string",
"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."
},
"appName": {
"type": [
"string",
"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)."
},
"claimed": {
"type": "boolean",
"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."
},
"revision": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0,
"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."
},
"shareUrl": {
"type": "string",
"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."
},
"description": {
"type": [
"string",
"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": {
"type": "string",
"description": "Human-readable name."
},
"capabilities": {
"type": "object",
"required": [
"imports",
"exports"
],
"properties": {
"exports": {
"type": "array",
"items": {
"type": "string"
},
"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."
},
"imports": {
"type": "array",
"items": {
"type": "string"
},
"description": "Capability tokens the app depends on (e.g. \"charming:storage/[email protected]\", \"charming:browser/[email protected]\")."
},
"frameHosts": {
"type": "array",
"items": {
"type": "string"
},
"description": "External https origins the app may embed, added to the app CSP frame-src (e.g. [\"https://www.youtube.com\"]). Omitted when the app declares none."
},
"imageHosts": {
"type": "array",
"items": {
"type": "string"
},
"description": "External https image hosts injected into the app CSP img-src (e.g. [\"covers.openlibrary.org\"]). Omitted when the app declares none."
},
"connectOrigins": {
"type": "array",
"items": {
"type": "string"
},
"description": "Exact https and wss origins injected into the app CSP connect-src so browser fetch, WebSocket, and EventSource can reach them (e.g. [\"https://api.example.com\", \"wss://stream.example.com\"]). Omitted when the app declares none."
}
},
"description": "Declared capability surface from the app manifest. The `exports` array is the discoverability hook for cross-app calls.",
"additionalProperties": false
},
"lastUpdatedAt": {
"type": "string",
"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."
}
},
"additionalProperties": false
},
"description": "Apps on this page, ordered by creation time newest-first."
},
"advisories": {
"type": "array",
"items": {
"type": "object",
"required": [
"kind",
"summary"
],
"properties": {
"data": {
"type": "object",
"description": "Kind-specific structured payload. Shape varies per advisory kind.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"kind": {
"type": "string",
"description": "Stable advisory identifier (e.g. \"legacy-bridge\")."
},
"doc_url": {
"type": "string",
"description": "Optional docs pointer for this advisory kind."
},
"summary": {
"type": "string",
"description": "Agent-facing summary. Self-sufficient; no extra context required."
},
"severity": {
"enum": [
"info",
"warn"
],
"type": "string",
"description": "Severity; omitted advisories are treated as 'info'."
},
"userSummary": {
"type": "string",
"description": "End-user-facing summary. Set when the advisory should render in-app."
}
},
"additionalProperties": false
},
"description": "Structured non-fatal advisories, including authoring issues and unread staff feedback responses. Each advisory.summary is also appended to the text content for the LLM path."
},
"nextCursor": {
"type": "string",
"description": "Opaque cursor for the next page. Pass back as `cursor` on the next list_apps call. Absent on the final page."
}
},
"additionalProperties": false
}
Original tool metadata
{
"name": "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.",
"inputSchema": {
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"limit": {
"type": "integer",
"maximum": 100,
"minimum": 1,
"description": "Max apps to return (1-100, default 50)."
},
"cursor": {
"type": "string",
"description": "Opaque cursor from a prior call's `nextCursor` to fetch the next page."
}
}
},
"outputSchema": {
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"ok",
"apps"
],
"properties": {
"ok": {
"type": "boolean",
"const": true,
"description": "Indicates success. Errors arrive as content with isError:true."
},
"apps": {
"type": "array",
"items": {
"type": "object",
"required": [
"id",
"role",
"displayName",
"revision",
"description",
"url",
"shareUrl",
"lastUpdatedAt",
"claimed",
"capabilities"
],
"properties": {
"id": {
"type": "string",
"description": "App UUID. Pass to get_app to render or get_app_source to read."
},
"url": {
"type": "string",
"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."
},
"role": {
"enum": [
"owner",
"collaborator",
"end-user",
"viewer",
"team-admin",
"team-member"
],
"type": "string",
"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."
},
"appName": {
"type": [
"string",
"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)."
},
"claimed": {
"type": "boolean",
"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."
},
"revision": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0,
"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."
},
"shareUrl": {
"type": "string",
"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."
},
"description": {
"type": [
"string",
"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": {
"type": "string",
"description": "Human-readable name."
},
"capabilities": {
"type": "object",
"required": [
"imports",
"exports"
],
"properties": {
"exports": {
"type": "array",
"items": {
"type": "string"
},
"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."
},
"imports": {
"type": "array",
"items": {
"type": "string"
},
"description": "Capability tokens the app depends on (e.g. \"charming:storage/[email protected]\", \"charming:browser/[email protected]\")."
},
"frameHosts": {
"type": "array",
"items": {
"type": "string"
},
"description": "External https origins the app may embed, added to the app CSP frame-src (e.g. [\"https://www.youtube.com\"]). Omitted when the app declares none."
},
"imageHosts": {
"type": "array",
"items": {
"type": "string"
},
"description": "External https image hosts injected into the app CSP img-src (e.g. [\"covers.openlibrary.org\"]). Omitted when the app declares none."
},
"connectOrigins": {
"type": "array",
"items": {
"type": "string"
},
"description": "Exact https and wss origins injected into the app CSP connect-src so browser fetch, WebSocket, and EventSource can reach them (e.g. [\"https://api.example.com\", \"wss://stream.example.com\"]). Omitted when the app declares none."
}
},
"description": "Declared capability surface from the app manifest. The `exports` array is the discoverability hook for cross-app calls.",
"additionalProperties": false
},
"lastUpdatedAt": {
"type": "string",
"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."
}
},
"additionalProperties": false
},
"description": "Apps on this page, ordered by creation time newest-first."
},
"advisories": {
"type": "array",
"items": {
"type": "object",
"required": [
"kind",
"summary"
],
"properties": {
"data": {
"type": "object",
"description": "Kind-specific structured payload. Shape varies per advisory kind.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"kind": {
"type": "string",
"description": "Stable advisory identifier (e.g. \"legacy-bridge\")."
},
"doc_url": {
"type": "string",
"description": "Optional docs pointer for this advisory kind."
},
"summary": {
"type": "string",
"description": "Agent-facing summary. Self-sufficient; no extra context required."
},
"severity": {
"enum": [
"info",
"warn"
],
"type": "string",
"description": "Severity; omitted advisories are treated as 'info'."
},
"userSummary": {
"type": "string",
"description": "End-user-facing summary. Set when the advisory should render in-app."
}
},
"additionalProperties": false
},
"description": "Structured non-fatal advisories, including authoring issues and unread staff feedback responses. Each advisory.summary is also appended to the text content for the LLM path."
},
"nextCursor": {
"type": "string",
"description": "Opaque cursor for the next page. Pass back as `cursor` on the next list_apps call. Absent on the final page."
}
},
"additionalProperties": false
},
"annotations": {
"title": "List Apps",
"readOnlyHint": true,
"openWorldHint": false,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description95/100
Permanently delete a Charming app the caller owns, optionally also purging its stored data and secrets. 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.
The description explains the capability, when to use it, and what to expect.
Inputs92/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
app_id
The app ID (UUID) to delete
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
purge_storage
Also delete the app's stored data and secrets now. Without it, its secrets are kept and its stored data is cleared within an hour.
Clear enough to supply this argument.
confirm
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.
Clear enough to supply this argument.
Annotations75/100
Declare idempotentHint based on the tool’s actual behavior.
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"ok",
"id",
"deleted"
],
"properties": {
"id": {
"type": "string",
"description": "UUID of the targeted app."
},
"ok": {
"type": "boolean",
"const": true,
"description": "Indicates success. Errors arrive as content with isError:true."
},
"deleted": {
"type": "boolean",
"description": "True if the app was deleted, false if deletion was cancelled by the user."
},
"cancelled": {
"type": "boolean",
"description": "Set to true when the user declined the elicitation confirmation prompt."
},
"advisories": {
"type": "array",
"items": {
"type": "object",
"required": [
"kind",
"summary"
],
"properties": {
"data": {
"type": "object",
"description": "Kind-specific structured payload. Shape varies per advisory kind.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"kind": {
"type": "string",
"description": "Stable advisory identifier (e.g. \"legacy-bridge\")."
},
"doc_url": {
"type": "string",
"description": "Optional docs pointer for this advisory kind."
},
"summary": {
"type": "string",
"description": "Agent-facing summary. Self-sufficient; no extra context required."
},
"severity": {
"enum": [
"info",
"warn"
],
"type": "string",
"description": "Severity; omitted advisories are treated as 'info'."
},
"userSummary": {
"type": "string",
"description": "End-user-facing summary. Set when the advisory should render in-app."
}
},
"additionalProperties": false
},
"description": "Structured non-fatal advisories, including authoring issues and unread staff feedback responses. Each advisory.summary is also appended to the text content for the LLM path."
}
},
"additionalProperties": false
}
Original tool metadata
{
"name": "delete_app",
"description": "Permanently delete a Charming app the caller owns, optionally also purging its stored data and secrets. 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.",
"inputSchema": {
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"app_id"
],
"properties": {
"app_id": {
"type": "string",
"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)$",
"description": "The app ID (UUID) to delete"
},
"confirm": {
"type": "boolean",
"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."
},
"purge_storage": {
"type": "boolean",
"description": "Also delete the app's stored data and secrets now. Without it, its secrets are kept and its stored data is cleared within an hour."
}
}
},
"outputSchema": {
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"ok",
"id",
"deleted"
],
"properties": {
"id": {
"type": "string",
"description": "UUID of the targeted app."
},
"ok": {
"type": "boolean",
"const": true,
"description": "Indicates success. Errors arrive as content with isError:true."
},
"deleted": {
"type": "boolean",
"description": "True if the app was deleted, false if deletion was cancelled by the user."
},
"cancelled": {
"type": "boolean",
"description": "Set to true when the user declined the elicitation confirmation prompt."
},
"advisories": {
"type": "array",
"items": {
"type": "object",
"required": [
"kind",
"summary"
],
"properties": {
"data": {
"type": "object",
"description": "Kind-specific structured payload. Shape varies per advisory kind.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"kind": {
"type": "string",
"description": "Stable advisory identifier (e.g. \"legacy-bridge\")."
},
"doc_url": {
"type": "string",
"description": "Optional docs pointer for this advisory kind."
},
"summary": {
"type": "string",
"description": "Agent-facing summary. Self-sufficient; no extra context required."
},
"severity": {
"enum": [
"info",
"warn"
],
"type": "string",
"description": "Severity; omitted advisories are treated as 'info'."
},
"userSummary": {
"type": "string",
"description": "End-user-facing summary. Set when the advisory should render in-app."
}
},
"additionalProperties": false
},
"description": "Structured non-fatal advisories, including authoring issues and unread staff feedback responses. Each advisory.summary is also appended to the text content for the LLM path."
}
},
"additionalProperties": false
},
"annotations": {
"title": "Delete App",
"readOnlyHint": false,
"openWorldHint": true,
"destructiveHint": true
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description89/100
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 or video clip, a JavaScript module or worker script (`.js`/`.mjs`, which needs edit access to the app), 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>/<audio>/<video>, and for `import()` or `new Worker()` of an uploaded script at the standalone app URL; chat embeds cannot run uploaded scripts) or `env.assets.get(key)` in its backend (the app must declare `charming:storage/[email protected]` in manifest.capabilities.imports to use env.assets). Caps: 10 MiB/asset, 50 assets/app, 100 MiB/app.
The description explains the capability, when to use it, and what to expect.
Inputs92/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
appId
The app id (app_modules.id) to attach the asset to. Must be an app you own.
Clear enough to supply this argument.
key
Asset name, e.g. "dataset_v1.json". URL-safe [a-zA-Z0-9._-], <=128 chars, no leading dot.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
contentType
MIME type. Defaults: text/javascript for a .js or .mjs key (also when a sourceUrl serves it as text/plain or application/octet-stream), otherwise text/plain for text, sniffed for sourceUrl, application/octet-stream otherwise.
Clear enough to supply this argument.
text
UTF-8 text payload (datasets, JSON, CSV). No base64.
Clear enough to supply this argument.
sourceUrl
https URL the server fetches and stores. Bytes never pass through this tool call.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
dataBase64
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.
Clear enough to supply this argument.
Annotations75/100
Declare idempotentHint based on the tool’s actual behavior.
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"ok",
"appId",
"key",
"bytes",
"contentType",
"url"
],
"properties": {
"ok": {
"type": "boolean",
"const": true,
"description": "Indicates success. Errors arrive as content with isError:true."
},
"key": {
"type": "string",
"description": "The asset key it was stored under. Same value passed in."
},
"url": {
"type": "string",
"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)."
},
"appId": {
"type": "string",
"description": "UUID of the asset-owning app."
},
"bytes": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991,
"description": "Stored byte length of the asset payload after decode/fetch."
},
"advisories": {
"type": "array",
"items": {
"type": "object",
"required": [
"kind",
"summary"
],
"properties": {
"data": {
"type": "object",
"description": "Kind-specific structured payload. Shape varies per advisory kind.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"kind": {
"type": "string",
"description": "Stable advisory identifier (e.g. \"legacy-bridge\")."
},
"doc_url": {
"type": "string",
"description": "Optional docs pointer for this advisory kind."
},
"summary": {
"type": "string",
"description": "Agent-facing summary. Self-sufficient; no extra context required."
},
"severity": {
"enum": [
"info",
"warn"
],
"type": "string",
"description": "Severity; omitted advisories are treated as 'info'."
},
"userSummary": {
"type": "string",
"description": "End-user-facing summary. Set when the advisory should render in-app."
}
},
"additionalProperties": false
},
"description": "Structured non-fatal advisories, including authoring issues and unread staff feedback responses. Each advisory.summary is also appended to the text content for the LLM path."
},
"contentType": {
"type": "string",
"description": "Effective MIME type stored with the asset (supplied, sniffed from sourceUrl, or defaulted)."
}
},
"additionalProperties": false
}
Original tool metadata
{
"name": "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 or video clip, a JavaScript module or worker script (`.js`/`.mjs`, which needs edit access to the app), 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>/<audio>/<video>, and for `import()` or `new Worker()` of an uploaded script at the standalone app URL; chat embeds cannot run uploaded scripts) or `env.assets.get(key)` in its backend (the app must declare `charming:storage/[email protected]` in manifest.capabilities.imports to use env.assets). Caps: 10 MiB/asset, 50 assets/app, 100 MiB/app.",
"inputSchema": {
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"appId",
"key"
],
"properties": {
"key": {
"type": "string",
"description": "Asset name, e.g. \"dataset_v1.json\". URL-safe [a-zA-Z0-9._-], <=128 chars, no leading dot."
},
"text": {
"type": "string",
"description": "UTF-8 text payload (datasets, JSON, CSV). No base64."
},
"appId": {
"type": "string",
"description": "The app id (app_modules.id) to attach the asset to. Must be an app you own."
},
"sourceUrl": {
"type": "string",
"format": "uri",
"description": "https URL the server fetches and stores. Bytes never pass through this tool call."
},
"dataBase64": {
"type": "string",
"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."
},
"contentType": {
"type": "string",
"description": "MIME type. Defaults: text/javascript for a .js or .mjs key (also when a sourceUrl serves it as text/plain or application/octet-stream), otherwise text/plain for text, sniffed for sourceUrl, application/octet-stream otherwise."
}
}
},
"outputSchema": {
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"ok",
"appId",
"key",
"bytes",
"contentType",
"url"
],
"properties": {
"ok": {
"type": "boolean",
"const": true,
"description": "Indicates success. Errors arrive as content with isError:true."
},
"key": {
"type": "string",
"description": "The asset key it was stored under. Same value passed in."
},
"url": {
"type": "string",
"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)."
},
"appId": {
"type": "string",
"description": "UUID of the asset-owning app."
},
"bytes": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991,
"description": "Stored byte length of the asset payload after decode/fetch."
},
"advisories": {
"type": "array",
"items": {
"type": "object",
"required": [
"kind",
"summary"
],
"properties": {
"data": {
"type": "object",
"description": "Kind-specific structured payload. Shape varies per advisory kind.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"kind": {
"type": "string",
"description": "Stable advisory identifier (e.g. \"legacy-bridge\")."
},
"doc_url": {
"type": "string",
"description": "Optional docs pointer for this advisory kind."
},
"summary": {
"type": "string",
"description": "Agent-facing summary. Self-sufficient; no extra context required."
},
"severity": {
"enum": [
"info",
"warn"
],
"type": "string",
"description": "Severity; omitted advisories are treated as 'info'."
},
"userSummary": {
"type": "string",
"description": "End-user-facing summary. Set when the advisory should render in-app."
}
},
"additionalProperties": false
},
"description": "Structured non-fatal advisories, including authoring issues and unread staff feedback responses. Each advisory.summary is also appended to the text content for the LLM path."
},
"contentType": {
"type": "string",
"description": "Effective MIME type stored with the asset (supplied, sniffed from sourceUrl, or defaulted)."
}
},
"additionalProperties": false
},
"annotations": {
"title": "Upload Asset",
"readOnlyHint": false,
"openWorldHint": true,
"destructiveHint": true
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description95/100
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.
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
app_id
The app ID (UUID) to rename.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
app_name
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.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
Annotations75/100
Declare idempotentHint based on the tool’s actual behavior.
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"ok",
"id",
"url",
"shareUrl",
"appName",
"previousAppName"
],
"properties": {
"id": {
"type": "string",
"description": "UUID of the renamed app."
},
"ok": {
"type": "boolean",
"const": true,
"description": "Indicates success. Errors arrive as content with isError:true."
},
"url": {
"type": "string",
"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."
},
"appName": {
"type": "string",
"description": "The new live slug for the app."
},
"shareUrl": {
"type": "string",
"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."
},
"advisories": {
"type": "array",
"items": {
"type": "object",
"required": [
"kind",
"summary"
],
"properties": {
"data": {
"type": "object",
"description": "Kind-specific structured payload. Shape varies per advisory kind.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"kind": {
"type": "string",
"description": "Stable advisory identifier (e.g. \"legacy-bridge\")."
},
"doc_url": {
"type": "string",
"description": "Optional docs pointer for this advisory kind."
},
"summary": {
"type": "string",
"description": "Agent-facing summary. Self-sufficient; no extra context required."
},
"severity": {
"enum": [
"info",
"warn"
],
"type": "string",
"description": "Severity; omitted advisories are treated as 'info'."
},
"userSummary": {
"type": "string",
"description": "End-user-facing summary. Set when the advisory should render in-app."
}
},
"additionalProperties": false
},
"description": "Structured non-fatal advisories, including authoring issues and unread staff feedback responses. Each advisory.summary is also appended to the text content for the LLM path."
},
"previousAppName": {
"type": "string",
"description": "The slug the app had before this rename. Retained as an alias so old URLs keep resolving (a redirect to the new slug)."
}
},
"additionalProperties": false
}
Original tool metadata
{
"name": "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.",
"inputSchema": {
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"app_id",
"app_name"
],
"properties": {
"app_id": {
"type": "string",
"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)$",
"description": "The app ID (UUID) to rename."
},
"app_name": {
"type": "string",
"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."
}
}
},
"outputSchema": {
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"ok",
"id",
"url",
"shareUrl",
"appName",
"previousAppName"
],
"properties": {
"id": {
"type": "string",
"description": "UUID of the renamed app."
},
"ok": {
"type": "boolean",
"const": true,
"description": "Indicates success. Errors arrive as content with isError:true."
},
"url": {
"type": "string",
"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."
},
"appName": {
"type": "string",
"description": "The new live slug for the app."
},
"shareUrl": {
"type": "string",
"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."
},
"advisories": {
"type": "array",
"items": {
"type": "object",
"required": [
"kind",
"summary"
],
"properties": {
"data": {
"type": "object",
"description": "Kind-specific structured payload. Shape varies per advisory kind.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"kind": {
"type": "string",
"description": "Stable advisory identifier (e.g. \"legacy-bridge\")."
},
"doc_url": {
"type": "string",
"description": "Optional docs pointer for this advisory kind."
},
"summary": {
"type": "string",
"description": "Agent-facing summary. Self-sufficient; no extra context required."
},
"severity": {
"enum": [
"info",
"warn"
],
"type": "string",
"description": "Severity; omitted advisories are treated as 'info'."
},
"userSummary": {
"type": "string",
"description": "End-user-facing summary. Set when the advisory should render in-app."
}
},
"additionalProperties": false
},
"description": "Structured non-fatal advisories, including authoring issues and unread staff feedback responses. Each advisory.summary is also appended to the text content for the LLM path."
},
"previousAppName": {
"type": "string",
"description": "The slug the app had before this rename. Retained as an alias so old URLs keep resolving (a redirect to the new slug)."
}
},
"additionalProperties": false
},
"annotations": {
"title": "Rename App",
"readOnlyHint": false,
"openWorldHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description91/100
Deprecated: use `set_template` instead. This metadata-blind alias accepts only `app_id`, enables Template for people who can already read the source App, and leaves listing metadata and Listed state unchanged. The original is never mutated. Use this when an older workflow requires the deprecated alias; use `set_template` for new work. Each copy belongs to the signed-in visitor from creation. 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.
The description explains the capability, when to use it, and what to expect.
Inputs93/100
Input documentation gives an agent enough context to populate the arguments.
app_id
The app ID (UUID) to share as remixable
Clear enough to supply this argument.
Annotations75/100
Declare idempotentHint based on the tool’s actual behavior.
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"ok",
"public_url",
"message"
],
"properties": {
"ok": {
"type": "boolean",
"const": true,
"description": "Indicates success. Errors arrive as content with isError:true."
},
"message": {
"type": "string",
"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."
},
"advisories": {
"type": "array",
"items": {
"type": "object",
"required": [
"kind",
"summary"
],
"properties": {
"data": {
"type": "object",
"description": "Kind-specific structured payload. Shape varies per advisory kind.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"kind": {
"type": "string",
"description": "Stable advisory identifier (e.g. \"legacy-bridge\")."
},
"doc_url": {
"type": "string",
"description": "Optional docs pointer for this advisory kind."
},
"summary": {
"type": "string",
"description": "Agent-facing summary. Self-sufficient; no extra context required."
},
"severity": {
"enum": [
"info",
"warn"
],
"type": "string",
"description": "Severity; omitted advisories are treated as 'info'."
},
"userSummary": {
"type": "string",
"description": "End-user-facing summary. Set when the advisory should render in-app."
}
},
"additionalProperties": false
},
"description": "Structured non-fatal advisories, including authoring issues and unread staff feedback responses. Each advisory.summary is also appended to the text content for the LLM path."
},
"public_url": {
"type": "string",
"description": "Canonical `/templates/<handle>/<app-name>` Template page when the owner and App have public names. A person who can already read the source App can choose to create an editable copy after signing in; the original is never mutated. Free of write-capable `?t=` tokens."
}
},
"additionalProperties": false
}
Original tool metadata
{
"name": "set_remixable",
"description": "Deprecated: use `set_template` instead. This metadata-blind alias accepts only `app_id`, enables Template for people who can already read the source App, and leaves listing metadata and Listed state unchanged. The original is never mutated. Use this when an older workflow requires the deprecated alias; use `set_template` for new work. Each copy belongs to the signed-in visitor from creation. 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.",
"inputSchema": {
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"app_id"
],
"properties": {
"app_id": {
"type": "string",
"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)$",
"description": "The app ID (UUID) to share as remixable"
}
}
},
"outputSchema": {
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"ok",
"public_url",
"message"
],
"properties": {
"ok": {
"type": "boolean",
"const": true,
"description": "Indicates success. Errors arrive as content with isError:true."
},
"message": {
"type": "string",
"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."
},
"advisories": {
"type": "array",
"items": {
"type": "object",
"required": [
"kind",
"summary"
],
"properties": {
"data": {
"type": "object",
"description": "Kind-specific structured payload. Shape varies per advisory kind.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"kind": {
"type": "string",
"description": "Stable advisory identifier (e.g. \"legacy-bridge\")."
},
"doc_url": {
"type": "string",
"description": "Optional docs pointer for this advisory kind."
},
"summary": {
"type": "string",
"description": "Agent-facing summary. Self-sufficient; no extra context required."
},
"severity": {
"enum": [
"info",
"warn"
],
"type": "string",
"description": "Severity; omitted advisories are treated as 'info'."
},
"userSummary": {
"type": "string",
"description": "End-user-facing summary. Set when the advisory should render in-app."
}
},
"additionalProperties": false
},
"description": "Structured non-fatal advisories, including authoring issues and unread staff feedback responses. Each advisory.summary is also appended to the text content for the LLM path."
},
"public_url": {
"type": "string",
"description": "Canonical `/templates/<handle>/<app-name>` Template page when the owner and App have public names. A person who can already read the source App can choose to create an editable copy after signing in; the original is never mutated. Free of write-capable `?t=` tokens."
}
},
"additionalProperties": false
},
"annotations": {
"title": "Share App as Remixable",
"readOnlyHint": false,
"openWorldHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description97/100
Deprecated: use `unset_template` instead (identical effect). Stop allowing remixes of a Charming app. Use this to stop offering copies to new visitors; existing copies survive untouched. Also clears any public template listing, since a listed app must stay copyable. Idempotent: calling on an already-non-remixable app is a no-op.
The description explains the capability, when to use it, and what to expect.
Inputs94/100
Input documentation gives an agent enough context to populate the arguments.
app_id
The app ID (UUID) to stop sharing as remixable
Clear enough to supply this argument.
Annotations75/100
Declare idempotentHint based on the tool’s actual behavior.
The title clearly communicates the tool’s action and subject. Keep it concise.
Description89/100
Enable a Charming Template for people who can already read the source App, and optionally publish it in Charming's public Template directory so anyone can discover and copy it. Listed grants no access to the live source App or its data. Use this when the user wants independent copies or a public Template listing, not access to the live App. Each copy belongs to the signed-in visitor from creation. Optional listing fields are a patch: `title`, `summary`, `content_markdown`, `category`, cover fields, and `gallery` change only when supplied. Publishing with `listed: true` requires a nonblank resulting title and summary; every cover or gallery image needs nonblank alt text and must reference an image asset from this App. Pass `listed: true` to also mark it for discovery, or `listed: false` to unlist it. Omitting `listed` is a patch, not a reset: it leaves the current listed state exactly as it is (an already-listed app stays listed; a never-listed app stays unlisted), so re-affirming that an app is a template never surprises the owner by delisting it. Idempotent: calling with the same effective `listed` state as the current one returns the same URL without re-firing analytics. Anonymous apps cannot be made into templates; the app must be claimed first.
The description explains the capability, when to use it, and what to expect.
Inputs92/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
app_id
The app ID (UUID) to publish as a template
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
title
Template listing title; null clears it
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
summary
Template listing summary; null clears it
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
content_markdown
Long-form Template listing content; null clears it
Clear enough to supply this argument.
category
Template listing category; null clears it
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
cover_asset_key
App asset key for the cover image; null removes the cover
Clear enough to supply this argument.
cover_alt
Nonblank cover alt text; supply with cover_asset_key
Clear enough to supply this argument.
cover_caption
Optional cover caption; supply with cover_asset_key
Clear enough to supply this argument.
gallery › anyOf › 0 › items › asset_key
App asset key for this screenshot
Clear enough to supply this argument.
gallery › anyOf › 0 › items › alt
Nonblank alt text for this screenshot
Clear enough to supply this argument.
gallery › anyOf › 0 › items › caption
Optional screenshot caption
Clear enough to supply this argument.
listed
Whether to also list the app in Charming's public template directory for discovery. `true` lists it, `false` unlists it. Omit this to leave the current listed state untouched — it is NOT a default-false reset, so omitting it never unlists an already-listed app.
Clear enough to supply this argument.
Annotations75/100
Declare idempotentHint based on the tool’s actual behavior.
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"ok",
"public_url",
"listed",
"message"
],
"properties": {
"ok": {
"type": "boolean",
"const": true,
"description": "Indicates success. Errors arrive as content with isError:true."
},
"listed": {
"type": "boolean",
"description": "Whether the app is currently listed in the public template directory, reflecting the ACTUAL resulting state — not necessarily the call's `listed` input, since an omitted `listed` leaves the prior state unchanged. A template can be copyable without being listed."
},
"message": {
"type": "string",
"description": "Consequence copy the agent should surface verbatim to the user when reporting the change. Spells out that other people will get their own copy — the user's data is untouched — and, when listed, that strangers can discover it."
},
"advisories": {
"type": "array",
"items": {
"type": "object",
"required": [
"kind",
"summary"
],
"properties": {
"data": {
"type": "object",
"description": "Kind-specific structured payload. Shape varies per advisory kind.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"kind": {
"type": "string",
"description": "Stable advisory identifier (e.g. \"legacy-bridge\")."
},
"doc_url": {
"type": "string",
"description": "Optional docs pointer for this advisory kind."
},
"summary": {
"type": "string",
"description": "Agent-facing summary. Self-sufficient; no extra context required."
},
"severity": {
"enum": [
"info",
"warn"
],
"type": "string",
"description": "Severity; omitted advisories are treated as 'info'."
},
"userSummary": {
"type": "string",
"description": "End-user-facing summary. Set when the advisory should render in-app."
}
},
"additionalProperties": false
},
"description": "Structured non-fatal advisories, including authoring issues and unread staff feedback responses. Each advisory.summary is also appended to the text content for the LLM path."
},
"public_url": {
"type": "string",
"description": "Canonical `/templates/<handle>/<app-name>` Template page. The URL stays the same while the Template is listed or unlisted. Existing App readers can copy an unlisted Template; anyone can copy it when Listed. The original is never mutated. Free of write-capable `?t=` tokens."
}
},
"additionalProperties": false
}
Original tool metadata
{
"name": "set_template",
"description": "Enable a Charming Template for people who can already read the source App, and optionally publish it in Charming's public Template directory so anyone can discover and copy it. Listed grants no access to the live source App or its data. Use this when the user wants independent copies or a public Template listing, not access to the live App. Each copy belongs to the signed-in visitor from creation. Optional listing fields are a patch: `title`, `summary`, `content_markdown`, `category`, cover fields, and `gallery` change only when supplied. Publishing with `listed: true` requires a nonblank resulting title and summary; every cover or gallery image needs nonblank alt text and must reference an image asset from this App. Pass `listed: true` to also mark it for discovery, or `listed: false` to unlist it. Omitting `listed` is a patch, not a reset: it leaves the current listed state exactly as it is (an already-listed app stays listed; a never-listed app stays unlisted), so re-affirming that an app is a template never surprises the owner by delisting it. Idempotent: calling with the same effective `listed` state as the current one returns the same URL without re-firing analytics. Anonymous apps cannot be made into templates; the app must be claimed first.",
"inputSchema": {
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"app_id"
],
"properties": {
"title": {
"type": [
"string",
"null"
],
"description": "Template listing title; null clears it"
},
"app_id": {
"type": "string",
"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)$",
"description": "The app ID (UUID) to publish as a template"
},
"listed": {
"type": "boolean",
"description": "Whether to also list the app in Charming's public template directory for discovery. `true` lists it, `false` unlists it. Omit this to leave the current listed state untouched — it is NOT a default-false reset, so omitting it never unlists an already-listed app."
},
"gallery": {
"anyOf": [
{
"type": "array",
"items": {
"type": "object",
"required": [
"asset_key",
"alt"
],
"properties": {
"alt": {
"type": "string",
"description": "Nonblank alt text for this screenshot"
},
"caption": {
"type": [
"string",
"null"
],
"description": "Optional screenshot caption"
},
"asset_key": {
"type": "string",
"description": "App asset key for this screenshot"
}
},
"additionalProperties": false
}
},
{
"type": "null"
}
],
"description": "Ordered screenshot records; null or an empty array clears the gallery"
},
"summary": {
"type": [
"string",
"null"
],
"description": "Template listing summary; null clears it"
},
"category": {
"anyOf": [
{
"enum": [
"business",
"developer-tools",
"education",
"entertainment",
"lifestyle",
"productivity",
"utilities"
],
"type": "string"
},
{
"type": "null"
}
],
"description": "Template listing category; null clears it"
},
"cover_alt": {
"type": "string",
"description": "Nonblank cover alt text; supply with cover_asset_key"
},
"cover_caption": {
"type": [
"string",
"null"
],
"description": "Optional cover caption; supply with cover_asset_key"
},
"cover_asset_key": {
"type": [
"string",
"null"
],
"description": "App asset key for the cover image; null removes the cover"
},
"content_markdown": {
"type": [
"string",
"null"
],
"description": "Long-form Template listing content; null clears it"
}
}
},
"outputSchema": {
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"ok",
"public_url",
"listed",
"message"
],
"properties": {
"ok": {
"type": "boolean",
"const": true,
"description": "Indicates success. Errors arrive as content with isError:true."
},
"listed": {
"type": "boolean",
"description": "Whether the app is currently listed in the public template directory, reflecting the ACTUAL resulting state — not necessarily the call's `listed` input, since an omitted `listed` leaves the prior state unchanged. A template can be copyable without being listed."
},
"message": {
"type": "string",
"description": "Consequence copy the agent should surface verbatim to the user when reporting the change. Spells out that other people will get their own copy — the user's data is untouched — and, when listed, that strangers can discover it."
},
"advisories": {
"type": "array",
"items": {
"type": "object",
"required": [
"kind",
"summary"
],
"properties": {
"data": {
"type": "object",
"description": "Kind-specific structured payload. Shape varies per advisory kind.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"kind": {
"type": "string",
"description": "Stable advisory identifier (e.g. \"legacy-bridge\")."
},
"doc_url": {
"type": "string",
"description": "Optional docs pointer for this advisory kind."
},
"summary": {
"type": "string",
"description": "Agent-facing summary. Self-sufficient; no extra context required."
},
"severity": {
"enum": [
"info",
"warn"
],
"type": "string",
"description": "Severity; omitted advisories are treated as 'info'."
},
"userSummary": {
"type": "string",
"description": "End-user-facing summary. Set when the advisory should render in-app."
}
},
"additionalProperties": false
},
"description": "Structured non-fatal advisories, including authoring issues and unread staff feedback responses. Each advisory.summary is also appended to the text content for the LLM path."
},
"public_url": {
"type": "string",
"description": "Canonical `/templates/<handle>/<app-name>` Template page. The URL stays the same while the Template is listed or unlisted. Existing App readers can copy an unlisted Template; anyone can copy it when Listed. The original is never mutated. Free of write-capable `?t=` tokens."
}
},
"additionalProperties": false
},
"annotations": {
"title": "Publish App as Template",
"readOnlyHint": false,
"openWorldHint": true,
"destructiveHint": true
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description97/100
Stop treating a Charming app as a template. Use this to turn off copying for new visitors and clear any public listing; existing copies survive untouched. Idempotent: calling on an app that is neither copyable nor listed is a no-op.
The description explains the capability, when to use it, and what to expect.
Inputs96/100
Input documentation gives an agent enough context to populate the arguments.
app_id
The app ID (UUID) to stop publishing as a template
Clear enough to supply this argument.
Annotations75/100
Declare idempotentHint based on the tool’s actual behavior.
The title clearly communicates the tool’s action and subject. Keep it concise.
Description98/100
Make a Charming app Public: anyone with the URL can open the live hosted App Viewer with no login and read its data. Public visitors cannot change data or source. Use this for a read-only public surface such as a dashboard, menu, schedule, or status page. Surface the returned `message` verbatim so the user understands the access granted. For write access, share with the `end-user` role or choose the signed-in audience setting. Template and Listed are separate settings. Idempotent: calling on an already-public app returns the same URL without re-firing analytics. Owner-only; anonymous apps must be claimed first.
The description explains the capability, when to use it, and what to expect.
Inputs96/100
Input documentation gives an agent enough context to populate the arguments.
app_id
The app ID (UUID) to make public
Clear enough to supply this argument.
Annotations75/100
Declare idempotentHint based on the tool’s actual behavior.
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"ok",
"public_url",
"message"
],
"properties": {
"ok": {
"type": "boolean",
"const": true,
"description": "Indicates success. Errors arrive as content with isError:true."
},
"message": {
"type": "string",
"description": "Consequence copy the agent should surface verbatim to the user. It states that anyone with the URL can view the live App with no login but cannot change its data or source."
},
"advisories": {
"type": "array",
"items": {
"type": "object",
"required": [
"kind",
"summary"
],
"properties": {
"data": {
"type": "object",
"description": "Kind-specific structured payload. Shape varies per advisory kind.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"kind": {
"type": "string",
"description": "Stable advisory identifier (e.g. \"legacy-bridge\")."
},
"doc_url": {
"type": "string",
"description": "Optional docs pointer for this advisory kind."
},
"summary": {
"type": "string",
"description": "Agent-facing summary. Self-sufficient; no extra context required."
},
"severity": {
"enum": [
"info",
"warn"
],
"type": "string",
"description": "Severity; omitted advisories are treated as 'info'."
},
"userSummary": {
"type": "string",
"description": "End-user-facing summary. Set when the advisory should render in-app."
}
},
"additionalProperties": false
},
"description": "Structured non-fatal advisories, including authoring issues and unread staff feedback responses. Each advisory.summary is also appended to the text content for the LLM path."
},
"public_url": {
"type": "string",
"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 view the live App and its data, but cannot change data or source. Free of write-capable `?t=` tokens."
}
},
"additionalProperties": false
}
Original tool metadata
{
"name": "set_public",
"description": "Make a Charming app Public: anyone with the URL can open the live hosted App Viewer with no login and read its data. Public visitors cannot change data or source. Use this for a read-only public surface such as a dashboard, menu, schedule, or status page. Surface the returned `message` verbatim so the user understands the access granted. For write access, share with the `end-user` role or choose the signed-in audience setting. Template and Listed are separate settings. Idempotent: calling on an already-public app returns the same URL without re-firing analytics. Owner-only; anonymous apps must be claimed first.",
"inputSchema": {
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"app_id"
],
"properties": {
"app_id": {
"type": "string",
"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)$",
"description": "The app ID (UUID) to make public"
}
}
},
"outputSchema": {
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"ok",
"public_url",
"message"
],
"properties": {
"ok": {
"type": "boolean",
"const": true,
"description": "Indicates success. Errors arrive as content with isError:true."
},
"message": {
"type": "string",
"description": "Consequence copy the agent should surface verbatim to the user. It states that anyone with the URL can view the live App with no login but cannot change its data or source."
},
"advisories": {
"type": "array",
"items": {
"type": "object",
"required": [
"kind",
"summary"
],
"properties": {
"data": {
"type": "object",
"description": "Kind-specific structured payload. Shape varies per advisory kind.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"kind": {
"type": "string",
"description": "Stable advisory identifier (e.g. \"legacy-bridge\")."
},
"doc_url": {
"type": "string",
"description": "Optional docs pointer for this advisory kind."
},
"summary": {
"type": "string",
"description": "Agent-facing summary. Self-sufficient; no extra context required."
},
"severity": {
"enum": [
"info",
"warn"
],
"type": "string",
"description": "Severity; omitted advisories are treated as 'info'."
},
"userSummary": {
"type": "string",
"description": "End-user-facing summary. Set when the advisory should render in-app."
}
},
"additionalProperties": false
},
"description": "Structured non-fatal advisories, including authoring issues and unread staff feedback responses. Each advisory.summary is also appended to the text content for the LLM path."
},
"public_url": {
"type": "string",
"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 view the live App and its data, but cannot change data or source. Free of write-capable `?t=` tokens."
}
},
"additionalProperties": false
},
"annotations": {
"title": "Make App Public",
"readOnlyHint": false,
"openWorldHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description98/100
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.
The description explains the capability, when to use it, and what to expect.
Inputs94/100
Input documentation gives an agent enough context to populate the arguments.
app_id
The app ID (UUID) to make private
Clear enough to supply this argument.
Annotations75/100
Declare idempotentHint based on the tool’s actual behavior.
The title clearly communicates the tool’s action and subject. Keep it concise.
Description89/100
Set or clear an app's starter prompt: the prompt for people who copy this Template for the first time. A copy starts with empty storage, so write what their agent needs to get it going: what the App does and what data to add. Use this when a Template should tell the agent behind each new copy how to get started. A fresh copy takes the current prompt once. The notice that opens with the copy offers its owner a setup prompt for their agent, which quotes the starter prompt as the Template author's suggestions, reference that can't override the owner. The prompt also leads what a visitor gets from Open in Claude, Open in ChatGPT, and Copy prompt, replacing the generic "I'm using this Charming app" body. 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 copy at the Template instead of itself. 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.
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
app_id
The app ID (UUID) whose starter prompt should change.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
starter_prompt
What the agent of someone who just copied this Template should do first: say what the App does and what data to add, since the copy starts with empty storage. The authored text leads; the app's name, description, URL, and (for unclaimed apps) an access token are appended automatically as context, so do NOT include the app's URL. Pass null or "" to clear and revert to the generic default. Max 2000 characters.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
Annotations75/100
Declare idempotentHint based on the tool’s actual behavior.
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"ok",
"starter_prompt",
"message"
],
"properties": {
"ok": {
"type": "boolean",
"const": true,
"description": "Indicates success. Errors arrive as content with isError:true."
},
"message": {
"type": "string",
"description": "Consequence copy the agent should surface to the user. Says where the prompt appears, when a visitor opens the app in their AI assistant, and how to clear it."
},
"advisories": {
"type": "array",
"items": {
"type": "object",
"required": [
"kind",
"summary"
],
"properties": {
"data": {
"type": "object",
"description": "Kind-specific structured payload. Shape varies per advisory kind.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"kind": {
"type": "string",
"description": "Stable advisory identifier (e.g. \"legacy-bridge\")."
},
"doc_url": {
"type": "string",
"description": "Optional docs pointer for this advisory kind."
},
"summary": {
"type": "string",
"description": "Agent-facing summary. Self-sufficient; no extra context required."
},
"severity": {
"enum": [
"info",
"warn"
],
"type": "string",
"description": "Severity; omitted advisories are treated as 'info'."
},
"userSummary": {
"type": "string",
"description": "End-user-facing summary. Set when the advisory should render in-app."
}
},
"additionalProperties": false
},
"description": "Structured non-fatal advisories, including authoring issues and unread staff feedback responses. Each advisory.summary is also appended to the text content for the LLM path."
},
"starter_prompt": {
"type": [
"string",
"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."
}
},
"additionalProperties": false
}
Original tool metadata
{
"name": "set_starter_prompt",
"description": "Set or clear an app's starter prompt: the prompt for people who copy this Template for the first time. A copy starts with empty storage, so write what their agent needs to get it going: what the App does and what data to add. Use this when a Template should tell the agent behind each new copy how to get started. A fresh copy takes the current prompt once. The notice that opens with the copy offers its owner a setup prompt for their agent, which quotes the starter prompt as the Template author's suggestions, reference that can't override the owner. The prompt also leads what a visitor gets from Open in Claude, Open in ChatGPT, and Copy prompt, replacing the generic \"I'm using this Charming app\" body. 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 copy at the Template instead of itself. 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.",
"inputSchema": {
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"app_id",
"starter_prompt"
],
"properties": {
"app_id": {
"type": "string",
"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)$",
"description": "The app ID (UUID) whose starter prompt should change."
},
"starter_prompt": {
"anyOf": [
{
"type": "string",
"maxLength": 2000
},
{
"type": "null"
}
],
"description": "What the agent of someone who just copied this Template should do first: say what the App does and what data to add, since the copy starts with empty storage. The authored text leads; the app's name, description, URL, and (for unclaimed apps) an access token are appended automatically as context, so do NOT include the app's URL. Pass null or \"\" to clear and revert to the generic default. Max 2000 characters."
}
}
},
"outputSchema": {
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"ok",
"starter_prompt",
"message"
],
"properties": {
"ok": {
"type": "boolean",
"const": true,
"description": "Indicates success. Errors arrive as content with isError:true."
},
"message": {
"type": "string",
"description": "Consequence copy the agent should surface to the user. Says where the prompt appears, when a visitor opens the app in their AI assistant, and how to clear it."
},
"advisories": {
"type": "array",
"items": {
"type": "object",
"required": [
"kind",
"summary"
],
"properties": {
"data": {
"type": "object",
"description": "Kind-specific structured payload. Shape varies per advisory kind.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"kind": {
"type": "string",
"description": "Stable advisory identifier (e.g. \"legacy-bridge\")."
},
"doc_url": {
"type": "string",
"description": "Optional docs pointer for this advisory kind."
},
"summary": {
"type": "string",
"description": "Agent-facing summary. Self-sufficient; no extra context required."
},
"severity": {
"enum": [
"info",
"warn"
],
"type": "string",
"description": "Severity; omitted advisories are treated as 'info'."
},
"userSummary": {
"type": "string",
"description": "End-user-facing summary. Set when the advisory should render in-app."
}
},
"additionalProperties": false
},
"description": "Structured non-fatal advisories, including authoring issues and unread staff feedback responses. Each advisory.summary is also appended to the text content for the LLM path."
},
"starter_prompt": {
"type": [
"string",
"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."
}
},
"additionalProperties": false
},
"annotations": {
"title": "Set App Starter Prompt",
"readOnlyHint": false,
"openWorldHint": true,
"destructiveHint": true
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description97/100
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 has no access until the eligible signed-in account chooses Open app from the email or Charming Home; that action creates the durable grant before entering the app. 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; the eligible grant appears after they register and verify that email (an unknown handle still errors, since there is no address to reach). Revoke anytime with `unshare_app`.
The description explains the capability, when to use it, and what to expect.
Inputs83/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
app_id
The app ID (UUID) to share
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
grantee
The invitee's Charming handle (e.g. `ada` or `@ada`) or account email
Clear enough to supply this argument.
role
The access level: `collaborator` (default — edit and run), `end-user` (run + write data, not edit source), or `viewer` (read-only)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
Annotations75/100
Declare idempotentHint based on the tool’s actual behavior.
{
"title": "Share App with a User",
"readOnlyHint": false,
"openWorldHint": true,
"destructiveHint": true
}
Output schema91/100
The output schema explains the structured result.
View output schema
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"ok",
"status",
"grantee",
"message"
],
"properties": {
"ok": {
"type": "boolean",
"const": true,
"description": "Indicates success. Errors arrive as content with isError:true."
},
"status": {
"enum": [
"pending",
"invited",
"updated"
],
"type": "string",
"description": "Sharing creates an invitation, not access. `pending` = the grantee has a Charming account and gets access only when that account chooses Open app. `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)."
},
"grantee": {
"type": "string",
"description": "The resolved grantee (handle when set, else email) the invitation was sent to."
},
"message": {
"type": "string",
"description": "Consequence copy the agent should surface verbatim: the invitee has no access until they choose Open app, and what their role permits after the durable grant succeeds."
},
"advisories": {
"type": "array",
"items": {
"type": "object",
"required": [
"kind",
"summary"
],
"properties": {
"data": {
"type": "object",
"description": "Kind-specific structured payload. Shape varies per advisory kind.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"kind": {
"type": "string",
"description": "Stable advisory identifier (e.g. \"legacy-bridge\")."
},
"doc_url": {
"type": "string",
"description": "Optional docs pointer for this advisory kind."
},
"summary": {
"type": "string",
"description": "Agent-facing summary. Self-sufficient; no extra context required."
},
"severity": {
"enum": [
"info",
"warn"
],
"type": "string",
"description": "Severity; omitted advisories are treated as 'info'."
},
"userSummary": {
"type": "string",
"description": "End-user-facing summary. Set when the advisory should render in-app."
}
},
"additionalProperties": false
},
"description": "Structured non-fatal advisories, including authoring issues and unread staff feedback responses. Each advisory.summary is also appended to the text content for the LLM path."
}
},
"additionalProperties": false
}
Original tool metadata
{
"name": "share_app",
"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 has no access until the eligible signed-in account chooses Open app from the email or Charming Home; that action creates the durable grant before entering the app. 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; the eligible grant appears after they register and verify that email (an unknown handle still errors, since there is no address to reach). Revoke anytime with `unshare_app`.",
"inputSchema": {
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"app_id",
"grantee"
],
"properties": {
"role": {
"enum": [
"collaborator",
"end-user",
"viewer"
],
"type": "string",
"description": "The access level: `collaborator` (default — edit and run), `end-user` (run + write data, not edit source), or `viewer` (read-only)"
},
"app_id": {
"type": "string",
"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)$",
"description": "The app ID (UUID) to share"
},
"grantee": {
"type": "string",
"minLength": 1,
"description": "The invitee's Charming handle (e.g. `ada` or `@ada`) or account email"
}
}
},
"outputSchema": {
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"ok",
"status",
"grantee",
"message"
],
"properties": {
"ok": {
"type": "boolean",
"const": true,
"description": "Indicates success. Errors arrive as content with isError:true."
},
"status": {
"enum": [
"pending",
"invited",
"updated"
],
"type": "string",
"description": "Sharing creates an invitation, not access. `pending` = the grantee has a Charming account and gets access only when that account chooses Open app. `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)."
},
"grantee": {
"type": "string",
"description": "The resolved grantee (handle when set, else email) the invitation was sent to."
},
"message": {
"type": "string",
"description": "Consequence copy the agent should surface verbatim: the invitee has no access until they choose Open app, and what their role permits after the durable grant succeeds."
},
"advisories": {
"type": "array",
"items": {
"type": "object",
"required": [
"kind",
"summary"
],
"properties": {
"data": {
"type": "object",
"description": "Kind-specific structured payload. Shape varies per advisory kind.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"kind": {
"type": "string",
"description": "Stable advisory identifier (e.g. \"legacy-bridge\")."
},
"doc_url": {
"type": "string",
"description": "Optional docs pointer for this advisory kind."
},
"summary": {
"type": "string",
"description": "Agent-facing summary. Self-sufficient; no extra context required."
},
"severity": {
"enum": [
"info",
"warn"
],
"type": "string",
"description": "Severity; omitted advisories are treated as 'info'."
},
"userSummary": {
"type": "string",
"description": "End-user-facing summary. Set when the advisory should render in-app."
}
},
"additionalProperties": false
},
"description": "Structured non-fatal advisories, including authoring issues and unread staff feedback responses. Each advisory.summary is also appended to the text content for the LLM path."
}
},
"additionalProperties": false
},
"annotations": {
"title": "Share App with a User",
"readOnlyHint": false,
"openWorldHint": true,
"destructiveHint": true
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description94/100
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.
The description explains the capability, when to use it, and what to expect.
Inputs93/100
Input documentation gives an agent enough context to populate the arguments.
app_id
The app ID (UUID) to revoke access on
Clear enough to supply this argument.
grantee
The grantee's Charming handle or account email
Clear enough to supply this argument.
Annotations75/100
Declare idempotentHint based on the tool’s actual behavior.
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"ok",
"removed"
],
"properties": {
"ok": {
"type": "boolean",
"const": true,
"description": "Indicates success. Errors arrive as content with isError:true."
},
"removed": {
"type": "boolean",
"description": "True when a pending invitation or active grant was removed; false when there was nothing to remove (idempotent no-op)."
},
"advisories": {
"type": "array",
"items": {
"type": "object",
"required": [
"kind",
"summary"
],
"properties": {
"data": {
"type": "object",
"description": "Kind-specific structured payload. Shape varies per advisory kind.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"kind": {
"type": "string",
"description": "Stable advisory identifier (e.g. \"legacy-bridge\")."
},
"doc_url": {
"type": "string",
"description": "Optional docs pointer for this advisory kind."
},
"summary": {
"type": "string",
"description": "Agent-facing summary. Self-sufficient; no extra context required."
},
"severity": {
"enum": [
"info",
"warn"
],
"type": "string",
"description": "Severity; omitted advisories are treated as 'info'."
},
"userSummary": {
"type": "string",
"description": "End-user-facing summary. Set when the advisory should render in-app."
}
},
"additionalProperties": false
},
"description": "Structured non-fatal advisories, including authoring issues and unread staff feedback responses. Each advisory.summary is also appended to the text content for the LLM path."
}
},
"additionalProperties": false
}
Original tool metadata
{
"name": "unshare_app",
"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.",
"inputSchema": {
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"app_id",
"grantee"
],
"properties": {
"app_id": {
"type": "string",
"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)$",
"description": "The app ID (UUID) to revoke access on"
},
"grantee": {
"type": "string",
"minLength": 1,
"description": "The grantee's Charming handle or account email"
}
}
},
"outputSchema": {
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"ok",
"removed"
],
"properties": {
"ok": {
"type": "boolean",
"const": true,
"description": "Indicates success. Errors arrive as content with isError:true."
},
"removed": {
"type": "boolean",
"description": "True when a pending invitation or active grant was removed; false when there was nothing to remove (idempotent no-op)."
},
"advisories": {
"type": "array",
"items": {
"type": "object",
"required": [
"kind",
"summary"
],
"properties": {
"data": {
"type": "object",
"description": "Kind-specific structured payload. Shape varies per advisory kind.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"kind": {
"type": "string",
"description": "Stable advisory identifier (e.g. \"legacy-bridge\")."
},
"doc_url": {
"type": "string",
"description": "Optional docs pointer for this advisory kind."
},
"summary": {
"type": "string",
"description": "Agent-facing summary. Self-sufficient; no extra context required."
},
"severity": {
"enum": [
"info",
"warn"
],
"type": "string",
"description": "Severity; omitted advisories are treated as 'info'."
},
"userSummary": {
"type": "string",
"description": "End-user-facing summary. Set when the advisory should render in-app."
}
},
"additionalProperties": false
},
"description": "Structured non-fatal advisories, including authoring issues and unread staff feedback responses. Each advisory.summary is also appended to the text content for the LLM path."
}
},
"additionalProperties": false
},
"annotations": {
"title": "Revoke App Share",
"readOnlyHint": false,
"openWorldHint": true,
"destructiveHint": true
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description87/100
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).
The description explains the capability, when to use it, and what to expect.
Inputs96/100
Input documentation gives an agent enough context to populate the arguments.
app_id
The app ID (UUID) to list shares for
Clear enough to supply this argument.
Annotations75/100
Declare idempotentHint based on the tool’s actual behavior.
The title clearly communicates the tool’s action and subject. Keep it concise.
Description85/100
Use this when the user wants an app op to run on its own, on a timer — no chat needs to be open. Schedules one of an app's declared ops as a Routine. Interval is `hourly`, `daily`, or `weekly` (no cron syntax). The target op is invoked with empty input (`{}`), so it must not declare any required input fields. Rejects a duplicate Routine on the same (app, op) pair, and enforces the per-app / per-owner caps of the app owner's plan.
Explain mutations, irreversible effects, or prerequisites that matter before calling this tool.
Inputs87/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
app_id
The app ID (UUID) to schedule a Routine on
Clear enough to supply this argument.
op
The declared op to invoke on each run
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
interval
How often the Routine runs
Clear enough to supply this argument.
Annotations75/100
Declare idempotentHint based on the tool’s actual behavior.
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"ok",
"routine"
],
"properties": {
"ok": {
"type": "boolean",
"const": true,
"description": "Indicates success. Errors arrive as content with isError:true."
},
"routine": {
"type": "object",
"required": [
"id",
"app_id",
"op",
"interval",
"enabled",
"disabled_reason",
"next_run_at",
"last_run_at",
"last_outcome",
"last_error",
"consecutive_failures"
],
"properties": {
"id": {
"type": "string",
"description": "Public Routine id, `routine_<uuid>` form. Use this with update_routine/delete_routine."
},
"op": {
"type": "string",
"description": "The target op this Routine invokes with empty input."
},
"app_id": {
"type": "string",
"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)$",
"description": "UUID of the app this Routine runs on."
},
"enabled": {
"type": "boolean",
"description": "`false` when the Routine is disabled (owner-disabled or auto-disabled)."
},
"interval": {
"enum": [
"hourly",
"daily",
"weekly"
],
"type": "string",
"description": "How often the Routine runs."
},
"last_error": {
"anyOf": [
{
"type": "object",
"required": [
"kind",
"message"
],
"properties": {
"kind": {
"type": "string"
},
"message": {
"type": "string"
}
},
"additionalProperties": false
},
{
"type": "null"
}
],
"description": "Classified failure detail from the most recent failed tick, reusing the sandbox executor's failure classification, truncated with the stack always dropped. `null` on success or if never run."
},
"last_run_at": {
"type": [
"string",
"null"
],
"description": "ISO-8601 timestamp of the most recent tick. `null` if this Routine has never run."
},
"next_run_at": {
"type": [
"string",
"null"
],
"description": "ISO-8601 timestamp of the next scheduled run. `null` while the Routine is disabled — the underlying schedule is preserved and reappears here once re-enabled, with no recomputation."
},
"last_outcome": {
"type": [
"string",
"null"
],
"description": "Result of the most recent tick (`success` or `failure`). `null` if this Routine has never run."
},
"disabled_reason": {
"anyOf": [
{
"enum": [
"owner",
"auto"
],
"type": "string"
},
{
"type": "null"
}
],
"description": "Why the Routine is disabled: `owner` (manually) or `auto` (5 consecutive failures). `null` when enabled."
},
"consecutive_failures": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0,
"description": "Consecutive failed ticks since the last success. Resets to 0 on success; the Routine auto-disables at 5."
}
},
"additionalProperties": false
},
"advisories": {
"type": "array",
"items": {
"type": "object",
"required": [
"kind",
"summary"
],
"properties": {
"data": {
"type": "object",
"description": "Kind-specific structured payload. Shape varies per advisory kind.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"kind": {
"type": "string",
"description": "Stable advisory identifier (e.g. \"legacy-bridge\")."
},
"doc_url": {
"type": "string",
"description": "Optional docs pointer for this advisory kind."
},
"summary": {
"type": "string",
"description": "Agent-facing summary. Self-sufficient; no extra context required."
},
"severity": {
"enum": [
"info",
"warn"
],
"type": "string",
"description": "Severity; omitted advisories are treated as 'info'."
},
"userSummary": {
"type": "string",
"description": "End-user-facing summary. Set when the advisory should render in-app."
}
},
"additionalProperties": false
},
"description": "Structured non-fatal advisories, including authoring issues and unread staff feedback responses. Each advisory.summary is also appended to the text content for the LLM path."
}
},
"additionalProperties": false
}
Original tool metadata
{
"name": "create_routine",
"description": "Use this when the user wants an app op to run on its own, on a timer — no chat needs to be open. Schedules one of an app's declared ops as a Routine. Interval is `hourly`, `daily`, or `weekly` (no cron syntax). The target op is invoked with empty input (`{}`), so it must not declare any required input fields. Rejects a duplicate Routine on the same (app, op) pair, and enforces the per-app / per-owner caps of the app owner's plan.",
"inputSchema": {
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"app_id",
"op",
"interval"
],
"properties": {
"op": {
"type": "string",
"minLength": 1,
"description": "The declared op to invoke on each run"
},
"app_id": {
"type": "string",
"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)$",
"description": "The app ID (UUID) to schedule a Routine on"
},
"interval": {
"enum": [
"hourly",
"daily",
"weekly"
],
"type": "string",
"description": "How often the Routine runs"
}
}
},
"outputSchema": {
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"ok",
"routine"
],
"properties": {
"ok": {
"type": "boolean",
"const": true,
"description": "Indicates success. Errors arrive as content with isError:true."
},
"routine": {
"type": "object",
"required": [
"id",
"app_id",
"op",
"interval",
"enabled",
"disabled_reason",
"next_run_at",
"last_run_at",
"last_outcome",
"last_error",
"consecutive_failures"
],
"properties": {
"id": {
"type": "string",
"description": "Public Routine id, `routine_<uuid>` form. Use this with update_routine/delete_routine."
},
"op": {
"type": "string",
"description": "The target op this Routine invokes with empty input."
},
"app_id": {
"type": "string",
"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)$",
"description": "UUID of the app this Routine runs on."
},
"enabled": {
"type": "boolean",
"description": "`false` when the Routine is disabled (owner-disabled or auto-disabled)."
},
"interval": {
"enum": [
"hourly",
"daily",
"weekly"
],
"type": "string",
"description": "How often the Routine runs."
},
"last_error": {
"anyOf": [
{
"type": "object",
"required": [
"kind",
"message"
],
"properties": {
"kind": {
"type": "string"
},
"message": {
"type": "string"
}
},
"additionalProperties": false
},
{
"type": "null"
}
],
"description": "Classified failure detail from the most recent failed tick, reusing the sandbox executor's failure classification, truncated with the stack always dropped. `null` on success or if never run."
},
"last_run_at": {
"type": [
"string",
"null"
],
"description": "ISO-8601 timestamp of the most recent tick. `null` if this Routine has never run."
},
"next_run_at": {
"type": [
"string",
"null"
],
"description": "ISO-8601 timestamp of the next scheduled run. `null` while the Routine is disabled — the underlying schedule is preserved and reappears here once re-enabled, with no recomputation."
},
"last_outcome": {
"type": [
"string",
"null"
],
"description": "Result of the most recent tick (`success` or `failure`). `null` if this Routine has never run."
},
"disabled_reason": {
"anyOf": [
{
"enum": [
"owner",
"auto"
],
"type": "string"
},
{
"type": "null"
}
],
"description": "Why the Routine is disabled: `owner` (manually) or `auto` (5 consecutive failures). `null` when enabled."
},
"consecutive_failures": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0,
"description": "Consecutive failed ticks since the last success. Resets to 0 on success; the Routine auto-disables at 5."
}
},
"additionalProperties": false
},
"advisories": {
"type": "array",
"items": {
"type": "object",
"required": [
"kind",
"summary"
],
"properties": {
"data": {
"type": "object",
"description": "Kind-specific structured payload. Shape varies per advisory kind.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"kind": {
"type": "string",
"description": "Stable advisory identifier (e.g. \"legacy-bridge\")."
},
"doc_url": {
"type": "string",
"description": "Optional docs pointer for this advisory kind."
},
"summary": {
"type": "string",
"description": "Agent-facing summary. Self-sufficient; no extra context required."
},
"severity": {
"enum": [
"info",
"warn"
],
"type": "string",
"description": "Severity; omitted advisories are treated as 'info'."
},
"userSummary": {
"type": "string",
"description": "End-user-facing summary. Set when the advisory should render in-app."
}
},
"additionalProperties": false
},
"description": "Structured non-fatal advisories, including authoring issues and unread staff feedback responses. Each advisory.summary is also appended to the text content for the LLM path."
}
},
"additionalProperties": false
},
"annotations": {
"title": "Create Routine",
"readOnlyHint": false,
"openWorldHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description80/100
Use this to change a Routine's `interval`, or enable/disable it. Re-enable is `{enabled: true}` — there is no separate re-enable action. Re-enabling also resets the consecutive-failure counter.
The description explains the capability, when to use it, and what to expect.
Inputs83/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
routine_id
The Routine id (`routine_<uuid>` form) to update
Clear enough to supply this argument.
interval
New schedule interval
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
enabled
`true` re-enables the Routine; `false` disables it (owner-disabled)
Explain what happens when this optional value is omitted.
Annotations75/100
Declare idempotentHint based on the tool’s actual behavior.
The title clearly communicates the tool’s action and subject. Keep it concise.
Description99/100
Use this when an external service (Zapier, a payment provider, a partner backend) needs to call one of an app's ops directly. Declares a Webhook on a declared op and returns its delivery URL, a one-time `chrm_hook_*` secret, and the op's input and output schemas, so you can hand the sender everything it needs. The sender POSTs a body matching the input schema to the URL with `Authorization: Bearer <secret>` (or `?key=<secret>`) and gets the op's result back. The body can be JSON or a form (`multipart/form-data` or url-encoded): text fields become the input, converted to the schema's types, and file parts are dropped. Rejects a duplicate Webhook on the same (app, op) pair and enforces per-app / per-owner caps.
The description explains the capability, when to use it, and what to expect.
Inputs83/100
Clarify units, where to obtain this value, or conventions not already expressed in the schema.
app_id
The app ID (UUID) to declare a Webhook on
Clarify units, where to obtain this value, or conventions not already expressed in the schema.
op
The declared op an inbound delivery invokes
Clear enough to supply this argument.
name
A label for the owner's list; omitted means the op's own title or description
Clear enough to supply this argument.
Annotations75/100
Declare idempotentHint based on the tool’s actual behavior.
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"ok",
"webhook",
"secret",
"target"
],
"properties": {
"ok": {
"type": "boolean",
"const": true,
"description": "Indicates success. Errors arrive as content with isError:true."
},
"secret": {
"type": "string",
"description": "The `chrm_hook_*` secret the sender presents. Shown once; only its hash is stored."
},
"target": {
"type": "object",
"required": [
"op",
"input_schema",
"output_schema"
],
"properties": {
"op": {
"type": "string"
},
"input_schema": {
"anyOf": [
{
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
{
"type": "null"
}
]
},
"output_schema": {
"anyOf": [
{
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
{
"type": "null"
}
]
}
},
"description": "The target op's contract: the JSON body a sender must POST and the value it gets back, read from the app's active code.",
"additionalProperties": false
},
"webhook": {
"type": "object",
"required": [
"id",
"app_id",
"op",
"name",
"url",
"secret_prefix",
"enabled",
"disabled_reason",
"last_run_at",
"last_outcome",
"last_error",
"consecutive_failures"
],
"properties": {
"id": {
"type": "string",
"description": "Public Webhook id, `webhook_<uuid>` form. Use this with update_webhook/delete_webhook."
},
"op": {
"type": "string",
"description": "The declared op an inbound delivery invokes."
},
"url": {
"type": "string",
"description": "The delivery URL an external sender POSTs to."
},
"name": {
"type": "string",
"description": "The owner's label for the Webhook; defaults from the op."
},
"app_id": {
"type": "string",
"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)$",
"description": "UUID of the app this Webhook targets."
},
"enabled": {
"type": "boolean",
"description": "`false` when the owner disabled the Webhook."
},
"last_error": {
"anyOf": [
{
"type": "object",
"required": [
"kind",
"message"
],
"properties": {
"kind": {
"type": "string"
},
"message": {
"type": "string"
}
},
"additionalProperties": false
},
{
"type": "null"
}
],
"description": "Classified failure detail from the most recent failed delivery. `null` on success or if never delivered."
},
"last_run_at": {
"type": [
"string",
"null"
],
"description": "ISO-8601 timestamp of the most recent delivery. `null` if never delivered."
},
"last_outcome": {
"type": [
"string",
"null"
],
"description": "Result of the most recent delivery (`success`, `failure`, or `deferred`). `null` if never delivered."
},
"secret_prefix": {
"type": "string",
"description": "First characters of the secret, for matching a stored copy by eye."
},
"disabled_reason": {
"anyOf": [
{
"enum": [
"owner"
],
"type": "string"
},
{
"type": "null"
}
],
"description": "`owner` when disabled manually; `null` when enabled. Webhooks never auto-disable."
},
"consecutive_failures": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0,
"description": "Consecutive failed deliveries since the last success. Diagnostic only; nothing disables at a threshold."
}
},
"additionalProperties": false
},
"advisories": {
"type": "array",
"items": {
"type": "object",
"required": [
"kind",
"summary"
],
"properties": {
"data": {
"type": "object",
"description": "Kind-specific structured payload. Shape varies per advisory kind.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"kind": {
"type": "string",
"description": "Stable advisory identifier (e.g. \"legacy-bridge\")."
},
"doc_url": {
"type": "string",
"description": "Optional docs pointer for this advisory kind."
},
"summary": {
"type": "string",
"description": "Agent-facing summary. Self-sufficient; no extra context required."
},
"severity": {
"enum": [
"info",
"warn"
],
"type": "string",
"description": "Severity; omitted advisories are treated as 'info'."
},
"userSummary": {
"type": "string",
"description": "End-user-facing summary. Set when the advisory should render in-app."
}
},
"additionalProperties": false
},
"description": "Structured non-fatal advisories, including authoring issues and unread staff feedback responses. Each advisory.summary is also appended to the text content for the LLM path."
}
},
"additionalProperties": false
}
Original tool metadata
{
"name": "create_webhook",
"description": "Use this when an external service (Zapier, a payment provider, a partner backend) needs to call one of an app's ops directly. Declares a Webhook on a declared op and returns its delivery URL, a one-time `chrm_hook_*` secret, and the op's input and output schemas, so you can hand the sender everything it needs. The sender POSTs a body matching the input schema to the URL with `Authorization: Bearer <secret>` (or `?key=<secret>`) and gets the op's result back. The body can be JSON or a form (`multipart/form-data` or url-encoded): text fields become the input, converted to the schema's types, and file parts are dropped. Rejects a duplicate Webhook on the same (app, op) pair and enforces per-app / per-owner caps.",
"inputSchema": {
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"app_id",
"op"
],
"properties": {
"op": {
"type": "string",
"minLength": 1,
"description": "The declared op an inbound delivery invokes"
},
"name": {
"type": "string",
"maxLength": 80,
"description": "A label for the owner's list; omitted means the op's own title or description"
},
"app_id": {
"type": "string",
"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)$",
"description": "The app ID (UUID) to declare a Webhook on"
}
}
},
"outputSchema": {
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"ok",
"webhook",
"secret",
"target"
],
"properties": {
"ok": {
"type": "boolean",
"const": true,
"description": "Indicates success. Errors arrive as content with isError:true."
},
"secret": {
"type": "string",
"description": "The `chrm_hook_*` secret the sender presents. Shown once; only its hash is stored."
},
"target": {
"type": "object",
"required": [
"op",
"input_schema",
"output_schema"
],
"properties": {
"op": {
"type": "string"
},
"input_schema": {
"anyOf": [
{
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
{
"type": "null"
}
]
},
"output_schema": {
"anyOf": [
{
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
{
"type": "null"
}
]
}
},
"description": "The target op's contract: the JSON body a sender must POST and the value it gets back, read from the app's active code.",
"additionalProperties": false
},
"webhook": {
"type": "object",
"required": [
"id",
"app_id",
"op",
"name",
"url",
"secret_prefix",
"enabled",
"disabled_reason",
"last_run_at",
"last_outcome",
"last_error",
"consecutive_failures"
],
"properties": {
"id": {
"type": "string",
"description": "Public Webhook id, `webhook_<uuid>` form. Use this with update_webhook/delete_webhook."
},
"op": {
"type": "string",
"description": "The declared op an inbound delivery invokes."
},
"url": {
"type": "string",
"description": "The delivery URL an external sender POSTs to."
},
"name": {
"type": "string",
"description": "The owner's label for the Webhook; defaults from the op."
},
"app_id": {
"type": "string",
"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)$",
"description": "UUID of the app this Webhook targets."
},
"enabled": {
"type": "boolean",
"description": "`false` when the owner disabled the Webhook."
},
"last_error": {
"anyOf": [
{
"type": "object",
"required": [
"kind",
"message"
],
"properties": {
"kind": {
"type": "string"
},
"message": {
"type": "string"
}
},
"additionalProperties": false
},
{
"type": "null"
}
],
"description": "Classified failure detail from the most recent failed delivery. `null` on success or if never delivered."
},
"last_run_at": {
"type": [
"string",
"null"
],
"description": "ISO-8601 timestamp of the most recent delivery. `null` if never delivered."
},
"last_outcome": {
"type": [
"string",
"null"
],
"description": "Result of the most recent delivery (`success`, `failure`, or `deferred`). `null` if never delivered."
},
"secret_prefix": {
"type": "string",
"description": "First characters of the secret, for matching a stored copy by eye."
},
"disabled_reason": {
"anyOf": [
{
"enum": [
"owner"
],
"type": "string"
},
{
"type": "null"
}
],
"description": "`owner` when disabled manually; `null` when enabled. Webhooks never auto-disable."
},
"consecutive_failures": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0,
"description": "Consecutive failed deliveries since the last success. Diagnostic only; nothing disables at a threshold."
}
},
"additionalProperties": false
},
"advisories": {
"type": "array",
"items": {
"type": "object",
"required": [
"kind",
"summary"
],
"properties": {
"data": {
"type": "object",
"description": "Kind-specific structured payload. Shape varies per advisory kind.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"kind": {
"type": "string",
"description": "Stable advisory identifier (e.g. \"legacy-bridge\")."
},
"doc_url": {
"type": "string",
"description": "Optional docs pointer for this advisory kind."
},
"summary": {
"type": "string",
"description": "Agent-facing summary. Self-sufficient; no extra context required."
},
"severity": {
"enum": [
"info",
"warn"
],
"type": "string",
"description": "Severity; omitted advisories are treated as 'info'."
},
"userSummary": {
"type": "string",
"description": "End-user-facing summary. Set when the advisory should render in-app."
}
},
"additionalProperties": false
},
"description": "Structured non-fatal advisories, including authoring issues and unread staff feedback responses. Each advisory.summary is also appended to the text content for the LLM path."
}
},
"additionalProperties": false
},
"annotations": {
"title": "Create Webhook",
"readOnlyHint": false,
"openWorldHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description86/100
Use this to rename a Webhook, enable/disable it, or rotate its secret. Re-enable is `{enabled: true}` and resets the consecutive-failure counter. `{rotate_secret: true}` replaces the secret immediately and returns the new one once; the old secret stops working.
Explain mutations, irreversible effects, or prerequisites that matter before calling this tool.
Inputs88/100
Explain what happens when this optional value is omitted.
webhook_id
The Webhook id (`webhook_<uuid>` form) to update
Clear enough to supply this argument.
enabled
`true` re-enables the Webhook; `false` disables it (owner-disabled)
Explain what happens when this optional value is omitted.
rotate_secret
`true` replaces the secret and returns the new one once
Clear enough to supply this argument.
name
A new label for the Webhook
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
Annotations75/100
Declare idempotentHint based on the tool’s actual behavior.
The title clearly communicates the tool’s action and subject. Keep it concise.
Description85/100
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.
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
app_id
The app ID (UUID)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
op
Operation name, routed to /api/:op inside the app module
Clear enough to supply this argument.
params
Optional JSON params
Explain the meaning of this value, not just its name.
Annotations75/100
Declare idempotentHint based on the tool’s actual behavior.
The title clearly communicates the tool’s action and subject. Keep it concise.
Description85/100
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.
The description explains the capability, when to use it, and what to expect.
Inputs67/100
Clarify units, where to obtain this value, or conventions not already expressed in the schema.
app_id
The app ID (UUID)
Clarify units, where to obtain this value, or conventions not already expressed in the schema.
op
Operation name, routed to /api/:op inside the app module
Clear enough to supply this argument.
params
Optional JSON params
Explain the meaning of this value, not just its name.
Annotations75/100
Declare idempotentHint based on the tool’s actual behavior.
The title clearly communicates the tool’s action and subject. Keep it concise.
Description77/100
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 have app:run access.
The description explains the capability, when to use it, and what to expect.
Inputs44/100
5 of 9 inputs have descriptions. Add descriptions to the missing inputs, including where to obtain IDs and how to supply values.
app_id
The app ID (UUID) the feedback is attached to.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
category
Feedback category. One of: bug, crash, enhancement, praise, other. Pass "crash" only when crash_data carries the captured error.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
text
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.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
The title clearly communicates the tool’s action and subject. Keep it concise.
Description84/100
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.
Describe the returned information so the agent knows whether it will answer the request.
Inputs85/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
app_id
Optional app ID (UUID) to restrict to. When omitted, returns feedback across every app the caller owns.
Clear enough to supply this argument.
category
Optional category filter. One of: bug, crash, enhancement, praise, other.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
since
Optional inclusive lower bound on `created_at` as an ISO-8601 timestamp.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
limit
Max rows to return (1-50, default 20).
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
cursor
Opaque cursor from a prior call's `cursor` to fetch the next page.
Clear enough to supply this argument.
Annotations75/100
Declare idempotentHint based on the tool’s actual behavior.
The title clearly communicates the tool’s action and subject. Keep it concise.
Description96/100
Fetch unread Charming staff responses to feedback submitted by this authenticated account. Use this when a feedback-responses-available advisory appears. Responses are ordered oldest-first. Reading does not acknowledge them; call acknowledge_feedback_responses with the returned response IDs after processing them.
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
limit
Max responses to return (1-50, default 20).
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
cursor
Opaque cursor returned by the prior page.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
Annotations75/100
Declare idempotentHint based on the tool’s actual behavior.
The title clearly communicates the tool’s action and subject. Keep it concise.
Description93/100
Acknowledge feedback responses after the agent has processed them. Use this to stop processed responses from appearing in advisories and list_feedback_responses. Acknowledgement is durable and idempotent.
The description explains the capability, when to use it, and what to expect.
Inputs98/100
Input documentation gives an agent enough context to populate the arguments.
response_ids
Response UUIDs returned by list_feedback_responses.
The title clearly communicates the tool’s action and subject. Keep it concise.
Description94/100
Read Charming documentation without web access. Call without arguments to get the docs index, then pass a page path from that index (for example capabilities/data-storage.md). Use this when building an app or looking up the runtime, API, or capability reference. Returns Markdown in chunks of up to 20,000 characters; pass next_offset as offset with the same path to continue. For the full authoring reference, use llms-full.txt. Only public Charming docs can be read. Limited to 60 reads per caller per minute.
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
path
Path relative to /docs/, such as capabilities/data-storage.md or llms-full.txt. A copied docs/ prefix is accepted. Omitted or empty paths default to llms.txt (the docs index). Page slugs without .md also work. No full URLs, query strings, or fragments.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
offset
Character offset returned as next_offset by the prior read. Defaults to 0.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
Annotations75/100
Declare idempotentHint based on the tool’s actual behavior.
{
"name": "read_docs",
"description": "Read Charming documentation without web access. Call without arguments to get the docs index, then pass a page path from that index (for example capabilities/data-storage.md). Use this when building an app or looking up the runtime, API, or capability reference. Returns Markdown in chunks of up to 20,000 characters; pass next_offset as offset with the same path to continue. For the full authoring reference, use llms-full.txt. Only public Charming docs can be read. Limited to 60 reads per caller per minute.",
"inputSchema": {
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"path": {
"type": "string",
"maxLength": 300,
"description": "Path relative to /docs/, such as capabilities/data-storage.md or llms-full.txt. A copied docs/ prefix is accepted. Omitted or empty paths default to llms.txt (the docs index). Page slugs without .md also work. No full URLs, query strings, or fragments."
},
"offset": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0,
"description": "Character offset returned as next_offset by the prior read. Defaults to 0."
}
}
},
"outputSchema": {
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"path",
"url",
"markdown",
"next_offset"
],
"properties": {
"url": {
"type": "string"
},
"path": {
"type": "string"
},
"markdown": {
"type": "string"
},
"next_offset": {
"type": [
"number",
"null"
]
}
},
"additionalProperties": false
},
"annotations": {
"title": "Read Docs",
"readOnlyHint": true,
"openWorldHint": false,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description96/100
Search the public Charming Template directory by keyword. Use this to check for an existing Template before calling create_app, or when the user asks what Templates exist. Returns the resolved listing title, summary, Markdown detail, category, cover, starter prompt, creator, builder count, canonical Template page, and browser copy URL. The copy URL starts browser navigation where the user chooses to copy and signs in before owned creation; it is not an MCP or REST write. After browser creation, find the copy with list_apps or GET /app and read it with get_app_source or GET /app/:id/source.
The description explains the capability, when to use it, and what to expect.
Inputs83/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
query
Case-insensitive match against listing title, summary, detail, category, and starter prompt.
Clear enough to supply this argument.
sort
`builders` ranks by builders reached; `recent` (default) is newest-listed first.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
limit
Max templates to return (1-100).
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
Annotations75/100
Declare idempotentHint based on the tool’s actual behavior.
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"ok",
"templates"
],
"properties": {
"ok": {
"type": "boolean",
"const": true,
"description": "Indicates success. Errors arrive as content with isError:true."
},
"templates": {
"type": "array",
"items": {
"type": "object",
"required": [
"displayName",
"description",
"contentMarkdown",
"category",
"cover",
"starterPrompt",
"copyUrl",
"templatePageUrl",
"builders",
"creatorHandle"
],
"properties": {
"cover": {
"anyOf": [
{
"type": "object",
"required": [
"url",
"alt",
"caption"
],
"properties": {
"alt": {
"type": "string"
},
"url": {
"type": "string"
},
"caption": {
"type": [
"string",
"null"
]
}
},
"additionalProperties": false
},
{
"type": "null"
}
],
"description": "The Template's structured cover image, when set."
},
"copyUrl": {
"type": "string",
"description": "The app's own live URL. It starts browser navigation where a non-owner can choose to copy and must sign in before Charming creates the owned App. It is not an MCP or REST write. After creation, use list_apps or GET /app to find the copy, then get_app_source or GET /app/<id>/source to read it. Free of write-capable `?t=` tokens."
},
"builders": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991,
"description": "Distinct accounts that own a copy of this template."
},
"category": {
"type": [
"string",
"null"
],
"description": "The Template's category, when set."
},
"description": {
"type": [
"string",
"null"
],
"description": "The creator's search-friendly summary."
},
"displayName": {
"type": "string",
"description": "Human-readable name."
},
"creatorHandle": {
"type": "string",
"description": "The template's creator handle."
},
"starterPrompt": {
"type": [
"string",
"null"
],
"description": "The prompt a fresh copy starts with, if the creator set one."
},
"contentMarkdown": {
"type": [
"string",
"null"
],
"description": "The creator's longer Template detail in safe-subset Markdown."
},
"templatePageUrl": {
"type": "string",
"description": "The indexable `/templates/<handle>/<app-name>` page for this template."
}
},
"additionalProperties": false
},
"description": "Listed templates matching the query, most relevant first per `sort`."
},
"advisories": {
"type": "array",
"items": {
"type": "object",
"required": [
"kind",
"summary"
],
"properties": {
"data": {
"type": "object",
"description": "Kind-specific structured payload. Shape varies per advisory kind.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"kind": {
"type": "string",
"description": "Stable advisory identifier (e.g. \"legacy-bridge\")."
},
"doc_url": {
"type": "string",
"description": "Optional docs pointer for this advisory kind."
},
"summary": {
"type": "string",
"description": "Agent-facing summary. Self-sufficient; no extra context required."
},
"severity": {
"enum": [
"info",
"warn"
],
"type": "string",
"description": "Severity; omitted advisories are treated as 'info'."
},
"userSummary": {
"type": "string",
"description": "End-user-facing summary. Set when the advisory should render in-app."
}
},
"additionalProperties": false
},
"description": "Structured non-fatal advisories, including authoring issues and unread staff feedback responses. Each advisory.summary is also appended to the text content for the LLM path."
}
},
"additionalProperties": false
}
Original tool metadata
{
"name": "search_templates",
"description": "Search the public Charming Template directory by keyword. Use this to check for an existing Template before calling create_app, or when the user asks what Templates exist. Returns the resolved listing title, summary, Markdown detail, category, cover, starter prompt, creator, builder count, canonical Template page, and browser copy URL. The copy URL starts browser navigation where the user chooses to copy and signs in before owned creation; it is not an MCP or REST write. After browser creation, find the copy with list_apps or GET /app and read it with get_app_source or GET /app/:id/source.",
"inputSchema": {
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"sort": {
"enum": [
"builders",
"recent"
],
"type": "string",
"description": "`builders` ranks by builders reached; `recent` (default) is newest-listed first."
},
"limit": {
"type": "integer",
"maximum": 100,
"minimum": 1,
"description": "Max templates to return (1-100)."
},
"query": {
"type": "string",
"description": "Case-insensitive match against listing title, summary, detail, category, and starter prompt."
}
}
},
"outputSchema": {
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"ok",
"templates"
],
"properties": {
"ok": {
"type": "boolean",
"const": true,
"description": "Indicates success. Errors arrive as content with isError:true."
},
"templates": {
"type": "array",
"items": {
"type": "object",
"required": [
"displayName",
"description",
"contentMarkdown",
"category",
"cover",
"starterPrompt",
"copyUrl",
"templatePageUrl",
"builders",
"creatorHandle"
],
"properties": {
"cover": {
"anyOf": [
{
"type": "object",
"required": [
"url",
"alt",
"caption"
],
"properties": {
"alt": {
"type": "string"
},
"url": {
"type": "string"
},
"caption": {
"type": [
"string",
"null"
]
}
},
"additionalProperties": false
},
{
"type": "null"
}
],
"description": "The Template's structured cover image, when set."
},
"copyUrl": {
"type": "string",
"description": "The app's own live URL. It starts browser navigation where a non-owner can choose to copy and must sign in before Charming creates the owned App. It is not an MCP or REST write. After creation, use list_apps or GET /app to find the copy, then get_app_source or GET /app/<id>/source to read it. Free of write-capable `?t=` tokens."
},
"builders": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991,
"description": "Distinct accounts that own a copy of this template."
},
"category": {
"type": [
"string",
"null"
],
"description": "The Template's category, when set."
},
"description": {
"type": [
"string",
"null"
],
"description": "The creator's search-friendly summary."
},
"displayName": {
"type": "string",
"description": "Human-readable name."
},
"creatorHandle": {
"type": "string",
"description": "The template's creator handle."
},
"starterPrompt": {
"type": [
"string",
"null"
],
"description": "The prompt a fresh copy starts with, if the creator set one."
},
"contentMarkdown": {
"type": [
"string",
"null"
],
"description": "The creator's longer Template detail in safe-subset Markdown."
},
"templatePageUrl": {
"type": "string",
"description": "The indexable `/templates/<handle>/<app-name>` page for this template."
}
},
"additionalProperties": false
},
"description": "Listed templates matching the query, most relevant first per `sort`."
},
"advisories": {
"type": "array",
"items": {
"type": "object",
"required": [
"kind",
"summary"
],
"properties": {
"data": {
"type": "object",
"description": "Kind-specific structured payload. Shape varies per advisory kind.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"kind": {
"type": "string",
"description": "Stable advisory identifier (e.g. \"legacy-bridge\")."
},
"doc_url": {
"type": "string",
"description": "Optional docs pointer for this advisory kind."
},
"summary": {
"type": "string",
"description": "Agent-facing summary. Self-sufficient; no extra context required."
},
"severity": {
"enum": [
"info",
"warn"
],
"type": "string",
"description": "Severity; omitted advisories are treated as 'info'."
},
"userSummary": {
"type": "string",
"description": "End-user-facing summary. Set when the advisory should render in-app."
}
},
"additionalProperties": false
},
"description": "Structured non-fatal advisories, including authoring issues and unread staff feedback responses. Each advisory.summary is also appended to the text content for the LLM path."
}
},
"additionalProperties": false
},
"annotations": {
"title": "Search Templates",
"readOnlyHint": true,
"openWorldHint": false,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description93/100
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.
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
handle
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.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
Annotations75/100
Declare idempotentHint based on the tool’s actual behavior.
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"ok",
"handle",
"previousHandle"
],
"properties": {
"ok": {
"type": "boolean",
"const": true,
"description": "Indicates success. Errors arrive as content with isError:true."
},
"handle": {
"type": "string",
"description": "The new live handle for the signed-in user."
},
"advisories": {
"type": "array",
"items": {
"type": "object",
"required": [
"kind",
"summary"
],
"properties": {
"data": {
"type": "object",
"description": "Kind-specific structured payload. Shape varies per advisory kind.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"kind": {
"type": "string",
"description": "Stable advisory identifier (e.g. \"legacy-bridge\")."
},
"doc_url": {
"type": "string",
"description": "Optional docs pointer for this advisory kind."
},
"summary": {
"type": "string",
"description": "Agent-facing summary. Self-sufficient; no extra context required."
},
"severity": {
"enum": [
"info",
"warn"
],
"type": "string",
"description": "Severity; omitted advisories are treated as 'info'."
},
"userSummary": {
"type": "string",
"description": "End-user-facing summary. Set when the advisory should render in-app."
}
},
"additionalProperties": false
},
"description": "Structured non-fatal advisories, including authoring issues and unread staff feedback responses. Each advisory.summary is also appended to the text content for the LLM path."
},
"previousHandle": {
"type": "string",
"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."
}
},
"additionalProperties": false
}
Original tool metadata
{
"name": "set_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.",
"inputSchema": {
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"handle"
],
"properties": {
"handle": {
"type": "string",
"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."
}
}
},
"outputSchema": {
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"ok",
"handle",
"previousHandle"
],
"properties": {
"ok": {
"type": "boolean",
"const": true,
"description": "Indicates success. Errors arrive as content with isError:true."
},
"handle": {
"type": "string",
"description": "The new live handle for the signed-in user."
},
"advisories": {
"type": "array",
"items": {
"type": "object",
"required": [
"kind",
"summary"
],
"properties": {
"data": {
"type": "object",
"description": "Kind-specific structured payload. Shape varies per advisory kind.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"kind": {
"type": "string",
"description": "Stable advisory identifier (e.g. \"legacy-bridge\")."
},
"doc_url": {
"type": "string",
"description": "Optional docs pointer for this advisory kind."
},
"summary": {
"type": "string",
"description": "Agent-facing summary. Self-sufficient; no extra context required."
},
"severity": {
"enum": [
"info",
"warn"
],
"type": "string",
"description": "Severity; omitted advisories are treated as 'info'."
},
"userSummary": {
"type": "string",
"description": "End-user-facing summary. Set when the advisory should render in-app."
}
},
"additionalProperties": false
},
"description": "Structured non-fatal advisories, including authoring issues and unread staff feedback responses. Each advisory.summary is also appended to the text content for the LLM path."
},
"previousHandle": {
"type": "string",
"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."
}
},
"additionalProperties": false
},
"annotations": {
"title": "Set Your Handle",
"readOnlyHint": false,
"openWorldHint": true,
"destructiveHint": false
}
}
Loading score analytics…
Put your MCP to the test
A few tasks worth trying with this server, chosen from its reviewed tools. These have not been run.
01List AppsList 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-on…
02Read DocsRead Charming documentation without web access.
03Search TemplatesSearch the public Charming Template directory by keyword.
04List App FeedbackList feedback rows for the caller's apps.
05List RoutinesUse this to see the Routines on every app the caller can edit.
Want to run these tasks against your MCP server with us?
charm.ing setup, tools, quality score, publishing readiness, and alternatives.
How do I add charm.ing to Claude, ChatGPT, or Cursor?
Add charm.ing to Claude, ChatGPT, or Cursor by connecting its remote MCP URL in each client's MCP settings. In Claude, add a custom connector. In ChatGPT, add an MCP app. In Cursor, add an MCP server under Settings. The MCP URL is https://charm.ing/mcp.
Is charm.ing MCP ready for ChatGPT and Claude?
As of October 14, 2026, charm.ing scores 90/100 on Manufact's MCP publishing checklist. The scan does not yet meet ChatGPT publishing checks and meets Claude publishing checks. Passing these checks does not guarantee marketplace approval.
How clear is charm.ing's MCP tool documentation?
As of October 14, 2026, charm.ing scores 90/100 in Manufact's review of tool titles, descriptions, input and output schemas, and behavior annotations. The review covers 37 of 37 discovered tools. The quality score measures documentation clarity; runtime behavior and marketplace readiness require separate checks.
What are the best alternatives to charm.ing?
As of October 14, 2026, alternatives to charm.ing in developer-tools on the Manufact MCP catalog include killfear-held-at-the-gate.netlify.app, AgentMail, Manufact Cloud, Authdog, and Apify MCP Server.
How can charm.ing improve its MCP documentation?
submit_feedback is the lowest-scoring reviewed tool at 75/100. 5 of 9 inputs have descriptions. Add descriptions to the missing inputs, including where to obtain IDs and how to supply values. Open its review for feedback and a copyable fix prompt, then rerun the documentation review after updating your tool metadata.
Not your MCP?
Review any public or OAuth MCP server for protocol compatibility, tool documentation, and ChatGPT and Claude publishing readiness. No account required.