Templates & assets
These tools let an agent design how an app's banners look and what their buttons do. A manifest says what an app sends (its fields and actions). A template says how a banner draws those fields on a grid of cells. Assets are the Rive animations and images a template can use. The tools read and write exactly what the Designer reads and writes, and they check a template with the same validation the Designer uses. This page is for an agent author, or for anyone who wants to know what an agent can change.
For the shared conventions (result format, errors, images) see the MCP server overview. The format of a template itself is in Grid and layout, the components are in Components, and the action rules are in Actions.
How these tools fit together#
The usual design session calls the tools in this order:
get_manifestto see the fields the app sends. Each field key is a{token}a template can bind.component_schemaonce, to learn the grid and the components.put_templateto save a draft. Errors name the JSON path and the cell id, and nothing is saved until there are none.render_previewto look at the result, in light and dark, with long or missing values passed indata.list_shortcutsandadd_action_rulefor buttons of your own.send_testto see the real banner on screen.
A worked example of this session is in Connect an agent.
Tools#
- Manifests
- The template format
- Templates
- Checking a design
- Buttons
- Sharing
- Assets and symbols
Manifests#
A manifest is registered by the app that sends notifications, or written by an agent for an app that does not. Its fields and actions are documented in Manifests. Agents installed from Settings > MCP get a manifest automatically; see Agent identity.
list_manifests#
Lists the manifests the issuing apps registered. A manifest declares the fields an app sends and the actions (buttons) it offers. The result has one summary per app. Use get_manifest for sample values.
Arguments
No arguments.
{}
{
"count": 1,
"manifests": [
{
"app": "example.bidbot",
"appName": "BidBot",
"version": 1,
"fields": ["title:text!", "item:text", "bids:number"],
"actions": ["view", "withdraw"],
"assets": [],
"defaultTemplate": "bid-won"
}
],
"note": "A trailing ! marks a required field. get_manifest shows sample values."
}
Each field is written key:type, with a trailing ! when the field is required.
HTTP route
Side effects
None. Read-only.
get_manifest#
Returns one app's whole manifest: the fields (type, required flag and sample value), the issuer's actions with their ids, the assets and the default template.
- The field keys are the
{tokens}a template binds. - The samples are what
render_previewandsend_testshow. - The manifest is also readable as the resource
herald://manifests/<app>, always shortened.
Strings over 1,200 characters, such as an embedded icon or a long sample, are shortened to a marker like <data:image/png;base64,... 5022 characters omitted>. Pass full: true to get the real values.
Arguments
| Name | Type | Required | Description |
|---|---|---|---|
app | string | required | The app id, such as example.bidbot. list_manifests shows the registered ids. |
full | boolean | optional | Returns long strings in full instead of shortening them. Default false. |
{"app": "example.bidbot"}
{
"app": "example.bidbot",
"appName": "BidBot",
"version": 1,
"fields": [
{"key": "title", "type": "text", "required": true, "sample": "Bid accepted"},
{"key": "item", "type": "text", "sample": "Oak desk, 1920"},
{"key": "bids", "type": "number", "sample": 3}
],
"actions": [
{"id": "view", "label": "View", "kind": "url", "url": "https://example.com/bids"},
{"id": "withdraw", "label": "Withdraw", "kind": "callback", "style": "destructive"}
],
"assets": [],
"defaultTemplate": "bid-won"
}
When the app has no manifest the tool fails and lists the apps that do.
HTTP route
Side effects
None. Read-only.
put_manifest#
Creates a manifest or replaces the whole one. Read it with get_manifest, edit it and send it back. Apps normally register their own manifest. Use this to describe an app that does not, to add sample values for the Designer, or to set defaultTemplate.
Rules for what you send:
- A shortened marker that
get_manifestwrote is swapped back for the stored value. A marker that matches nothing stored is refused: read the manifest again withfull: true. - An issuer action is one of the kinds
url,callback,command,script,shortcut,openApp,replyordismiss. Each kind is described in Actions. Ascriptorshortcutaction runs only when the user has allowed the app to run commands, scripts and Shortcuts. A snooze action is authored in a template withadd_action_rule. The manifest can also carry a defaultfollowUp, described in Manifests. - An invalid manifest is rejected with the path of the field.
- Rive files named in
assetsare copied into Herald's assets folder.
Arguments
| Name | Type | Required | Description |
|---|---|---|---|
manifest | object | required | The whole manifest. app is required. The other fields are in Manifests. |
{
"manifest": {
"app": "example.bidbot",
"appName": "BidBot",
"fields": [
{"key": "title", "type": "text", "required": true, "sample": "Bid accepted"},
{"key": "bids", "type": "number", "sample": 3}
],
"actions": [{"id": "view", "label": "View", "kind": "url", "url": "https://example.com/bids"}],
"defaultTemplate": "bid-won"
}
}
{
"saved": true,
"app": "example.bidbot",
"fields": 2,
"actions": 1,
"assets": 0,
"defaultTemplate": "bid-won",
"restoredAbbreviatedValues": 0
}
HTTP route
Side effects
Overwrites the app's manifest. Existing templates are kept.
delete_manifest#
Deletes an app's manifest and the copies of its Rive assets. Templates stay. The app's notifications then use the generic look until a manifest is registered again.
Arguments
| Name | Type | Required | Description |
|---|---|---|---|
app | string | required | The app id. |
{"app": "example.bidbot"}
{"ok": true}
HTTP route
Side effects
Deletes stored data. The tool carries the destructive hint, so a client can ask before it runs.
The template format#
component_schema#
Returns the format of a template, for authoring: the grid, every component type with each property, allowed values and default, how {token} bindings and empty collapsing work, the action kinds and rules, symbol styling, and complete examples. Read it before writing a template. The same format is documented for people in Grid and layout and Components. It is also available as the short resource herald://docs/components.
The tool asks the running Herald for its own document and falls back to the one built into herald-mcp, so it works when Herald is down.
Arguments
| Name | Type | Required | Description |
|---|---|---|---|
component | string | optional | Returns only this component's schema plus the shared definitions it uses. A component name from the component reference. |
section | string | optional | Returns only this part of the document. One of definitions, components, bindings, actions, examples, workflow. |
{"component": "badge"}
Example result
The result is a JSON document. A call with no arguments returns the whole document as text. An unknown name fails and lists the valid names. A narrowed call returns an object with these keys:
| Key | Meaning |
|---|---|
component | The component name that was asked for. |
schema | The JSON schema of that component. |
definitions | The shared definitions the schema uses. |
source | herald when the running app answered, or herald- when the server answered from its own copy. |
HTTP route
Side effects
None. Read-only.
Templates#
Four built-in templates exist for every app and are generated on request: builtin.imageLeft, builtin.imageRight, builtin.hero and builtin.compact. They are read-only starting points: copy one with duplicate_template or read it with get_template and save it under another name.
list_templates#
Lists the saved templates of one app or of all apps, and the names of the built-ins. Each summary has the app, the name, the layout version, the cell count, the {tokens} the template reads, the number of action rules, and whether it is the app's default.
Arguments
| Name | Type | Required | Description |
|---|---|---|---|
app | string | optional | Only this app's templates. Default: all apps. |
{"app": "example.bidbot"}
{
"count": 1,
"templates": [
{
"app": "example.bidbot",
"name": "bid-won",
"layoutVersion": 2,
"cells": 5,
"tokens": ["bids", "item", "title"],
"actionRules": 1,
"collapseEmpty": true,
"isDefault": true
}
],
"builtins": ["builtin.imageLeft", "builtin.imageRight", "builtin.hero", "builtin.compact"]
}
HTTP route
Side effects
None. Read-only.
get_template#
Returns the full JSON of one template, or of a built-in generated for the app. Edit it and save it back with put_template. It is also readable as the resource herald://templates/<app>/<name>; path parts are percent-encoded, so Bid won is Bid%20won.
Arguments
| Name | Type | Required | Description |
|---|---|---|---|
app | string | required | The app id. |
name | string | required | A template name, or builtin.imageLeft, builtin.imageRight, builtin.hero or builtin.compact. |
{"app": "example.bidbot", "name": "builtin.compact"}
Example result
The result is the template as a JSON document, in the format described in Grid and layout. A name that does not exist fails and lists the saved and built-in names.
HTTP route
GET /v1/templates, filtered by name. Built-in templates are generated by the server.
Side effects
None. Read-only.
put_template#
Creates or overwrites a template. Herald validates it against the grid schema first. Errors carry the JSON path and the id of the cell at fault, and nothing is saved until there are none. Errors include overlapping cells, a cell outside the grid, an unknown component type, a property of the wrong type and a bad colour. Warnings are returned with the success and do not block: a {token} the manifest does not declare, or an unknown key that is probably a typo such as colspan for colSpan.
Names that start with builtin. are reserved. With setAsDefault: true the template also becomes the manifest's defaultTemplate; the app needs a manifest for that.
Arguments
| Name | Type | Required | Description |
|---|---|---|---|
template | object | required | The whole template object. Its fields are in Grid and layout. |
app | string | optional | The app id. Needed only when the template object has no app. It must match the template's own app when both are given. |
setAsDefault | boolean | optional | Also sets the manifest's defaultTemplate to this template. Default false. |
{
"template": {
"name": "bid-won",
"app": "example.bidbot",
"layoutVersion": 2,
"grid": {"rows": 3, "cols": 3, "rowSizes": ["auto", "auto", "auto"], "colSizes": [40, "fill", "auto"], "gap": 6, "padding": 12, "width": 380},
"cells": [
{"id": "icon", "row": 0, "col": 0, "rowSpan": 2, "component": {"type": "issuerIcon", "size": 32}},
{"id": "title", "row": 0, "col": 1, "component": {"type": "text", "binding": "{title}", "style": "title", "maxLines": 2}},
{"id": "bids", "row": 0, "col": 2, "component": {"type": "badge", "binding": "{bids}"}},
{"id": "item", "row": 1, "col": 1, "colSpan": 2, "component": {"type": "text", "binding": "{item}", "maxLines": 3}},
{"id": "actions", "row": 2, "col": 0, "colSpan": 3, "component": {"type": "actions", "source": "merged", "layout": "wrap"}}
]
},
"setAsDefault": true
}
{
"saved": true,
"app": "example.bidbot",
"name": "bid-won",
"layoutVersion": 2,
"cells": 5,
"isDefault": true,
"warnings": [],
"notes": [],
"next": "render_preview to look at it; send_test to see the real banner."
}
When validation fails the result has isError set and looks like this. Fix the cells it names and call again.
{
"ok": false,
"error": "Template not saved: 1 error(s). Fix them and call again.",
"saved": false,
"errors": [{"severity": "error", "path": "cells[2]", "cellId": "bids", "message": "cell 'bids' overlaps cell 'title' at row 0, col 1"}],
"warnings": []
}
The notes array explains what the tool could not do, such as ignoring setAsDefault for an app with no manifest.
HTTP route
PUT /v1/templatessaves the template.PUT /v1/manifestsets the default, only whensetAsDefaultistrue.
Side effects
Saves a template, overwriting one of the same name. The user is asked again before a changed command, script or Shortcut runs.
delete_template#
Deletes a saved template. Built-in templates cannot be deleted. Notifications that name a deleted template use the default look. If the deleted template was the manifest's defaultTemplate, the result says so in notes.
Arguments
| Name | Type | Required | Description |
|---|---|---|---|
app | string | required | The app id. |
name | string | required | The template name. |
{"app": "example.bidbot", "name": "bid-won"}
{
"deleted": true,
"app": "example.bidbot",
"name": "bid-won",
"notes": ["'bid-won' is still the manifest's defaultTemplate; put_manifest to change it."]
}
HTTP route
Side effects
Deletes stored data. The tool carries the destructive hint.
duplicate_template#
Copies a saved template, or a builtin.* layout, under a new name, optionally for another app. It is the Designer's Duplicate. Without newName the copy is called <name> copy, numbered when that name is taken.
Arguments
| Name | Type | Required | Description |
|---|---|---|---|
app | string | required | The app the template belongs to. |
name | string | required | The template to copy: a saved name or builtin.*. |
newName | string | optional | The name of the copy. It must not start with ., _ or builtin., and must not contain / or :. |
toApp | string | optional | Creates the copy for this app instead. |
{"app": "example.bidbot", "name": "builtin.hero", "newName": "bid-lost"}
{"ok": true, "app": "example.bidbot", "name": "bid-lost", "copiedFrom": {"app": "example.bidbot", "name": "builtin.hero"}}
HTTP route
Side effects
Saves a new template. Changes no existing one.
rename_template#
Renames a saved template. The app's default template follows the new name. Command, script and Shortcut approvals belong to the old name, so the user is asked again the first time those buttons run. Built-in templates cannot be renamed; duplicate them instead.
Arguments
| Name | Type | Required | Description |
|---|---|---|---|
app | string | required | The app id. |
name | string | required | The current name. |
newName | string | required | The new name. |
{"app": "example.bidbot", "name": "bid-won", "newName": "bid-accepted"}
{
"ok": true,
"app": "example.bidbot",
"name": "bid-accepted",
"renamedFrom": "bid-won",
"isDefault": true,
"note": "Command, script and Shortcut approvals belong to the old name; the user is asked again the first time they run."
}
HTTP route
Side effects
Renames stored data and resets the user's approvals for the template. The tool carries the destructive hint.
set_default_template#
Makes a template the app's default: the one used by notifications that name no template. This sets the manifest's defaultTemplate, so the app needs a manifest. Omit name to clear the default.
Arguments
| Name | Type | Required | Description |
|---|---|---|---|
app | string | required | The app id. |
name | string | optional | The template name, saved or builtin.*. Omit it to clear the default. |
{"app": "example.bidbot", "name": "bid-won"}
{"ok": true, "app": "example.bidbot", "defaultTemplate": "bid-won"}
HTTP route
Side effects
Changes the manifest's defaultTemplate.
Check a design#
Three tools let an agent see a design without leaving a banner behind. validate_template checks the structure. render_preview draws the banner. designer_snapshot draws the editor.
validate_template#
Checks a template without saving it. Give a draft as template, or a saved one as app and name.
- The result lists errors and warnings, each with a path and the cell id.
- When a manifest is available it also reports, for the manifest's sample data, which cells are empty, which rows and columns collapse, and the resulting button list.
- It works without Herald running. The manifest check is then skipped and the result says so in
note.
Arguments
| Name | Type | Required | Description |
|---|---|---|---|
template | object | optional | A draft template object. Omit it when you use app and name. |
app | string | optional | The app id. Tokens are checked against its manifest. Required with name. |
name | string | optional | A saved template, or builtin.*, to validate instead of a draft. |
One of template or name is needed.
{"app": "example.bidbot", "name": "bid-won"}
{
"valid": true,
"errors": [],
"warnings": [],
"manifestChecked": true,
"tokens": ["bids", "item", "title"],
"withSampleData": {
"emptyCells": [],
"collapsedCells": [],
"collapsedRows": [],
"collapsedCols": [],
"actions": [
{"id": "view", "label": "View", "kind": "url", "origin": "issuer"},
{"id": "withdraw", "label": "Withdraw", "kind": "callback", "style": "destructive", "origin": "issuer"}
]
}
}
Each issue has the keys listed under Validation issues. cellId is present when the problem is inside a cell.
HTTP route
None for the check itself: the tool validates locally. When it needs data it reads:
- the manifest, with
GET /v1/manifest; - a saved template, with
GET /v1/templates.
Side effects
None. Read-only.
render_preview#
Draws a template offscreen with Herald's real banner renderer and returns the picture. The result has two parts: an image block (a PNG) and a text block with the saved file path and the pixel size. Preview a saved template with name (or builtin.*), an unsaved draft with template (validated first; errors name the cell), or neither, which draws the app's default template.
What the picture is drawn from:
- The data is the manifest's sample values by default, or the app's latest real notification with
source: "last". dataoverrides single fields. Pass a very long value to see how text wraps, and leave a key out to see how its cell collapses.- Sample data stands in for an issuer that names every action its manifest declares, so the buttons shown are all of those. A real notification shows only the actions it sends.
- Rive components are drawn as placeholders and symbol effects are not drawn.
- Render light and dark to check both.
Arguments
| Name | Type | Required | Description |
|---|---|---|---|
name | string | optional | A saved template's name, or builtin.*. Needs app. |
template | object | optional | An unsaved draft template, instead of name. |
app | string | optional | The app id. Defaults to the draft's own app. Required with name or with neither argument. |
source | string | optional | sample (default) uses the manifest's samples. last uses the app's most recent notification in History. |
data | object | optional | Field values that override the source's, such as {"bids":. |
appearance | string | optional | light (default) or dark. |
scale | number | optional | The pixel scale, from 1 to 3. Default 2. |
{"app": "example.bidbot", "name": "bid-won", "appearance": "dark", "data": {"bids": 14, "item": "Oak desk with three drawers, restored in 1962"}}
Example result
The first content block is the image, a PNG of the banner. The second is text:
{
"path": "/var/folders/xy/T/herald-previews/preview-example.bidbot-bid-won-dark-20261004-150210-E3BC.png",
"bytes": 41873,
"app": "example.bidbot",
"template": "bid-won",
"appearance": "dark",
"scale": 2,
"data": "sample+overrides",
"width": 760,
"height": 300
}
The reply carries these extra facts:
datasays where the values came from:sample,last, or either followed by+overrides.- A
warningsarray is added when a draft has warnings. - The server keeps the newest 40 preview files in its preview folder.
HTTP route
Side effects
Writes a PNG to the preview folder and removes the oldest ones beyond 40. Shows nothing on screen.
designer_snapshot#
Draws the Designer window's content offscreen and returns it as a PNG: the grid canvas with its handles, the inspector and the live preview. No window opens and nothing takes focus. Use it to check how the editor looks for a template; use render_preview to see the banner itself.
Arguments
| Name | Type | Required | Description |
|---|---|---|---|
app | string | optional | The app to open in the Designer. |
template | string | optional | A saved template name to open. |
select | string | optional | A cell id to select, so the inspector shows it. |
width | integer | optional | The image width, from 600 to 4000. Default 1100. |
height | integer | optional | The image height, from 400 to 3000. Default 820. |
{"app": "example.bidbot", "template": "bid-won", "select": "title", "width": 1200, "height": 800}
Example result
The result is an image block (a PNG) and a text block such as {"bytes": 188402, "width": 1200, "height": 800}.
HTTP route
Side effects
None. Read-only. Opens no window.
Buttons#
A template can change the buttons the issuing app sent and add buttons of its own. The rules live in the template's actionRules; see Actions. An agent cannot press a button and cannot grant the permission to run a command. The user confirms a command, script or Shortcut in Herald the first time its button is pressed, or the first time a follow-up would run it.
list_shortcuts#
Lists the names of the Apple Shortcuts installed on this Mac. Use one in an action of kind shortcut (see add_action_rule): Herald runs it with the notification as input when the user presses the button.
Arguments
No arguments.
{}
{
"count": 2,
"shortcuts": ["Create follow-up", "Log to Notes"],
"use": "add_action_rule with {\"add\": {\"id\": \"...\", \"label\": \"...\", \"kind\": \"shortcut\", \"shortcut\": \"<one of these names>\", \"input\": \"{title}\\n{url}\"}}"
}
HTTP route
Side effects
None. Read-only.
add_action_rule#
Adds one rule to a saved template's actionRules. A rule either changes an issuer action or adds an action of your own.
- To change an issuer action, set
matchto its id, its label or*, together with the change to make. The fields are in Action rules. - To add an action, set
addto an action object. The kinds and their fields are in Action object and Fields by kind. For a Shortcut, take the name fromlist_shortcuts.
Rules that apply to every rule:
- A
scriptaction runs a file in~/Library/Application Support/Herald/scripts/, whichherald_statuslists. The script receives the notification JSON on stdin. This server only talks to Herald's API, so an agent that wants a script writes the file itself and then adds the rule. - A command, script or Shortcut you add is confirmed by the user the first time its button is pressed, and again whenever it changes. A follow-up uses the same confirmation, so a button and a follow-up that run the same Shortcut share it.
- An add with the id of one of the issuer's own actions overwrites that button, and the confirmation says so. An action id that an earlier add rule already uses overwrites that rule.
- Any action can carry an SF Symbol, as a name (
checkmark.circle) or a full styling object. An unknown name is a warning. - The tool validates the template before saving it. It warns about a script that is missing or cannot run, a Shortcut that is not installed, and a
matchthat matches nothing.
Arguments
| Name | Type | Required | Description |
|---|---|---|---|
app | string | required | The app id. |
template | string | required | The saved template's name. Built-in templates are read-only: copy one with duplicate_template first. |
rule | object | required | One rule. Its fields are in Action rules. |
{
"app": "example.bidbot",
"template": "bid-won",
"rule": {
"add": {
"id": "followup",
"label": "Follow up",
"kind": "shortcut",
"shortcut": "Create follow-up",
"input": "{title}\n{item}"
}
}
}
{
"saved": true,
"app": "example.bidbot",
"template": "bid-won",
"ruleIndex": 0,
"replacedExistingRule": false,
"actionRules": 1,
"resultingActions": [
{"id": "view", "label": "View", "kind": "url", "origin": "issuer"},
{"id": "withdraw", "label": "Withdraw", "kind": "callback", "style": "destructive", "origin": "issuer"},
{"id": "followup", "label": "Follow up", "kind": "shortcut", "origin": "template"}
],
"warnings": []
}
HTTP route
PUT /v1/templatessaves the template with the rule added.GET /v1/templatesis read first, for the template.GET /v1/manifestis read first, to check the rule.GET /v1/shortcutsis read first, to check a Shortcut name.
Side effects
Saves the template with the rule added. The user must confirm a new command, script or Shortcut in Herald before it first runs.
set_follow_up#
Gives a template a follow-up: one action that runs when a banner from the app is left unattended, such as a Shortcut that forwards it to a phone. Use it when you want to add or change a follow-up without reading and rewriting the whole template. The tool saves the template with the follow-up and reports what the person still has to do.
This tool never approves code. A Shortcut, script or command that follows up asks the person at the Mac the first time,
in the banner. The result's approval and note tell you where things stand. Tell the person, and do not retry to
get around it. How a follow-up works is in Follow-ups.
Arguments
| Name | Type | Required | Description |
|---|---|---|---|
app | string | required | The app id. Cloud connectors are cloud.NAME. |
template | string | optional | The saved template to edit. A built-in layout is refused. Default: the app's default template, created from the current layout when the app has none. |
after | number or string | optional | Seconds from 5 to 604800, or "90s", "10m", "2h". Required unless enabled is false. |
shortcut | string | optional | An installed Shortcut. Take the name from list_shortcuts. |
script | string | optional | A file name in Herald's scripts folder. |
command | string | optional | A shell command. |
actionRef | string | optional | The id, or label, of an action the notification offers. |
input | string | optional | Text for the Shortcut or script, with {tokens} filled. |
label | string | optional | The name shown in Follow-up ran: LABEL. Default: the Shortcut or script name, or Follow-up for a command. |
enabled | boolean | optional | false switches the follow-up off, including one the issuer declares. |
Give exactly one of shortcut, script, command and actionRef, unless enabled is false.
{
"app": "example.bidbot",
"template": "Bid won",
"after": "10m",
"shortcut": "Forward to phone",
"input": "{title}"
}
{
"saved": true,
"app": "example.bidbot",
"template": "Bid won",
"createdTemplate": false,
"followUp": {"after": 600, "action": {"id": "follow-up", "label": "Forward to phone",
"kind": "shortcut", "shortcut": "Forward to phone", "input": "{title}"}},
"action": {"id": "follow-up", "label": "Forward to phone",
"kind": "shortcut", "shortcut": "Forward to phone", "input": "{title}"},
"origin": "template",
"approval": "needs-approval",
"needsApproval": true,
"note": "Approval stays with the person at the Mac: the banner asks the first time the action would run, and Always allow lets later follow-ups run unattended. Nothing was approved here. list_shortcuts names the installed Shortcuts."
}
The result's fields are the ones in PUT /v1/templates/follow-up,
with the approval values listed there. A refused call returns the same message as the route's 400 or 404.
HTTP route
Side effects
Saves the template, and creates it when the app has none. Runs nothing and approves nothing. The person must approve code at the Mac before the follow-up first runs.
Share a template#
A template bundle is one .heraldtemplate file that holds a template and the Rive files it plays. It is what the Designer's Export and Import use.
export_template_bundle#
Packs a saved template, or a built-in generated for the app, and the Rive files it plays into one bundle. With path Herald writes the file on this Mac. Without it the bundle comes back as base64.
Arguments
| Name | Type | Required | Description |
|---|---|---|---|
app | string | required | The app id. |
name | string | required | The template name. |
path | string | optional | Where to write the file. It must end in .heraldtemplate and its folder must exist. |
{"app": "example.bidbot", "name": "bid-won", "path": "~/Desktop/bid-won.heraldtemplate"}
{
"ok": true,
"app": "example.bidbot",
"name": "bid-won",
"file": "bid-won.heraldtemplate",
"bytes": 2481,
"assets": [],
"warnings": [],
"path": "/Users/you/Desktop/bid-won.heraldtemplate"
}
Without path, the result has a base64 field instead of path.
HTTP route
Side effects
Writes a file when path is given. Changes no stored template.
import_template_bundle#
Imports a .heraldtemplate bundle. Give the bundle in one of two ways, not both:
path, a file on this Mac, up to 64 MB.base64, up to about 700 KB.
app retargets the template to another app. The bundle's Rive files go into that app's assets folder. Script actions inside a bundle still need their script files and the user's approval.
Arguments
| Name | Type | Required | Description |
|---|---|---|---|
path | string | optional | A .heraldtemplate file on this Mac. |
base64 | string | optional | The bundle, base64 encoded. |
app | string | optional | Imports the template for this app instead of the app in the bundle. |
onConflict | string | optional | What to do when the name exists: keepBoth (default; saves it as name 2), replace or fail. |
One of path or base64 is needed.
{"path": "~/Desktop/bid-won.heraldtemplate", "app": "example.bidbot", "onConflict": "keepBoth"}
{
"ok": true,
"app": "example.bidbot",
"name": "bid-won 2",
"replaced": false,
"installedAssets": [],
"reusedAssets": [],
"missingAssets": [],
"warnings": [],
"renamedFrom": "bid-won"
}
renamedFrom appears only when keepBoth renamed the template.
HTTP route
Side effects
Saves a template and copies its Rive files. With onConflict: "replace" it overwrites a template of the same name. The tool carries the destructive hint.
Assets and symbols#
Assets are the Rive animations (.riv, at most 10 MB each, 32 per app) and images (at most 10 MB each, 100 per app) stored for an app. A template refers to a Rive file with {"type": "rive", "path": "confetti.riv"}. See the Rive component.
list_assets#
Lists the Rive animations and images stored for an app, with their size, whether the manifest declares them, which templates play them, and the component snippet to use.
Arguments
| Name | Type | Required | Description |
|---|---|---|---|
app | string | required | The app id. |
{"app": "example.bidbot"}
{
"app": "example.bidbot",
"assets": [
{
"kind": "rive",
"id": "confetti",
"file": "confetti.riv",
"path": "/Users/you/Library/Application Support/Herald/assets/example.bidbot/confetti.riv",
"bytes": 48213,
"declared": true,
"usedBy": ["bid-won"],
"component": {"type": "rive", "path": "confetti.riv"}
},
{"kind": "image", "file": "logo.png", "path": "/Users/you/Library/Application Support/Herald/assets/example.bidbot/images/logo.png", "bytes": 5120}
],
"folder": "/Users/you/Library/Application Support/Herald/assets/example.bidbot",
"limits": {"bytes": 10485760, "riveFiles": 32}
}
HTTP route
Side effects
None. Read-only.
upload_asset#
Adds a Rive animation or an image to an app's assets, like the Designer's Add. Give path (a file on this Mac) or base64 (up to about 700 KB, with name). The reply has the stored path and the component to use. An existing file of the same name is overwritten. Images are checked by their bytes, so a wrong extension is caught. Accepted images are PNG, JPEG, GIF, WebP, HEIC, TIFF and BMP.
Arguments
| Name | Type | Required | Description |
|---|---|---|---|
app | string | required | The app id. |
path | string | optional | A file on this Mac. |
base64 | string | optional | The file, base64 encoded. Needs name. |
name | string | optional | The file name, such as confetti.riv or logo.png. Required with base64. |
kind | string | optional | rive or image. Inferred from the name when omitted. |
Give exactly one of path or base64.
{"app": "example.bidbot", "path": "~/Downloads/confetti.riv"}
{
"ok": true,
"kind": "rive",
"app": "example.bidbot",
"id": "confetti",
"file": "confetti.riv",
"path": "/Users/you/Library/Application Support/Herald/assets/example.bidbot/confetti.riv",
"bytes": 48213,
"replaced": false,
"component": {"type": "rive", "path": "confetti.riv"}
}
An image reply has format and usage fields instead of id and component; usage says to use the path as an image field's value or as a fixed image source.
HTTP route
Side effects
Writes a file into the app's assets folder, overwriting one of the same name.
delete_asset#
Deletes a Rive file or an image from an app's assets by file name. Templates that play a deleted animation show a placeholder, and the reply lists them.
Arguments
| Name | Type | Required | Description |
|---|---|---|---|
app | string | required | The app id. |
file | string | required | The file name, such as confetti.riv. list_assets shows the names. |
{"app": "example.bidbot", "file": "confetti.riv"}
{"ok": true, "deleted": "confetti.riv", "stillReferencedBy": ["bid-won"]}
HTTP route
Side effects
Deletes a file. The tool carries the destructive hint.
list_symbols#
Searches the SF Symbol names available on this Mac, with their categories, for a component's symbol property. See SF Symbols. The arguments are in the table below. In short:
qmatches every word in the name or its search terms.categorynarrows the list, and the reply lists the categories with counts.limitandoffsetpage through long lists.
Arguments
| Name | Type | Required | Description |
|---|---|---|---|
q | string | optional | Search words, such as bell or arrow up. |
category | string | optional | A category key from the reply, such as communication or weather. |
limit | integer | optional | Names per page, up to 1,000. Default 100. |
offset | integer | optional | How many names to skip. Default 0. |
{"q": "bell", "limit": 2}
{
"total": 14,
"offset": 0,
"limit": 2,
"symbols": [
{"name": "bell", "categories": ["communication"]},
{"name": "bell.fill", "categories": ["communication"]}
],
"categories": [{"key": "communication", "title": "Communication", "icon": "bubble.left.and.bubble.right", "count": 10}]
}
HTTP route
Side effects
None. Read-only.
rive_check#
Loads a rive component without a window and reports what it found: the artboards, the state machines and their inputs, and the view-model properties. Nothing is shown or stored. See Rive.
With simulate it first runs pointer steps in order and reports what they wrote to the inputs. The steps are:
| Step | Pointer action |
|---|---|
hoverIn | The pointer moves onto the animation. |
hoverOut | The pointer leaves it. |
pressDown | The button goes down. |
pressUp | The button comes up. |
Arguments
| Name | Type | Required | Description |
|---|---|---|---|
app | string | required | The app id. Assets resolve in its folder. |
component | object | required | The rive component: asset or path, and optionally artboard, stateMachine. |
fields | object | optional | Field values for the component's bindings. |
simulate | array of string | optional | Pointer steps to run in order. |
{"app": "example.bidbot", "component": {"type": "rive", "path": "confetti.riv"}, "simulate": ["hoverIn", "pressDown"]}
Example result
The reply is trimmed here to its main fields.
{
"loaded": true,
"inputs": {"celebrate": "trigger"},
"applied": {},
"artboards": [
{
"name": "Main",
"width": 320,
"height": 120,
"defaultMachine": "Machine",
"machines": [{"name": "Machine", "inputs": [{"name": "celebrate", "kind": "trigger"}]}],
"animations": []
}
],
"takesClicks": false,
"pointerWrites": {}
}
When the animation does not load, loaded is false and error holds the reason the banner's placeholder shows.
HTTP route
Side effects
None. Opens no window and stores nothing.
Related#
- MCP server overview for the conventions and the index of every tool.
- Design a banner with an agent for a full session using these tools.
- Grid and layout, Components and Actions for the formats behind the arguments.
- Manifests for the fields and actions an app declares.
- Templates API and Manifests API for the HTTP calls.