Skip to content

First-party plugins

The four Kalebtec-maintained plugins that ship from this repo (packages/plugins/*) and publish on npm under the @browxai/* scope. This page is the adopter-facing reference for each plugin’s full tool surface: what it adapts, what it returns, and what it needs enabled on the host. For the install model and CLI see Plugins overview; for writing your own plugin see Authoring plugins.

PluginNamespaceCapabilitiesWhat it adapts
@browxai/plugin-exampleexample(none)Nothing. It is the canonical runtime-contract reference.
@browxai/plugin-figmafigmaeval + canvasFigma, via the page-side figma.* plugin-context global.
@browxai/plugin-tldrawtldraweval + canvasTldraw v2+, via the window.editor global.
@browxai/plugin-excalidrawexcalidraweval + canvasExcalidraw 0.17+, via the window.excalidrawAPI global.

Why the canvas adapters declare eval + canvas

Section titled “Why the canvas adapters declare eval + canvas”

Every canvas-adapter tool is a thin wrapper around an api.callTool("eval_js", {expr}) round-trip: the plugin builds an app-API expression (figma.currentPage.selection, editor.createShapes([...]), excalidrawAPI.updateScene({...})), dispatches it through the host’s eval_js tool, and parses the JSON envelope back into a structured {ok, …} result. That inner eval_js call goes through the same capability gate a direct MCP call would, so the plugin honestly declares eval (arbitrary JS in page context) plus canvas (the canvas-app automation lane that also gates canvas_query). If either capability is missing from the server’s active set at startup, the plugin is disabled with status: "disabled-by-capability-mismatch" and the reason surfaces on plugins_list.

Enable both before the server starts, in the env of your MCP client’s entry for browxai (browxai run by hand only waits for a client):

Terminal window
BROWX_CAPABILITIES=read,navigation,action,human,eval,canvas

(set_config can’t add capabilities: a saved capabilities list only narrows BROWX_CAPABILITIES. Capabilities and plugins are both resolved once at server start.)

The example plugin declares no capabilities. It runs on a server with the default set and exists to prove the runtime end-to-end.

Host-app detection: the <adapter>-not-loaded envelope

Section titled “Host-app detection: the <adapter>-not-loaded envelope”

Each canvas adapter probes for its host app’s global before doing any work (typeof figma !== "undefined", window.editor, window.excalidrawAPI). When the app isn’t on the page (wrong tab, editor still booting, or the deployment doesn’t expose the global), every tool returns the same structured envelope instead of throwing:

{
"ok": false,
"error": "Tldraw not loaded — open the app first OR the surface is not exposed on this version of the app",
"code": "tldraw-not-loaded"
}

The code is stable per adapter (figma-not-loaded, tldraw-not-loaded, excalidraw-not-loaded), so an agent loop can match on it and recover (navigate to the app, wait, retry). The other structured codes the adapters share: bad-arg (missing/invalid argument, checked before the page is touched), eval-failed (the page-side expression threw), plus per-tool codes noted below.

All adapter tools act on the default session: open the app there first (navigate, with no session argument). Each adapter probes a different global, so confirm it exists before the first call: eval_js({expr: "typeof figma"}), eval_js({expr: "typeof window.editor"}) or eval_js({expr: "typeof window.excalidrawAPI"}) should not answer "undefined".

Two equivalent routes:

  • Direct. The tools are plain namespaced MCP tools: figma.get_selection, tldraw.create_shape, … on tools/list. On the SDK: client.plugins.figma.get_selection({}) (typed via each plugin’s schema.d.ts overlay; see Authoring plugins).
  • Via canvas_query. The canvas-substrate dispatcher maps canvas_query({adapter, op, args}) to the <adapter>.<op> registry entry. Useful for adapter-generic agent loops; if the plugin isn’t installed the dispatcher returns the structured code:"no-adapter" error naming the package to install.

The canonical reference plugin. Exercises every primitive of the v1 runtime contract (register(api) entry, namespaced registration, no capabilities, empty dependsOn) and is the fixture the plugin-runtime keystone test loads end-to-end. Install it to smoke-test your plugin wiring; copy it to start your own plugin.

ToolArgsReturns
example.echo{msg: string}{ok: true, result}, echoing msg back (missing msg echoes "").
example.add{a: number, b: number}{ok: true, sum}, with non-numeric args coerced to 0.
example.now(none){ok: true, iso, epochMs}.

Adapts Figma through the page-side figma.* global that Figma exposes in its plugin-iframe context. Targets the long-stable core of Figma’s plugin API: figma.viewport.{center,zoom}, figma.currentPage.selection, figma.getNodeById(), figma.createRectangle(), and mutable x/y/fills on scene nodes.

ToolArgsReturns
figma.get_selection(none){ok, nodes: [{id, name, type, x, y, width, height}]}
figma.get_viewport(none){ok, center: {x, y}, zoom}
figma.select_node{nodeId: string}{ok, nodeId}, or code:"node-not-found"
figma.move_node{nodeId: string, dx: number, dy: number}{ok, nodeId, x, y} (post-move position), or code:"node-not-found"
figma.create_rectangle{x, y, width, height, fillColor?: {r, g, b}} (r/g/b are 0 to 1 floats, Figma’s color convention; width/height must be > 0){ok, nodeId}

Error codes: figma-not-loaded, bad-arg, node-not-found, eval-failed.

Adapts Tldraw v2+ through the window.editor global that Tldraw exposes when an Editor component is mounted. Targets the v2 Editor API: editor.getSelectedShapes(), editor.getViewportPageBounds(), editor.getZoomLevel(), editor.createShapes(), editor.deleteShapes(), editor.setSelectedShapes().

ToolArgsReturns
tldraw.get_selected_shapes(none){ok, shapes: [{id, type, x, y, props}]}
tldraw.get_viewport(none){ok, x, y, w, h, zoom} (page-space viewport bounds)
tldraw.create_shape{type: string, x: number, y: number, props?: object}{ok, shapeId}, id resolved by diffing the page shape list before/after; code:"create-failed" if no new shape appeared
tldraw.delete_shape{shapeId: string}{ok, shapeId}
tldraw.select_shapes{shapeIds: string[]}{ok, shapeIds}

Error codes: tldraw-not-loaded, bad-arg, create-failed, eval-failed.

Adapts Excalidraw 0.17+ through the window.excalidrawAPI global, the imperative ref the Excalidraw React component hands to its host page. The host page has to forward that ref to window.excalidrawAPI for this plugin to find it. Targets excalidrawAPI.getSceneElements(), excalidrawAPI.getAppState(), excalidrawAPI.updateScene().

ToolArgsReturns
excalidraw.get_scene_state(none){ok, elements: [{id, type, x, y, width, height}], appState: {viewBackgroundColor, viewModeEnabled, zoom}}
excalidraw.get_viewport(none){ok, scrollX, scrollY, zoom}
excalidraw.add_element{type, x, y, width, height, ...passthrough} (extra fields forwarded to Excalidraw verbatim; id minted via crypto.randomUUID() when not supplied){ok, elementId}
excalidraw.delete_element{elementId: string}{ok, elementId}, or code:"element-not-found"
excalidraw.set_scroll{scrollX: number, scrollY: number}{ok, scrollX, scrollY}

Error codes: excalidraw-not-loaded, bad-arg, element-not-found, eval-failed.

End-to-end, using Tldraw as the example (the same flow works for any adapter: swap the install name, the URL, and the ops):

Terminal window
# 1. Install the plugin (workspace-rooted; see docs/plugins.md).
browxai plugin install @browxai/plugin-tldraw
# 2. Start the server with eval + canvas enabled: set this in the MCP entry's env.
BROWX_CAPABILITIES=read,navigation,action,human,eval,canvas
// 3. Confirm the plugin loaded.
plugins_list()
// → { "ok": true, "apiVersion": "1.0.0",
// "plugins": [{ "name": "@browxai/plugin-tldraw", "namespace": "tldraw",
// "version": "0.1.2", "trust": "kalebtec",
// "capabilities": ["eval", "canvas"], "dependsOn": [],
// "status": "loaded",
// "tools": ["tldraw.get_selected_shapes", "tldraw.get_viewport",
// "tldraw.create_shape", "tldraw.delete_shape",
// "tldraw.select_shapes"], ... }] }
// 4. Open a page that mounts Tldraw and exposes window.editor, then check it.
navigate({ url: "<your Tldraw page>" })
eval_js({ expr: "typeof window.editor" })
// → { "ok": true, "value": "object" }
// 5. Drive it through the dispatcher (or call tldraw.* directly).
canvas_query({ adapter: "tldraw", op: "get_viewport" })
// → { "ok": true, "x": 0, "y": 0, "w": 1280, "h": 720, "zoom": 1 }
canvas_query({ adapter: "tldraw", op: "create_shape",
args: { type: "geo", x: 128, y: 128 } })
// → { "ok": true, "shapeId": "shape:abc123" }

Per-adapter step 5 equivalents:

// figma: open a page whose main frame defines the figma global first
canvas_query({ adapter: "figma", op: "get_selection" })
canvas_query({ adapter: "figma", op: "create_rectangle",
args: { x: 0, y: 0, width: 200, height: 120,
fillColor: { r: 0.9, g: 0.2, b: 0.2 } } })
// excalidraw: open a page that sets window.excalidrawAPI first
canvas_query({ adapter: "excalidraw", op: "get_scene_state" })
canvas_query({ adapter: "excalidraw", op: "add_element",
args: { type: "rectangle", x: 64, y: 64,
width: 200, height: 120 } })

If a step-5 call returns code:"no-adapter", the plugin isn’t loaded. Check plugins_list() for the status + reason, and remember the runtime is resolved once at server start (install, then restart, then retry). If it returns code:"<adapter>-not-loaded", the plugin is fine but the app isn’t on the current page.

For deeper introspection on one plugin (full manifest, lock pin, transitive deps, registered tool schemas) call plugins_info({name: "@browxai/plugin-tldraw"}).

All four plugins are trust tier kalebtec when installed from the registry: maintained in the browxai monorepo, released through the same OIDC-trusted-publishing pipeline as the host package, npm-provenance signed. A file: development install of one of them shows as local. But the trust tier is a label, not a sandbox: browxai plugins run in-process with full Node access, and the runtime gates every tier identically at capability + call-graph time. Treat installing any plugin, these four included, like adding an npm dependency: review what it declares (browxai plugin info <pkg>), grant only the capabilities it needs, and audit the live set with plugins_list. See Plugin governance for the tier definitions.

Each plugin’s manifest carries a browxaiVersion range (>=0.14.0 today: the oldest host version it was tested against; a mismatch only warns) and a binding apiVersion (the plugin-runtime contract major; see Authoring plugins). The canvas adapters track the upstream app APIs noted per section above; if an upstream app renames a method, the fix is an eval-expression swap in the plugin’s src/index.ts and a patch release.