Skip to main content
A widget can carry actions: buttons and links that do something when the person viewing it activates them. Actions are declared as data, never as code, so a template never gains a way to run anything. The exact grammar is in the Action definition reference.

Two kinds of action

prompt proposes a message. Its text is a sequence of literals and { "bind" } segments that resolves at render time to one plain-text message. Activating the element places that text in the user’s composer; the user decides whether to send it. No server call is made, and prompt actions work with any key. http calls a URL server-side. The definition carries method (GET or POST), a fixed absolute https url (no userinfo, fragment or bindings), an input JSON Schema of type object for the arguments, an output schema the response must satisfy, and optional headers and query maps whose values are literal strings or { "secret": "<name>" } references. GET sends the validated arguments as query parameters, POST as a JSON body. Header names the transport owns (host, content-length, transfer-encoding, connection) are refused. A shared action is an entry { name, label?, description?, definition }:

Binding an action to an element

Put action on a button or an a element; an element carries action or href, never both (CONFLICTING_ATTRIBUTES). The binding names its action by reference, { "ref": "refresh" }, or inline under definition, and may add:
  • input: one entry per input-schema field, a template path or a { "const": ... } literal, resolved at render time in the element’s scope ($root, $parent and $index available), so a button inside each can name its own row. Arguments must be declared in the input schema and must not share a name with a fixed query parameter.
  • output: how the validated response merges into the widget’s data. mode is merge (the default: top-level keys over data), replace (the response becomes data) or patch (written at a required path); an optional map of target path to source path projects the response first. The result is re-validated as a payload of the widget’s kind; a response that breaks the schema fails with INVALID_ACTION_OUTPUT and the previous data stands.
A widget may also declare one load binding beside its template, a top-level load field with the same keys. It accepts only http GET actions and runs once when the widget first renders in an Apps host. In the widget designer the add menu offers action on button and a elements only, with an editor for the mapping and output mode; the preview renders bound elements with an inert badge, and nothing executes from a preview.

The action designer and the Test control

Shared actions live in the Actions section, managed like widgets, themes and schemas. The action designer edits a prompt’s text segments or an http action’s method, URL, schemas, headers and query, with { "secret" } references chosen from your secret names; a shared schema can be copied into an input or output schema, and the control says so, because an action’s contract never references one. Import and Export sections accept and produce the entry JSON above. An http action is savable only after a Test call succeeds: the app builds a form from the input schema, runs the call through the same server-side path production uses (never from the browser), requires the response to validate against the output schema, and shows the result with secret values redacted. Test calls share the per-principal execution limit. Saving a prompt action asks you to acknowledge that the proposed message is your responsibility. Deleting an action that widgets still bind is refused with ACTION_IN_USE, naming them.

Execution gates

An http action leaves the server only when three gates pass:
  1. The host proxies widget tool calls. The frame calls execute_action itself, which needs an MCP Apps host advertising serverTools; elsewhere http elements render disabled with an explanatory title, and prompt elements stay enabled.
  2. The key carries execute. The scope is an opt-in at key creation and fixed afterwards. Under a read-only key the widget renders with its http descriptors marked disabled: "scope" and no load fires. See Keys and scopes.
  3. The definition comes from your store. A request names a widget and a binding identifier; the server resolves the definition from your stored template or shared action and ignores any URL, method, headers or schema in the request. A binding to an action you have not saved renders disabled: "unresolved".
The fetch is guarded: https only, private, loopback, link-local and metadata targets refused, no redirects, a total deadline, a response size cap and JSON content only (the values); a 204 or empty body arrives as null for the output schema to judge. After a successful action the widget re-renders in place and posts one model-context update with its new payload, so the agent and the visual never disagree. See Trust model.

Action notes

The model never sees the frame, so every render_widget and execute_action text output ends with an Action notes: tail whenever the widget carries actions: how many http and prompt actions it has (per binding; a button repeated by each counts once), how they behave, whether the widget loads data on first render, why any http action is disabled (scope or unresolved), and that the agent does not call execute_action itself.

Secrets

The Secrets section is write-only. You set, replace and delete named secrets through a password-style field; after entry the app never shows a value, a preview or a length, and the list offers Replace and Delete, never Show or Copy. A name matches ^[a-z][a-z0-9-]{0,63}$. At rest each secret is envelope-encrypted: a fresh data key encrypts the value with AES-256-GCM and is itself wrapped by a key-encryption key held in a vault, so only ciphertext is persisted. An http action references a secret only as { "secret": "<name>" } in a header or query value; references in the URL, body or input mapping are refused. At execution the server resolves each reference from your own secrets and injects the value; a missing secret fails with UNKNOWN_SECRET before any network activity. Values never appear in rendered output, structuredContent, tool text or model-context updates, and every diagnostic, including errors echoed by the remote service, has each value replaced by *** in its raw, percent-encoded and JSON-escaped forms. A secret an action still references cannot be deleted (SECRET_IN_USE, naming the actions).