Skip to content

Manifest reference

manifest.json is the declaration half of a plugin (the other half is the main.ts entry — see Sandboxed packages). It is validated on discovery; an invalid manifest is rejected with a typed error and never partially registered. The published JSON Schema (https://schemas.lowkeyagents.com/plugins/manifest.schema.json, in-repo packages/shared/src/plugins/manifest.schema.json) and Lowkey’s TypeScript validator are parallel sources of truth.

The manifest has two families of fields: capabilities (host-run grants for the Deno jail — the delivery model) and the declarative-seam blocks (surface/mcp/webhook/agent) selected by scopes. A web-installable package ships a main.ts and typically uses capabilities; a manifest that uses only declarative seams is valid but operator-sideload only.

Field Type Required Notes
id string yes Unique, kebab-case (^[a-z0-9]+(?:-[a-z0-9]+)*$). Must equal the plugin’s directory name.
version string yes Semver (MAJOR.MINOR.PATCH, optional pre-release/build).
name string yes Display name. Non-empty.
scopes string[] yes Closed set, unique items. See below.
surface object no Mount-point placement. Requires the matching slot scope.
launch object no Header launch-entry metadata for panel plugins.
mcp object no MCP server to inject. Requires the mcp scope.
webhook object no Inbound trigger source. Requires the webhook scope.
agent object no Agent contribution. Requires the agent scope.
capabilities object[] no Host-run grants for the Deno jail (the delivery model). Present on any package with a main.ts. See Sandboxed packages.
signing object no Signature fields. Present but unenforced in v1.

Unknown top-level keys and unknown keys inside any block are rejected.

scopes is an enumerated, closed array. An undeclared capability is denied by the scoped token at request time; declaring a seam block without its scope fails validation.

Scope Grants
cards Emit closed cards into the session feed.
mcp Register an MCP server, injected per project at agent launch.
webhook Register an inbound webhook source; subscribe to turn-completion events.
focus Mount a surface in the focus slot.
side-panel Mount a surface in the side-panel slot.
agent Contribute an AgentDef (system prompt, prompt mode, translator, runtime).
session-read Read a session’s transcript + lineage.

media and storage are not in v1 (deferred — see Compatibility).

A seam block requires its scope. Validation enforces:

Block present Scope required
mcp mcp
webhook webhook
agent agent
surface with slot: "focus" focus
surface with slot: "side-panel" side-panel

A validated manifest guarantees this coupling. Downstream seams nonetheless re-check defensively — MCP registration re-tests the mcp scope before injecting a server, and every scoped HTTP route re-runs the scoped-token guard (token → project → registration/enablement → scope) on each request. Validation is the first gate, not the only one.

Declares which host-owned mount point the plugin’s iframe fills. The host draws the chrome; the plugin fills the rectangle. See surfaces.

Field Type Required Notes
slot "focus" | "side-panel" yes Closed set.
url string yes Absolute http/https URL of the served surface.
"surface": { "slot": "side-panel", "url": "https://plugin.example.com/panel" }

Header launch-entry metadata. Panel plugins only (those with a surface) get a header icon next to the focus-pane button; headless plugins are managed on the Plugins settings page and have no icon.

Field Type Required Notes
icon string yes v1 renders a generic icon for every panel plugin and ignores this value; per-plugin launch.icon → glyph mapping is reserved for a future version. Still required when launch is present.
label string yes Used as the icon’s tooltip + accessible label. Falls back to name if launch is absent.

An MCP server config injected per project at agent launch, the same path as built-in MCP servers, keyed by plugin id. See MCP registration.

Field Type Required Notes
command string yes Executable to spawn (stdio transport).
args string[] no Arguments.
env object<string,string> no Environment variables.

Registers an inbound trigger source. A POST /api/webhook/<source> carrying the plugin’s scoped token spawns an agent turn. See webhook source.

Field Type Required Notes
source string yes The <source> segment in the webhook path. Must be unique enough to route.

Contributes an AgentDef. See agent contribution and the external protocol.

Field Type Required Notes
agentId string yes Roster id of the contributed agent.
systemPrompt string yes The agent’s own system prompt.
platformPromptMode "full" | "minimal" yes minimal keeps the safety floor, drops productivity instructions. No none in v1.
translator "none" | "default" yes none = raw agent output, no post-turn rewrite.
runtime string yes A built-in runtime id, or external:<pluginId>.
endpoint string conditional Required when runtime starts with external:. Absolute http/https URL Lowkey POSTs turns to.
"agent": {
"agentId": "muse",
"systemPrompt": "You are Muse, a warm conversational companion…",
"platformPromptMode": "minimal",
"translator": "none",
"runtime": "external:muse",
"endpoint": "https://muse.example.com/turn"
}

A closed, enumerated array of host-run grants for the Deno jail — the capabilities your main.ts may use. This is the plugin delivery model; an author-hosted service is a main.ts shim that declares net.egress to its own domain. Omit capabilities only for a manifest-only plugin that uses solely declarative seams (operator-sideload only). The full vocabulary (net.egress, fs.project, fs.project:write, events.turn, mount.focus/mount.side-panel, cards.emit, session.read, session.fork, token, run.agent), each entry’s shape, the consent-install flow, and the sandbox enforcement are documented in Sandboxed packages. Every capability id and its per-entry fields are in the published schema.

Carries publicKey and signature. Designed-for but unenforced in v1 — integrity verification lands with the catalog. Include the fields; do not expect enforcement.

Lowkey discovers plugins from two roots:

  • Global~/.lowkey/plugins/<id>/
  • Per-project<project>/.lowkey/plugins/<id>/

Rules:

  • The directory name must equal the manifest id; the loader enforces it.
  • Duplicate ids are rejected, not shadowed. If the same id appears twice — in both roots, or twice in one — the first discovered (the per-project root is scanned before the global one) is registered and the duplicate is rejected with a clear error (plugin id "<id>" is already registered for this project). The rejection is not silent: it is returned in the rejected[] array of the GET /api/plugins response (alongside plugins[]) so the UI surfaces it, and enabling an id that resolves only to a rejected manifest returns 400 with the error. Lowkey never silently picks one of two same-id plugins.
  • A plugin is discovered but disabled by default. Enablement is separate per-project state in <project>/.lowkey/plugin-state.json, toggled via POST /api/plugins. Only enabled plugins contribute MCP servers, mount points, and agents — and only an enabled plugin is issued a scoped token.

Manage all of this from /api/plugins (list/enable/disable/configure), surfaced in the Plugins settings page and the per-project enable section.