Developers

Plugin API

Author a sandboxed web plugin that reaches meith capabilities only through approved, audited grants.

A meith plugin is a web app that runs inside a controlled plugin browser tab. It does not load code into the main process, does not get Node access, and does not register tools into the host.

Instead, the host may expose window.meithPlugin in that plugin tab. The bridge contains only the API namespaces the user approved, and every privileged action routes back through the main-process tool registry. See templates/plugin-basic/main.js for a starter.

Lifecycle

  1. A plugin is installed from a local directory, packaged archive, or development URL.
  2. The host reads the manifest and stores its requested grants.
  3. The plugin starts disabled with empty approved grants.
  4. The user reviews and approves a subset of the requested API namespaces and tool capabilities.
  5. The plugin can be enabled only after its requested API namespaces are approved.
  6. Opening the plugin creates a plugin-mode browser tab; the main process maps that tab's webContents.id to the plugin id.

Identity cannot be forged

Identity is always resolved from the sender webContents. The plugin page cannot forge another plugin id or grant itself extra permissions.

Manifest

The manifest can be in plugin.json at the plugin root or in the meith field of package.json.

json
{
  "kind": "plugin",
  "id": "com.example.hello",
  "name": "Hello Plugin",
  "version": "0.1.0",
  "description": "Shown in the permissions review UI.",
  "entry": "index.html",
  "permissions": ["read-only"],
  "requestedApis": ["tools", "storage"]
}

permissions and requestedApis are requests, not grants. Runtime enforcement uses only approvedGrants, never the requested values. Approving grants always intersects the supplied grants with the requested grants, so approval can never exceed the manifest.

API namespaces

The bridge shape is exported from @meith/protocol as MeithPluginApi. Namespaces are optional and should always be feature-detected.

NamespaceProvides
identityApproved plugin id, name, version, APIs, and capabilities. Always present.
toolslist() and call() against the registry, gated by approved capabilities.
storageRead-only browser and workspace tab listings.
cdpSend Chrome DevTools Protocol commands, following the tab-ownership model.
aistreamText() through an ephemeral agent session.

tools

Tool calls are still gated by approved capabilities. A plugin with the tools API but without controls-browser cannot call a browser-control tool — the result is a normal ToolResult failure with PERMISSION_DENIED.

ai

AbortSignal is not used across the context bridge because it is not cloneable. The plugin receives cancellation through onStart.

js
const result = await window.meithPlugin.ai.streamText({
  prompt: "Summarize my open tabs.",
  onStart: (controls) => {
    cancel = controls.cancel
  },
  onText: (delta) => {
    output.textContent += delta
  },
})

Security model

  • Plugin tabs run with context isolation and no Node integration.
  • window.meithPlugin is injected only by the plugin preload, and identity is resolved from webContents.id.
  • API namespaces are present only when approved, and tool calls are checked against approved capabilities on every call.
  • A disabled or uninstalled plugin loses live tab authority; navigating away revokes the mapping.
  • Local/package entries are realpath-contained, and archives are extracted with path traversal and link checks.

Control-plane tools

Plugin management is itself exposed through normal tools, surfaced in the Settings Plugins panel and the CLI.

ToolPurpose
list_pluginsList installed plugins, grants, and enabled state.
install_pluginInstall from directory, archive, or devUrl.
approve_plugin_grantsApprove a subset of requested capabilities and APIs.
set_plugin_enabledEnable or disable an installed plugin.
uninstall_pluginRemove the plugin and revoke open plugin tabs.
open_plugin_tabOpen an enabled plugin in a plugin-mode browser tab.
bash
# install from a dev server, then approve and enable
meith call install_plugin --devUrl http://localhost:5173/
meith call approve_plugin_grants \
  --pluginId com.example.hello \
  --capabilities-json '["read-only"]' \
  --apis-json '["tools","storage"]'
meith call set_plugin_enabled --pluginId com.example.hello --enabled true
meith call open_plugin_tab --pluginId com.example.hello