Overview

PaneScale is a local desktop app, so its “API” is local too. There are four places where you can build on it:

SurfaceWhat it is forWhere it runs
MCP bridgeLet any MCP client read and change the task board, list workflows, notify you and call your managed MCP connections127.0.0.1, off by default
Workflow stepsChain agents, model prompts, commands, browser checks, Git review, MCP tools and approvalsInside PaneScale
Video scenesWrite product videos as small canvas functions on a timelineInside PaneScale
Status signalsMake any CLI report “working”, “needs you” and “done” to its paneYour terminal

MCP bridge

The bridge is a real MCP server using the Streamable HTTP transport with plain JSON responses. It only listens on the loopback interface and is off until you turn it on in Settings → MCP bridge.

PropertyValue
EndpointPOST http://127.0.0.1:<port>/mcp
PortThe first free port from 41920 to 41929 — shown in Settings
AuthAuthorization: Bearer omcp_… — a 43-character token, compared in constant time
Limits1 MB request body; requests carrying a browser Origin header are refused
TokenStored in a token file in your user profile; regenerate it any time in Settings

Connect

Copy the URL and token from Settings → MCP bridge. For Claude Code:

Claude Code
claude mcp add --transport http panescale http://127.0.0.1:41920/mcp \
  --header "Authorization: Bearer omcp_your_token"

For clients that read a JSON configuration:

mcp.json
{
  "mcpServers": {
    "panescale": {
      "type": "http",
      "url": "http://127.0.0.1:41920/mcp",
      "headers": { "Authorization": "Bearer omcp_your_token" }
    }
  }
}

Tools

ToolArgumentsNotes
panescale_list_tasksstatus?One of ready, running, needs_review, blocked, failed, stopped, done
panescale_create_tasktitle, description?, labels?, project?Creates a card on the board
panescale_move_taskid, status, summary?Any column except done — only a person finishes a card
panescale_list_workflows—Saved workflows with their ids
panescale_notifytitle, text?Shows a notification in PaneScale
panescale_call_connectionconnection, tool, arguments?Calls a tool on one of your managed MCP connections

A raw call looks like any other MCP request:

curl
curl -s http://127.0.0.1:41920/mcp \
  -H "Authorization: Bearer omcp_your_token" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"panescale_create_task","arguments":{"title":"Add an empty state to uploads","labels":"ui"}}}'

Workflow steps

A workflow is a brief plus up to 16 steps, run left to right. Every step can store its output under a key; later steps reference it as {{key}}.

KindRunsInputDefault timeout
agentClaude Code or Codex, optionally allowed to editInstruction30 min
modelA prompt to Ollama or your own endpointPrompt5 min
commandA shell command in the workspaceCommand line3 min
browserFetches a page and returns its title and a text preview (30 s, 200 KB)URL3 min
gitChecks the working tree diff—3 min
mcpCalls a tool on a managed connectionConnection, tool, arguments3 min
approvalWaits for your decision——

Steps can run in parallel groups, but not approvals or editing agents — those always run alone. A step can have a condition on an earlier output: contains, not_contains, equals or not_empty. Outputs can be validated as JSON.

Triggers: manual, interval or git_change (a new commit in the workspace).

Video scenes

A video is a folder: timeline.json plus one file per scene in scenes/<id>.js. Each scene file is the body of a function that draws one frame onto a 2D canvas. The background is already filled with theme.bg; the camera, transitions and captions are applied around your code.

scenes/title.js
// Parameters: ctx, t, w, h, theme, u — t runs from 0 to 1 within the scene.
u.backdrop({ grid: true });

// Words rise in one after another; "|" breaks the line.
u.words('Ship it|tonight', w / 2, h * 0.45, u.seg(t, 0.05, 0.5), {
  size: u.min * 0.11, weight: 760, align: 'center', maxWidth: w * 0.86,
});

// A gradient underline that grows in, then a counter.
const lw = u.min * 0.22 * u.easeInOut(u.seg(t, 0.35, 0.6));
ctx.fillStyle = u.grad(w / 2 - lw / 2, 0, w / 2 + lw / 2, 0);
u.rr(w / 2 - lw / 2, h * 0.62, lw, u.min * 0.008, u.min * 0.004);
ctx.fill();
u.text(u.count(20000, u.seg(t, 0.4, 0.9), { suffix: '+ teams' }), w / 2, h * 0.72, {
  size: u.min * 0.04, color: theme.muted, align: 'center',
});
NameMeaning
themebg, surface, text, muted, accent, accent2 (brand colors already applied)
u.seg(t, a, b), u.stagger(t, i, n)Progress between two moments · staggered entrances
u.easeOut, u.easeInOut, u.backOut, u.spring, u.elasticEasing curves on 0…1
u.backdrop(), u.panel(), u.grad()Lit background with grid and vignette · card with shadow · gradient
u.text(), u.words(), u.type(), u.count()Text · word-by-word reveal · typewriter · counter
u.browser(), u.phone(), u.terminal(), u.code(), u.cursor()Device frames that return their content area, a typing shell and editor, a clicking pointer
u.icon(name, x, y, size)check, bolt, star, play, arrow, shield, clock, spark, grid, chat, chart, lock
u.min, u.vw(p), u.vh(p), u.portraitSizes relative to the frame — lay out 9:16 differently with u.portrait
u.rand(n), u.noise(x)Deterministic randomness
u.sec, u.dur, u.frame, u.fpsSeconds into the scene, its length, absolute frame, frame rate

The timeline itself is plain JSON:

timeline.json
{
  "format": "16:9",
  "height": 1080,
  "fps": 30,
  "theme": "graphit",
  "brand": { "accent": "#1463ff" },
  "captions": true,
  "caption_style": "kinetic",
  "sfx_auto": true,
  "scenes": [
    {
      "id": "title",
      "kind": "type",
      "title": "Title",
      "duration_secs": 3.4,
      "transition": "zoom",
      "transition_secs": 0.5,
      "caption": "Every agent in one window",
      "camera": [{ "at": 0, "zoom": 1 }, { "at": 3, "zoom": 1.12, "x": 0.04, "ease": "inout" }],
      "sfx": [{ "at": 1.2, "type": "pop", "gain": 0.6 }],
      "code": ""
    }
  ],
  "notes": [{ "id": "n1", "at": 2.4, "text": "Logo bigger" }]
}
  • format: 16:9, 9:16, 1:1 or 4:5 · height: the short side, 720 to 2160 · fps: 24 to 60
  • theme: nacht, graphit, aurora, ozean, wald, sonne, hell, papier · brand overrides single colors
  • transition (how a scene enters, overlapping the one before): cut, fade, dip, push, slide, zoom, whip, wipe, circle, sweep, glitch, blur · direction: left, right, up, down
  • camera keys: x/y as a share of the frame, zoom (1 = none), rotate in degrees, ease: linear, out, inout, spring
  • sfx: whoosh, riser, impact, click, pop, swell, tick, chime — generated locally, no audio files
  • caption_style: kinetic, plain or bold · kind: motion, type, chart, code, device, end

Scenes must be deterministic (no Math.random, Date.now or performance.now — use u.rand) and must not call ctx.setTransform, because the camera uses the transform. During export, numbers and typed text are frozen per frame so motion blur never smears digits.

Status signals

PaneScale shows each pane as working, waiting for you or done. Claude Code reports through hooks that PaneScale configures for its own panes. For every other CLI, these signals work:

  • OSC 9 and OSC 777 terminal notifications — many CLIs already send them when they need input or finish a task.
  • Prompt detection on the visible screen as a fallback for text-mode CLIs.

If you build your own CLI, send a desktop notification escape sequence when you need the user:

Notify the pane
printf '\033]9;Waiting for approval\007'
Missing something you need? Tell us.

Wire PaneScale into your stack.

Turn on the bridge and give your agents a shared board.