MCP build tools
These are the build and control tools exposed at https://robutler.ai/mcp once your coding agent is connected. Apps are built as widgets, so the tools are named widget_* (authoring) and workspace_* (driving apps you have open). For the discovery and platform tools (search, delegate, and so on) see the MCP tool catalog.
Every tool authorizes as the connected user and re-checks ownership in the underlying pipeline. Client-supplied ids are never trusted: you cannot publish a folder you do not own or invoke a command in a workspace you cannot access.
Each tool returns JSON. Errors come back as a text result flagged isError.
Two ids per app, and they are not interchangeable. An app has a widget id (widgetContentId — the row that mounts and renders) and a bundle folder id (folderId — where its files live). Mount and share with the widget id; read and write files with the folder id. widget_instantiate resolves an owned bundle folder to its widget for you, but the distinction is worth keeping straight.
Permissions. Every tool carries MCP annotations: read-only tools (widget_catalog, widget_download, widget_fn_logs, widget_scaffold, the workspace_* listing and log tools) run without a per-call confirmation in clients such as Claude; write tools create or update something you own; destructive tools (widget_put_files, which overwrites files, widget_publish, which replaces the live public version, and workspace_widgets_invoke, which runs whatever command an app declares) prompt before each call. See the MCP tool catalog.
Publishing is open to every owner. widget_publish and widget_snapshot are registered for every account; both re-check that you own the folder or working app before anything reaches the catalog. An unpublished app still runs, mounts, and can be shared privately.
Authoring tools (widget_*)
create_widget
The one-call authoring path: scaffolds the chosen template, lands it as an editable bundle folder you own, registers it as a private widget you can mount immediately, and mints a dev token.
| Argument | Type | Notes |
|---|---|---|
name | string | App name. Non-kebab characters are normalized to -. |
template | string | collab-app (default, full starter) · minimal · webgpu · webrtc. |
Returns { folderId, widgetContentId, widgetType, name, template, fileCount, files, openUrl, devToken, hint }. Mount with widget_instantiate(refId=widgetContentId); edit with widget_put_files(folderId, …); openUrl opens the app full screen.
widget_scaffold
Returns the same starter bundle as a files map to write locally: widget.json, the entry HTML, app code, and (for collab-app) an AGENT.md, tests, and a server function. It does not write anything server-side — prefer create_widget unless you want to iterate locally first.
| Argument | Type | Notes |
|---|---|---|
name | string | App name. |
template | string | Same choices as create_widget. |
Returns { name, template, files, hint } where files is a path-to-contents map.
widget_put_files
The edit step of the loop. Upserts text files into a bundle folder you own, writing the bytes inline so the sandbox serves them immediately. Creates parent subfolders as needed.
| Argument | Type | Notes |
|---|---|---|
folderId | string | The bundle folder id (from create_widget, widget_remix, or widget_download). |
files[] | array | Each entry is { path, text } (full replace) or { path, edits: [{ oldText, newText }] } (in-place patch; each oldText must occur exactly once). Prefer edits for large files so you do not re-send them. |
UTF-8 text only: widget.json, the entry HTML, JS / CSS modules, AGENT.md. Binary assets (images, fonts) go through the dev-token upload path, not this tool. Limits: at most 100 files per call, 32 MiB per file. Storage is automatic: small files live inline, larger ones go to content-addressed blob storage — edits patch a file of any size either way (a remixed app's multi-MB built boot.js included).
File edits are live in the bundle — reload the mounted instance (workspace_widgets_invoke builtin __reload) to see them, and bump any ?v= cache token on assets you changed. Changes to widget.json or the declared command surface are snapshotted onto the widget row and refresh on republish.
Returns { folderId, results: [{ path, action: 'created' | 'updated', id }], hint }.
widget_instantiate
Mounts an app on a workspace canvas and returns where to open it. Validates the ref before mounting: an id that does not resolve to content you can read is an error, and a bundle folder id is auto-resolved to its widget row (registering one for a folder you own) rather than mounting an inert file.
| Argument | Type | Notes |
|---|---|---|
refId | string | A widget id (widget_catalog refId, or the widgetContentId from create_widget / widget_remix), a project contentId (create_document), or an owned bundle folderId (auto-resolved). |
workspaceId | string | Existing workspace. A new one is created if omitted. |
name | string | Name for the new workspace. |
fresh | boolean | Force a new instance. By default the call is idempotent: an existing mount of the same refId (recent call, or one of your open workspaces) is returned with reused: true. |
Returns { workspaceId, itemId, refId, mountKind, canvasUrl } (+ reused, and resolvedFrom when a folder id was resolved). Assert on mountKind: "widget" is a running sandboxed app, "document" is content opened by its handler. Then confirm the render — workspace_widgets_list shows the instance and workspace_widgets_logs reaches loadState: "ready"; a successful mount call alone proves nothing rendered.
widget_catalog
Lists the apps available to this account — each with the refId to pass to widget_instantiate, whether it is collaborative, and the document mimes it opens (for create_document).
| Argument | Type | Notes |
|---|---|---|
query | string | Optional filter over widgetType / title / mime. |
Returns { count, widgets: [{ widgetType, refId, title, collab, opensMimes }], hint }.
create_document
Creates an empty project document (an ADR-0022 project folder) stamped with the mime that decides which app opens it. The returned contentId mounts with widget_instantiate and names the document's collab room.
| Argument | Type | Notes |
|---|---|---|
mime | string | The project mime, e.g. application/vnd.robutler.eda+json. Pick one from widget_catalog's opensMimes. |
name | string | Document name. Default Untitled. |
Returns { contentId, room, projectMime, openUrl, handler, hint }.
widget_remix
Clones an app you can use into your own private, editable copy — the same operation as the UI Remix button. No publish entitlement needed; nothing is published and the original is untouched. Works on any app you can access: published, or shared with you. If the app reached you as a shared document (you hold access to the document, not the app), pass that document's id as via — the server verifies the chain (you can read the document, the app is its opener, and the document's author is entitled to the app).
| Argument | Type | Notes |
|---|---|---|
refId | string | The source widget id (a widget_catalog refId, a canvas item refId, or a widgetContentId). |
via | string | Optional shared-document delegation (see above). |
Returns { widgetContentId, folderId, displayName, canPublish, hint }. Mount the copy with widget_instantiate(refId=widgetContentId), edit it with widget_put_files(folderId, …). canPublish says whether this account can later widget_publish it. Some kinds are not remixable (passive and native external widgets, and live canvas items) and return an error. See Publishing and remix.
widget_publish
Publishing-enabled accounts. Publishes a folder bundle as a public app. In one transaction it mints (or reuses) a dedicated agent for the app, wires each declared tool as a custom function, stamps the app's command interface, and upserts the catalog and post rows. Idempotent per (author, folder): republishing updates in place — and a republish is also how manifest / command-surface changes reach the widget row.
| Argument | Type | Notes |
|---|---|---|
folderId | string | The bundle folder id holding widget.json plus the entry HTML. |
Returns { postId, agentId, widgetContentId, publicMcpAppUrl }. The publicMcpAppUrl is the app's own outbound MCP App manifest (/api/widgets/<postId>/mcp).
widget_snapshot
Publishing-enabled accounts. Cuts an immutable, content-addressed version of a working app and, when listed, creates or repoints a marketplace post. Idempotent: an unchanged tree (same Merkle hash as the last version) is a no-op that just clears the dirty flag.
| Argument | Type | Notes |
|---|---|---|
workingWidgetId | string | The working app's content row id. |
listed | boolean | List in the marketplace. Omit on an update to reuse the prior choice. |
title | string | Optional title. |
message | string | Optional commit-style version note. |
Returns { versionId, widgetContentId, treeFolderId, treeHash, postId, deduped }. See Publishing and remix.
widget_download
Lists and reads the files of a bundle folder for local editing or debug. Walks the subtree and returns text inline (binary files are flagged, not inlined) up to a byte budget, so a bundle with multi-MB built assets never lands in the conversation whole.
| Argument | Type | Notes |
|---|---|---|
folderId | string | The bundle folder id. |
maxFiles | number | 1..1000, default 200. |
maxBytes | number | Inline text budget, 10000..2000000 bytes. Default 200000. |
paths | string[] | Only these folder-relative files, or folder prefixes such as tests. Up to 100. |
Returns { folderId, count, bytes, files: [{ id, path, mime, binary, text, bytes }], omitted?, hint? }. A file past the budget is still listed, with text: null and omitted: true; call again with paths set to the ones you need, or a larger maxBytes.
widget_fn_logs
Server-side debug: reads the recent custom-function invocations of an agent you own, with status, error code, and duration. Useful for diagnosing why a published tool is failing. A published app's functions run under the agent minted for it at publish time, which the publisher owns.
| Argument | Type | Notes |
|---|---|---|
agentId | string | Id or username of an agent you own. |
functionName | string | Optional filter to one function. |
limit | number | 1..100, default 25. |
Returns { agentId, count, invocations: [{ functionName, sourceSkill, status, errorCode, durationMs, createdAt }] }.
widget_dev_token
Mints a short-lived, least-privilege widget-dev Bearer so a locally running app can reach the portal during development. This token is for the local dev server only and is rejected at /mcp.
| Argument | Type | Notes |
|---|---|---|
agentId | string | Optionally bind the token to one agent you own. |
ttlMinutes | number | 5..60, default 30. |
Returns { token, portalUrl, agentId, expiresAt, scope }. Keep it on the dev machine and never commit it. See the security model.
Live-app control tools (workspace_*)
These drive apps you already have open in a live browser tab, the same command surface the in-workspace agent uses. A command only lands while the app is open in a live tab: there is no durable offline queue and the RPC times out (up to 5 minutes). They also answer for a standalone /w/ share view — pass the shared contentId (or share-<contentId>) where a workspace id is expected.
workspace_list_open
Lists the surfaces you currently have open in a live tab, with the apps open in each — workspaces (kind: "workspace") and standalone /w/ share views (kind: "share"). The result names the instance and identity that answered, which matters when more than one Robutler deployment is connected. No arguments.
Returns { instance, user, open: [{ workspaceId, title, tabIds, openWidgetItemIds, kind }] }.
workspace_widgets_list
Lists the apps on a workspace and the commands each understands (its agent-control surface). Static, so it works without a live tab. Each entry is flagged live when it is open in one of your tabs and therefore invokable now.
| Argument | Type | Notes |
|---|---|---|
workspaceId | string | The workspace id, or a share-surface id / contentId for a standalone /w/ view. |
Returns { workspaceId, hasLiveTab, widgets: [{ itemId, ..., live }] }. See Agent command interface.
workspace_widgets_invoke
Runs a command on an app open in your live tab.
| Argument | Type | Notes |
|---|---|---|
workspaceId | string | Optional — omitted, it is derived from the item id (item ids are unique across your open workspaces). Pass it only to disambiguate a short prefix. |
itemId | string | The app item id (from workspace_widgets_list), a unique prefix of it, or a shared contentId for a /w/ view. |
name | string | A declared command, or a builtin (below). |
args | object | Command arguments. |
timeoutMs | number | 1000..300000, default 30000. Exports can be slow. |
targetTabId | string | Pin a stateful sequence to one tab (from a prior result). Omit to auto-route. |
Builtin command names every app answers:
__screenshot: pixel-true PNG of the app, captured host-side (returned as an image). May first ask the user for a one-time screen-share grant on the tab.__record:{ durationMs, fps?, selector? }, records a canvas / video app to a stored clip, returns a content ref.__capture:{ durationMs?, fps? }, host-side Region Capture; a still, or a WebM clip withdurationMs.__getState,__describe: introspect the app.__reload: reload the app iframe to pick upwidget_put_filesedits — no tab refresh needed. Sub-assets pinned with their own?v=token need that token bumped too.
Returns { tabId, result }.
workspace_widgets_logs
Reads a deployed app instance's captured browser console logs plus load state (loading / ready / failed), including script errors, CSP blocks, and Permissions-Policy violations. Use it to debug why an app on the canvas is blank or broken. An item that is not a running app (content mounted as a file attachment) is refused outright with an explanation, rather than reporting loading forever. Routes to the live tab; requires the workspace open in a browser.
| Argument | Type | Notes |
|---|---|---|
workspaceId | string | The workspace id, or a share-surface id for a /w/ view. |
itemId | string | The app item id. |
limit | number | 1..1000, default 100. |
timeoutMs | number | 1000..60000, default 30000. |
Returns { tabId, result }.
Related
- Quickstart: the tools in order, end to end.
- App authoring: bundle layout and
widget.json. - Publishing and remix: private remix, publish, snapshot in depth.