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.
| Plugin | Namespace | Capabilities | What it adapts |
|---|---|---|---|
@browxai/plugin-example | example | (none) | Nothing. It is the canonical runtime-contract reference. |
@browxai/plugin-figma | figma | eval + canvas | Figma, via the page-side figma.* plugin-context global. |
@browxai/plugin-tldraw | tldraw | eval + canvas | Tldraw v2+, via the window.editor global. |
@browxai/plugin-excalidraw | excalidraw | eval + canvas | Excalidraw 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):
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".
Calling adapter tools
Section titled “Calling adapter tools”Two equivalent routes:
- Direct. The tools are plain namespaced MCP tools:
figma.get_selection,tldraw.create_shape, … ontools/list. On the SDK:client.plugins.figma.get_selection({})(typed via each plugin’sschema.d.tsoverlay; see Authoring plugins). - Via
canvas_query. The canvas-substrate dispatcher mapscanvas_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 structuredcode:"no-adapter"error naming the package to install.
@browxai/plugin-example
Section titled “@browxai/plugin-example”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.
| Tool | Args | Returns |
|---|---|---|
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}. |
@browxai/plugin-figma
Section titled “@browxai/plugin-figma”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.
| Tool | Args | Returns |
|---|---|---|
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.
@browxai/plugin-tldraw
Section titled “@browxai/plugin-tldraw”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().
| Tool | Args | Returns |
|---|---|---|
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.
@browxai/plugin-excalidraw
Section titled “@browxai/plugin-excalidraw”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().
| Tool | Args | Returns |
|---|---|---|
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.
Usage walkthrough
Section titled “Usage walkthrough”End-to-end, using Tldraw as the example (the same flow works for any adapter: swap the install name, the URL, and the ops):
# 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 firstcanvas_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 firstcanvas_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"}).
Trust note
Section titled “Trust note”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.
Versioning
Section titled “Versioning”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.