hyperpanes-mcp
An MCP server for the hyperpanes terminal-workspace app: it composes and launches tiled workspace layouts without a running app, and — when the app's loopback control API is enabled — inspects, streams, and drives a live instance (panes, panes-as-agents, tokens/locks, a durable work queue).
Compose & launch (no app needed): list layouts, validate specs, build canonical workspace JSON (optionally write to file), launch via lossless temp file or compiled CLI flags (
HYPERPANES_BIN).Inspect a running app:
control_status,list_panesacross windows/tabs with activity + org metadata,whoami,read_pane(raw / rendered-screen grid / Claude transcript tail; tail, strip, byte-cursor deltas,waitForIdle),read_messages.Drive panes/tabs: open panes (cwd/env/args/meta/project) and tabs,
spawn_workers, set layout, focus/close/restart, rename/recolor pane & tab, move live panes,set_meta.Send input (triple-gated):
send_inputtext,send_keysnamed keys,prompt_paneone-call type→submit→wait→read turn.Agent orchestration: durable per-pane inboxes (send / to-parent / subtree broadcast), subtree-scoped
mint_token, advisorylock_pane/unlock_pane, hierarchy asmeta.parent.Durable work queue: enqueue/claim/ack/nack/extend/get/list/purge with leases and fencing tokens; runner panes drain a queue, with optional git-worktree isolation, CLAUDE_CONFIG_DIR account rotation, stream-json progress, and log files.
Projects: list/add/rename/recolor/remove remembered directories and open panes into them.
Speech (TTS): per-pane talk of new Claude replies, global mute / focused-only, stop speech.
Lifecycle & durable prompts:
restart_app(GUI-only keeps panes; "phoenix" full restart snapshots and resumes all Claude conversations) andqueue_promptto deliver a message to a Claude session across restarts.Live streams: subscribable resources for pane output and inboxes (
resources/updatednotifications).
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@hyperpanes-mcp launch a workspace with two panes, one for code and one for terminal"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
hyperpanes-mcp
An MCP server for hyperpanes — the Electron terminal-workspace app (one window of tiled, labeled, color-framed terminal panes). It lets an agent compose and launch workspace layouts and drive a running instance — read, spawn, and arrange panes, stream their output, and (guarded) type into live shells — from Claude, Cursor, or any MCP client.
It works at two levels, and you can use either without the other:
Level | Needs a running app? | What it does |
Compose & launch | No | Generate/validate a workspace config and shell out to |
Live control | Yes | Talk to the app's loopback control API to inspect/drive panes and stream output. |
Live control is off by default. The app's control API is a loopback (
127.0.0.1), token-authenticated server that only listens once you enable Preferences → "Allow agent control" in hyperpanes. Typing into shells (send_input) is gated further still — see the send_input safety model.
Tools
Compose & launch (no running app needed)
Tool | Description |
| List the tab layouts ( |
| Validate a workspace spec against the schema. Returns |
| Validate + return canonical workspace JSON; optionally write it to a |
| Launch |
Inspect a running instance
Tool | Description |
| Is the control API reachable? Reports app pid/version, whether the app allows input, the bridge-side |
| All panes across windows/tabs, with status, activity (busy/idle/exited), org metadata, tab/window context ( |
| A pane's terminal output. |
| Drain a pane's durable message inbox past a cursor ( |
| Identify the pane this bridge is running inside ( |
Drive panes
Tool | Description |
| Open a new pane beside the caller: in the calling pane's own tab (from |
| Attach one or more new tabs to a window, each with fresh shells; returns the new tab ids in the caller's window ( |
| Set a tab's tiling layout (defaults to the first window's active tab). |
| Focus a pane (and its tab/window). |
| Close a pane, terminating its shell. |
| Kill and respawn a pane's shell. |
| Change a pane's label and (optionally) subtitle, live. |
| Change a pane's frame color, live (any CSS color). |
| Change a tab's title, live. Address the tab by the |
| Set a tab's colour (a stripe and tint on its tab chip), live and saved with the session. |
| Move a live pane to another tab or window without restarting it (same pty, session, |
| Attach/update a pane's free-form metadata (merge; |
| Turn per-pane talk on/off — while enabled, the pane speaks NEW Claude assistant replies aloud via local TTS. Off by default. |
Speech (TTS)
Global controls over the app's text-to-speech output (talk is opted into per-pane via set_talk above).
Tool | Description |
| Set global speech settings: |
| Immediately kill any in-flight speech utterance and clear the backlog. |
Send input
All three type into a live shell — they run whatever you send in a real terminal. Triple-gated and never on by default. See the safety model.
Tool | Description |
| Type text. |
| Send named keys as the right terminal bytes: |
| One full turn in one call: type → submit → wait for the pane to settle → return the rendered transcript + whether it's now |
Driving an interactive TUI agent
Two supported patterns for talking to an agent running inside a pane (e.g. a live claude):
Structured bus (preferred when the agent is MCP-capable). If the pane-agent also has the hyperpanes MCP, converse over its inbox (
send_message/send_to_parent/read_messages) — a clean, structured channel, no screen-scraping. Run such workers with an inbox-poll loop ("listening agent") so they pick messages up unprompted.TUI scrape (for any agent, incl. an interactive
claudethat won't poll its inbox). Drive the terminal directly. The one-call path isprompt_pane; under the hood that'ssend_input({ submit:true })to type a line,read_pane({ waitForIdle:true })to block until the reply lands, andread_pane({ mode:"screen" })to read it back cleanly. Usesend_keys(["enter"])to clear a first-run trust dialog, and watchawaitingInputon the result to know when the agent is blocked on a prompt rather than done.
Agent orchestration
Turn the control plane into a substrate for an LLM agent org — one orchestrator driving
worker panes, or a recursive manager→worker tree. Hierarchy is data (meta.parent), the
message bus is hierarchy-agnostic, and tokens scope what a child can reach.
Tool | Description |
| Enqueue a structured message to a pane's durable inbox (at-least-once delivery). |
| Message this pane's org parent (resolved from |
| Message every pane in an org subtree (all panes whose |
| Mint a subtree-scoped control token (no escalation) to hand a child via |
| Take an advisory write lock so only the holder can |
| Release an advisory write lock you hold. |
Work queue
A durable, SQLite-backed job queue behind the control server — fan work out to competing
workers with at-least-once delivery and leases. A claim returns each task's fencingToken;
every ack/nack/extend must carry it (lease fencing, so a reaped worker can't clobber a
task another worker now owns).
Tool | Description |
| Every queue and its per-state depth. |
| Append a task ( |
| Claim the next task(s) for a |
| Complete a claimed task (carry the |
| Fail a task — re-queued until |
| Heartbeat a long task: extend its lease so it isn't reaped. |
| Fetch one task (state, payload, attempts, lease, timestamps). |
| List/inspect a queue's tasks with a |
| Drop terminal tasks (done/failed/dead) for cleanup. |
Related MCP server: cmuxlayer
Resources
Pane output and inboxes are exposed as subscribable MCP resources — read for a snapshot,
subscribe for a live stream (the bridge consumes the app's /events WebSocket and emits
resources/updated / resources/list_changed notifications):
Resource URI | Content |
| Terminal output — scrollback on read, deltas on subscribe ( |
| The pane's durable message inbox — JSON on read, live deliveries on subscribe. |
Installation
The server runs over stdio and is launched by your MCP client.
Claude Desktop / generic MCP config
{
"mcpServers": {
"hyperpanes": {
"command": "npx",
"args": ["-y", "hyperpanes-mcp"],
"env": {
"HYPERPANES_BIN": "C:/path/to/hyperpanes.exe"
}
}
}
}HYPERPANES_BIN is only needed for launch_workspace; the live-control tools find the app
via its control.json (see Configuration).
Claude Code
claude mcp add hyperpanes -- npx -y hyperpanes-mcpInstall globally
npm install -g hyperpanes-mcp
hyperpanes-mcp # runs the stdio serverAlso published to GitHub Packages as @eyalm321/hyperpanes-mcp.
Configuration
All variables are optional. launch_workspace needs a launcher; the live-control tools need
the app running with "Allow agent control" enabled.
Env var | Purpose |
| Path to the hyperpanes executable (for |
| Whitespace-separated leading args for the launcher (e.g. a dev runner). |
| Override the path to the app's |
| Override just the userData dir; |
| A scoped control token + port for a child pane (set automatically by |
| The pane this bridge runs inside — enables |
|
|
| Comma-separated pane ids or labels allowed to receive input. |
Default control.json locations:
Windows:
%APPDATA%\hyperpanes\control.jsonmacOS:
~/Library/Application Support/hyperpanes/control.jsonLinux:
$XDG_CONFIG_HOME/hyperpanes/control.json(or~/.config/hyperpanes/…)
send_input safety model
send_inputtypes into live shells — it runs whatever you send in a real terminal. It is the sharp edge of this server and is never on by default. Three independent gates, all required:
App-side (enforced by hyperpanes): the control server is loopback + token, disabled by default, and
send_inputreturns 403 unless "Allow agent control → input" is on. The bridge cannot bypass this.Bridge opt-in: refused unless
HYPERPANES_ALLOW_INPUT=1is set in this server's environment. OptionallyHYPERPANES_INPUT_ALLOWLISTrestricts which panes accept input.Per-call confirmation: every call must pass
confirm: true.
control_status surfaces all three (appAllowsInput + inputGate) so a refusal is always
explainable.
Workspace schema
A faithful mirror of the app's WorkspaceFile. The canonical shape is nested; the legacy
single-window fields are kept for back-compat, and everything normalizes through one
windowsOf funnel (windows[] verbatim → groups[] as one window → panes[] as one window/tab).
WorkspaceFile { name?, layout?, panes?, groups?, active?, windows? }
WindowSpec { title?, active?, bounds?, groups[] }
GroupSpec { title?, color?, layout?, panes[], sizes?, mainFraction?, focused?, zoomed? } // a tab
PaneSpec { label?, subtitle?, color?, command?, cwd?, shell?, fontSize? }
Layout = auto | single | columns | rows | grid | main-stackLaunch modes.
launch_workspacedefaults to writing a temp.json(lossless).mode:"cli"compiles to--window/--tab/-c …flags — convenient but lossy: window bounds, the active-tab index, pane subtitle, split sizes, tab colour, and command-less panes are JSON-only and reported inlossy.Relative
cwd. In a workspace file, relativecwdresolves against the file's dir. Inline specs are written to a temp file — prefer absolutecwdfor inline specs.Strict validation. Unknown keys are rejected (typo guard),
layoutmust be a known id,fontSizea positive integer, a tabcolor"#rrggbb"/"#rgb"(ornull), and a workspace must declare at least one pane.
See examples/dev.workspace.json for a full two-window spec.
Development
npm install
npm run build # tsc -> dist/
npm test # vitest (pure units; no running app needed)
npm run test:watch
npm run dev # tsx src/index.ts
node scripts/smoke.mjs # end-to-end stdio check (no app needed)The unit tests mirror the app's own workspace.test.ts / control read-model cases, so a
contract drift in the app surfaces here as a test failure.
Architecture
src/
index.ts # stdio entrypoint
server.ts # creates the MCP server; registers compose/launch tools + wires control tools
schema.ts # workspace schema (zod) + windowsOf/summarize — mirrors the app's workspace.ts
compile-cli.ts # WorkspaceFile -> hyperpanes CLI argv (inverse of the app's parseCli)
launch.ts # launcher resolution + launch planning/execution
control-tools.ts # live-control + orchestration tools, and the subscribable pane resources
control/
discovery.ts # locate + parse control.json (and scoped-token env)
client.ts # HTTP client for the control API (state/output/input/command/messages/tokens/locks)
model.ts # read-model types + pure helpers (flatten, resolve, URIs, whoami, subtree)
subscriptions.ts# /events WebSocket -> MCP resource notifications
input-gate.ts # send_input gating (opt-in + confirm + allowlist)
scripts/smoke.mjs # end-to-end stdio check
examples/ # sample workspace filesReleasing
CI runs the build + tests on every push and PR to main (Node 20 & 22). Publishing is
triggered by creating a GitHub Release, which publishes to both registries:
npm as the unscoped package
hyperpanes-mcpGitHub Packages as
@eyalm321/hyperpanes-mcp
One-time repo setup
Add an
NPM_TOKENrepository secret (an npm automation token).GITHUB_TOKENis provided automatically for GitHub Packages.To release:
npm version <patch|minor|major>, push with--follow-tags, then create a GitHub Release for the tag (e.g.v0.1.1). Thepublishworkflow builds, tests, and publishes to both registries.
License
MIT © Eyalm321
GitHub sources
Turn labelled GitHub issues into queued tasks, and sync task status back to the issue (comments and hp:* labels). Opt-in per repo via a config file; issue text is treated as untrusted. Design and contract: docs/github-sources.md.
# ingest: queue open issues labelled `agent-ready` (use --dry-run to preview)
node dist/backlog/cli.js --github-issues acme/widgets [--label agent-ready] [--github-config p] [--dry-run]
# ingest: queue the newest failed GitHub Actions run per workflow+branch as a `ci_failure` task
# (needs "ci": {"enabled": true} for the repo; may be combined with --github-issues for the same repo)
node dist/backlog/cli.js --github-ci acme/widgets [--github-config p] [--dry-run]
# sync-back: reconcile queue state onto the issues
node dist/github/cli.js sync [--repo acme/widgets] [--once | --watch 300] [--dry-run]Auth: GITHUB_TOKEN, then GH_TOKEN, then gh auth token.
Draft PRs on completion (opt-in)
With "pr": {"enabled": true, "checkout": "/path/to/clone"} for a repo, sync opens a draft PR for each finished GitHub task (issue or CI) that left commits on its branch. pr.checkout is required when pr.enabled is true. Defaults: draft: true, review: true, remote: "origin".
The branch is pushed with a plain
git push <remote> <local>:refs/heads/hp/issue-<n>-<id8>(hp/ci-<runId>-<id8>for CI), never--force, using the checkout's existing git credentials. The remote must point at the opted-in repo.With
review: truethe localclaude -pjudge reviewsgit diff <base>...<branch>first; ablockverdict means no push and no PR. The verdict is cached, so the judge does not re-run.A PR that already exists for the branch (open or closed) is reused, not duplicated.
Merge is a human decision. The tool has no merge, auto-merge or approve capability.
The
doneissue comment is posted once the PR step reaches a final outcome and links the PR. CI tasks keep their link in the PR body. Failures are retried next cycle and never posted to GitHub.CI verdicts and outcomes are remembered in
$XDG_STATE_HOME/hyperpanes-mcp/github-sync-state.json(default~/.local/state/...).--dry-runprintswould review,would push …,would open draft PR "…"and does none of them.
Available Tools
50 toolsack_taskAck (complete) a taskA
Mark a claimed task done. Must carry the fencingToken from the claim (lease fencing). result is an optional stored outcome string.
Inputs: taskId (required), fencingToken (required), result.
add_projectAdd projectA
Remember a directory as a project (the programmatic equivalent of the sidebar PROJECTS + button). The path must exist and be a directory; a git repo is NOT required. Adding an already-known directory is a no-op — returns added:false with the existing entry. Returns the project (id/path/name/color).
Inputs: dir (required).
broadcast_subtreeBroadcast to my subtreeA
Send a message to every pane in an org subtree — all panes whose meta.parent chain leads back to root (agent-orchestration E). root defaults to HYPERPANES_PANE_ID. Returns the list of recipients.
Inputs: body (required), from, root.
build_workspaceBuild workspaceA
Validate a workspace spec and return canonical workspace JSON. Optionally write it to path (a .json file). Also returns the equivalent hyperpanes CLI command when the spec is losslessly expressible as flags (otherwise notes which fields are JSON-only).
Inputs: spec (required), path.
claim_taskClaim task(s)A
Claim the next task(s) off a queue for worker (competing consumers — each task goes to exactly one claimer). An empty queue returns {tasks:[]} (not an error). Each returned task carries a fencingToken and visibilityDeadline — keep them: ack/nack/extend need the token, and the task returns to the queue if you do not ack/extend before the deadline. count prefetches several; leaseMs overrides the task default lease.
Inputs: queue (required), worker (required), count, leaseMs.
close_paneClose paneB
Close a pane, terminating its shell.
Inputs: paneId (required).
control_statusControl statusA
Check whether the hyperpanes control API is reachable and whether input is allowed. Reports the app pid/version (and build, when the app reports one), the bridge-side send_input gate, and the control.json path. Call this first if other control tools fail.
Inputs: no named parameters.
enqueue_taskEnqueue a taskA
Append a task to a work queue (created on first use). payload is the opaque task body (often JSON) a worker reads after claiming. Optional: kind/title for triage, priority (higher runs sooner), maxAttempts (auto re-queue up to N on nack/lease-expiry), visibilityTimeoutMs (default lease), delayMs (schedule into the future), dedupeKey (collapse duplicate enqueues), and — for the goals system — goalId (tag the task to a goal) and dependsOn (task ids that must all reach done before this task becomes claimable; a DAG gate). Returns the new task id + seq.
Inputs: queue (required), payload (required), kind, title, +7 more.
extend_taskExtend a task leaseA
Heartbeat a long-running task: extend its lease by extraMs so it is not reaped and handed to another worker. Must carry the claim fencingToken.
Inputs: taskId (required), extraMs (required), fencingToken (required).
focus_paneFocus paneC
Focus a pane (and its tab/window).
Inputs: paneId (required).
get_taskGet a taskA
Fetch one task by id (state, payload, attempts, lease, timestamps). Returns task:null if it does not exist.
Inputs: taskId (required).
launch_workspaceLaunch workspaceA
Launch hyperpanes with a workspace. Provide either path (an existing .json) or spec (a workspace object). Requires a launcher: pass launcher or set the HYPERPANES_BIN env var (hyperpanes has no PATH binary). mode defaults to "file" (writes a temp .json — lossless); "cli" compiles to flags (drops JSON-only fields like bounds/active/subtitle).
Inputs: mode, path, spec, launcher.
list_layoutsList layoutsA
List the tab layouts hyperpanes supports (auto, single, columns, rows, grid, main-stack) with descriptions. Use these ids for a group/tab layout.
Inputs: no named parameters.
list_panesList panesA
List all panes across all windows/tabs of the running hyperpanes instance, with status, activity (busy/idle/exited liveness heuristic), any org metadata (role/parent/agentType/task), tab/window context (tabId, tabTitle, tabColor, tabStatus: working iff any pane in the tab is busy, else idle), and the resource URI for streaming each pane's output.
Inputs: no named parameters.
list_projectsList projectsA
List the projects (remembered directories) the running hyperpanes instance tracks — its sidebar rail — newest-opened first. Each has an id, absolute path, name, frame color, and lastOpenedAt. Use an id (or name) with rename_project/recolor_project/remove_project, or as open_pane's project option to open a pane in it.
Inputs: no named parameters.
list_queuesList work queuesA
List every work queue and its depth (pending/claimed/etc. counts). Scope-filtered to the token. The work queue is the durable job queue behind the control server; use it to fan work out to competing workers.
Inputs: no named parameters.
list_tasksList tasks in a queueA
List/inspect tasks in a queue. Optional state filter (queued/claimed/done/failed/dead), after (seq cursor — pass a prior result's latest seq to page) and limit (default 100, max 1000). Returns the tasks plus per-state counts.
Inputs: queue (required), after, limit, state.
lock_paneLock a pane for writingA
Take an advisory write lock on a pane (agent-orchestration H) so only this owner can send_input until it expires or is released. Advisory: an unlocked pane is writable by anyone. Renew by acquiring again as the same owner. Pass the same owner to send_input while holding it.
Inputs: owner (required), paneId (required), ttlMs.
mint_tokenMint a scoped tokenA
Mint a subtree-scoped control token (agent-orchestration F). The new token can only reach the named windows/tabs/panes, and only within the minting token's own authority (no escalation). Hand it to a child via open_pane env: { HYPERPANES_CONTROL_TOKEN: token, HYPERPANES_CONTROL_PORT: port } — the child then controls only its subtree and never sees the master token. Optional ttlMs expires it.
Inputs: ttlMs, tabIds, paneIds, windowIds.
move_paneMove paneA
Move a LIVE pane to another tab, in the same window or another one, without restarting it: the pty, session, paneId, label, subtitle, meta and inbox are untouched; only its tab changes. Give exactly one target: tabId (an existing tab, as list_panes reports it), newTab ({title?, color?}: a new background tab), or project (a remembered project id, name or absolute path: that project's tab in the window, reused if present, else created with the project's name and colour). windowId picks the window for newTab/project (default: the pane's own). Returns the target tabId. A tab emptied by the move closes; the window's only pane cannot move (409). Needs a hyperpanes app with the movePane command.
Inputs: paneId (required), tabId, newTab, project, +1 more.
nack_taskNack (fail/retry) a taskA
Report a claimed task as failed. By default it is re-queued until maxAttempts is exhausted (then it lands in dead). Must carry the claim fencingToken. delayMs backs off the retry; requeue:false fails it immediately without retrying.
Inputs: taskId (required), fencingToken (required), error, delayMs, +1 more.
open_paneOpen paneA
Open a new pane. With no tabId/projectTab/windowId it opens beside the caller, in the calling pane's own tab (HYPERPANES_PANE_ID); outside hyperpanes, or with only windowId, it opens in that window's active tab (default: the first window). Returns the new paneId. command alone runs through the shell. Pass args (a string array) to run command DIRECTLY as the executable with that exact argv — no shell, no re-parse — which is the reliable way to pass arguments containing spaces or quotes (e.g. command:"claude", args:["--append-system-prompt","…long persona…"]); a single command string with such args gets mangled by the shell. meta attaches free-form org metadata (reserved keys: role/parent/agentType/task) so the new worker is self-describing. project opens the pane in a remembered project (see list_projects/add_project): it defaults the pane's cwd + frame color from that project and bumps its recency in the sidebar rail.
Inputs: cwd, env, args, meta, +8 more.
open_tabOpen tab(s)A
Attach one or more new tabs to an existing window (defaults to the first window), each with fresh shells — the programmatic equivalent of hyperpanes --attach. Returns the new tab ids. Use this (not open_pane) to add a whole tab, or several tabs, in one call. Pass as:"panes" to instead merge ALL the given panes into one existing tab (returns the new pane ids): tabId if given, else the calling pane's own tab when no windowId is given either, else the window's ACTIVE tab. With no windowId, new tabs open in the calling pane's window. Each group is one tab: { title?, color?, layout?, panes:[{ command?, args?, label?, cwd?, shell?, color?, meta? }] }. A group color ("#rrggbb"/"#rgb") is the new tab's manual colour, the same one recolor_tab sets (it overrides a pane-group colour); a pane color is that pane's frame colour.
Inputs: groups (required), as, tabId, windowId.
prompt_panePrompt a pane and read the replyA
Drive one full turn of a TUI agent (e.g. a live claude) in a pane with ONE call: type text, submit it cleanly (a separate Enter), wait for the pane to go output-quiet, then return the RENDERED screen transcript and whether it is now awaiting input. The wait is turn-aware — it won't return on the pre-prompt screen, only once the reply has begun and settled. Composes send_input(submit) + read_pane(waitForIdle, mode:"screen"). Same triple gate as send_input (it IS input): app toggle + HYPERPANES_ALLOW_INPUT=1 + confirm=true.
Inputs: text (required), paneId (required), tail, owner, +3 more.
purge_queuePurge terminal tasksA
Drop TERMINAL tasks (done/failed/dead) from a queue for retention/cleanup. In-flight (queued/claimed) tasks are kept. Returns how many were removed.
Inputs: queue (required).
queue_promptQueue prompt for a Claude sessionA
Durably queue a message for a Claude conversation (by session id). It is typed into the owning pane the next time that session is ready — including after a pane restart, an app relaunch, or a reboot. Deliver-once; survives every process involved.
Inputs: text (required), sessionId (required).
read_messagesRead pane messagesA
Read a pane's durable message inbox (agent-orchestration E). after is a cursor — pass the highest seq you have seen to get only newer messages. dropped reports how many were evicted by the per-pane cap. The inbox is in-memory and starts over (seq back at 1) when the app restarts: epoch names the current inbox, and reset:true means your after cursor predates it, so messages is the whole backlog — re-base your cursor on latestSeq. For live delivery, subscribe to the pane's messages resource instead of polling.
Inputs: paneId (required), after.
read_paneRead pane outputA
Read a pane's terminal output. mode:"transcript" (Claude panes only) returns the pane's actual CONVERSATION — the last messages user/assistant turns from the session transcript on disk, joined via the pane's session marker — the reliable way to read a claude pane (TUI screen scrapes are lossy). mode:"screen" returns the RENDERED cell grid (what's actually on screen — no overdraw, spinner spam, or mangled spacing) instead of the raw pty stream. Default "raw". tail limits to the last N lines; strip removes ANSI escape codes from a raw read. waitForIdle BLOCKS until the pane has been output-quiet for settleMs (default 600ms) or timeoutMs (default 30000ms) elapses — the way to read a reply without polling/sleeping. since is a byte cursor (use the cursor from a prior read) that returns only NEW output (raw mode), so you don't re-scrape the whole scrollback each turn. Every read returns the current cursor. A screen read also returns inputLine {text, kind: empty|typed|suggestion} for a Claude-style prompt box (null if none): suggestion is the agent's own faint ghost text that nobody typed — never relay or submit it as someone's input. For continuous streaming, subscribe to the pane's output resource instead.
Inputs: paneId (required), mode, tail, since, +5 more.
recolor_paneRecolor paneA
Change a pane's frame color, applied live. Accepts any CSS color string (e.g. "#e5484d").
Inputs: color (required), paneId (required).
recolor_projectRecolor projectA
Set a remembered project's frame/dot color by id (find ids with list_projects). Pass a hex color like "#a855f7"; the app retints open panes inside that project too.
Inputs: id (required), color (required).
recolor_tabRecolor tabA
Set a tab's colour (a stripe and tint on its tab chip), applied live and saved with the session. color is "#rrggbb" or "#rgb"; null clears it back to the default (or, on a pane-group tab, back to the group's project colour, which a manual colour overrides). Address the tab by the tabId list_panes reports.
Inputs: color (required), tabId (required).
remove_projectRemove projectA
Forget a remembered project by id (find ids with list_projects). Removes it from the sidebar rail; does NOT delete the directory on disk.
Inputs: id (required).
rename_paneRename paneA
Change a pane's label (title) and, optionally, its subtitle — applied live to the pane header. Pass subtitle:"" to clear it; omit subtitle to leave it unchanged.
Inputs: label (required), paneId (required), subtitle.
rename_projectRename projectA
Rename a remembered project by id (find ids with list_projects). The new name shows in the sidebar rail.
Inputs: id (required), name (required).
rename_tabRename tabA
Change a tab's title, applied live to the tab strip. Address the tab by the tabId list_panes reports. The app trims the title and rejects an empty one.
Inputs: tabId (required), title (required).
restart_appRestart hyperpanesA
Restart the hyperpanes app itself (root token only). scope:"gui" relaunches the GUI — panes and their processes survive via the session daemon and re-attach. scope:"full" is the phoenix: a final workspace snapshot is flushed, GUI AND daemon exit (every pane dies), and the relaunched app restores the workspace, auto-resuming every pane's Claude conversation in its own cwd. Optional sessionId+prompt pre-queues a speak-first message for one resurrected conversation (e.g. the caller's own — self-restart across the apocalypse).
Inputs: scope (required), prompt, sessionId.
restart_paneRestart paneA
Kill and respawn a pane's shell. With resume:true, the pane's live Claude conversation (per its session marker) is resumed in its own cwd after the respawn — an agent may target its OWN pane to restart itself. prompt queues a message typed into the resumed claude the moment it is ready (speak-first); requires resume:true.
Inputs: paneId (required), prompt, resume.
send_inputSend input to a paneA
Type text into a live shell. With submit:true the app writes your text, then a SEPARATE bare Enter a beat later — the reliable way to submit a line to a TUI agent (a trailing "\n" in one write is read as a bracketed paste, not Enter). Without submit, include a trailing newline yourself to run a shell command. DANGEROUS: this executes whatever you send in a real terminal. Triple-gated and never on by default — requires (1) the app's "Allow agent control → input" toggle, (2) HYPERPANES_ALLOW_INPUT=1 on this bridge, and (3) confirm=true on every call. See README "send_input safety model".
Inputs: data (required), paneId (required), owner, submit, +2 more.
send_keysSend named keys to a paneA
Send a sequence of named keys to a live pane as the right terminal bytes: enter, escape, tab, shift+tab, up/down/left/right, home/end, pageup/pagedown, backspace, delete, space, and ctrl+ (e.g. ctrl+c). For menus, y/n and trust prompts, and cancelling — things a text string can't express. Same triple gate as send_input (it IS input): app toggle + HYPERPANES_ALLOW_INPUT=1 + confirm=true.
Inputs: keys (required), paneId (required), owner, confirm.
send_messageSend a message to a paneA
Enqueue a structured message to a pane's durable inbox (agent-orchestration E). Delivered at-least-once; the target reads it via read_messages or its messages resource. from should identify the sender (your paneId, or an orchestrator label). An org pane (role goals-orch/spec, or hp.nudge=on) also gets a one-line inbox nudge typed in when idle; pass nudge:false only when you wake the target yourself some other way, so one message does not cost it two turns.
Inputs: to (required), body (required), from, nudge.
send_to_parentSend a message to my parentA
Message this pane's org parent — resolved from its meta.parent (agent-orchestration E). Requires HYPERPANES_PANE_ID (this bridge runs inside a pane), or pass from explicitly. Errors if the pane has no parent set.
Inputs: body (required), from.
set_layoutSet tab layoutA
Set the tiling layout of a tab. tabId defaults to the active tab of the first window. Use list_layouts for valid ids.
Inputs: layout (required), tabId.
set_metaSet pane metadataA
Attach or update free-form metadata on a pane (merged: a string value sets/overwrites a key, an explicit null DELETES that key, untouched keys are kept). Reserved keys role/parent/agentType/task describe an agent org; the rest is open. Returns the TRUE merged metadata read back from the app (not your raw input). This is how an orchestrator records the org chart as data.
Inputs: meta (required), paneId (required).
set_speechSet global speech settingsA
Set global speech (TTS) settings: muted mutes/unmutes all speech output; focusedOnly restricts speaking to the currently focused pane. Provide at least one of the two.
Inputs: muted, focusedOnly.
set_talkSet pane talkA
Turn per-pane talk on/off: while enabled, the pane speaks NEW Claude assistant replies aloud via local TTS. Off by default. Returns the resolved talk state and TTS backend (plus a warning if the backend has a problem, e.g. missing binary).
Inputs: paneId (required), enabled (required).
spawn_workersSpawn worker poolA
Spawn a pool of queue workers in one call (#6). Worker panes open beside the caller (in the calling pane's tab) unless tabId, projectTab or windowId says otherwise. Opens pane(s) running the native hyperpanes worker runner bound to queue: it claims tasks and runs command (via sh -c, so $HP_TASK_PAYLOAD / $HP_TASK_ID / $HP_FENCING_TOKEN / $HP_QUEUE are in the environment) for each, acking on exit 0 and nacking on non-zero, until the queue drains — then the pane exits. count = how many competing workers; by default each gets ITS OWN pane (layout:"pane-per-worker") so you can watch one agent per pane — pass layout:"single-pane" for the legacy one-pane-runs-N behaviour. isolation:"worktree" runs each task in a throwaway git worktree that auto-removes (needs cwd to be a git repo) and REQUIRES base — the explicit committish each task worktree forks from, passed to the runner as --base (the call fails without it). stream:true renders a Claude child's --output-format stream-json events as readable progress instead of the silence a bare claude -p gives you; logDir tees each task's raw output to a file that outlives the pane; lingerSecs keeps the pane open after the queue drains. The runner binary defaults to hyperpanes on PATH (override with HYPERPANES_WORKER_BIN on the bridge). env/accounts/meta/project/subtitle pass through to each worker pane spec — merge order: env is the shared base, accounts[i % len] sets each pane's CLAUDE_CONFIG_DIR and wins over env (rotation requires layout:"pane-per-worker" when count > 1; single-pane shares one process env, so the call fails loudly). Every worker pane is stamped with default org meta role="worker", task="queue:", parent=<this bridge's pane> (so worker send_to_parent works out of the box); caller meta wins over those defaults. Returns the spawned pane ids.
Inputs: queue (required), command (required), cwd, env, +15 more.
stop_speechStop speechA
Immediately kill any in-flight speech utterance and clear the speech backlog (global one-shot).
Inputs: no named parameters.
unlock_paneRelease a pane lockB
Release an advisory write lock you hold on a pane (agent-orchestration H).
Inputs: owner (required), paneId (required).
validate_workspaceValidate workspaceA
Validate a workspace spec against the hyperpanes schema without launching anything. Returns { valid, errors?, summary } where summary counts windows/tabs/panes. Accepts the nested windows[]->groups[]->panes[] shape or the legacy single-window panes[]/groups[] shape.
Inputs: spec (required).
whoamiWho am IA
Identify the pane this MCP bridge is running inside (from HYPERPANES_PANE_ID) and report its org metadata (role/parent/agentType/task) + window/tab context, plus app (the running hyperpanes version, and build when reported). The recursion enabler: a manager-agent-in-a-pane calls this to learn who it is before driving its sub-workers. Optionally pass paneId to describe a specific pane instead.
Inputs: paneId.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 50 tools
Most tools target a distinct resource/action, and the descriptions carefully separate close clusters like send_input vs send_keys vs prompt_pane, and send_message vs queue_prompt. A few boundaries still require careful reading—especially between direct prompting, durable queueing, and inbox messaging—but the set is generally clear.
The surface overwhelmingly follows a snake_case verb_noun convention (open_pane, rename_tab, claim_task, list_projects). Minor deviations like whoami and control_status prevent a perfect score, but they remain readable and predictable.
50 tools is very heavy for one MCP surface, even for a broad terminal-orchestration platform. The domains are logically grouped, but the count creates substantial selection overhead and makes it hard for an agent to keep the whole surface in working memory.
Coverage is broad: pane/tab lifecycle, queues, messaging, projects, workspaces, speech, locks, and tokens are all represented. There are still notable gaps such as no explicit close_tab or window-management operations, but most core workflows have a path.
Maintenance
Related MCP Connectors
Remote MCP server exposing your Node Command fleet (files, shell, processes, browser) to AI hosts.
MCP server for your apps' tools and custom tools, plus hosted AI agents and approval-gated workflows
Real-time chat for AI agents. Claude Code, Cursor, Cline and Codex join channels over MCP.
MCP server for AI agents to plan, verify, and deploy Cloudflare-native apps.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceTerminal MCP server for AI coding agents with persistent PTY sessions, ring-buffer incremental reads, headless xterm screen capture, multi-agent orchestration, and a real-time web dashboard.38 npm28MIT
- AlicenseAqualityBmaintenanceTerminal multiplexer MCP server for orchestrating parallel AI agents. Manages workspaces, panes, surfaces with send_input/read_screen/spawn_agent/stop_agent tools. Supports Claude Code, Codex, Gemini, Cursor CLI agents with lifecycle management, browser automation, and agent status push via Claude --channels.1030Apache 2.0
- AlicenseBqualityBmaintenanceA comprehensive MCP server for driving tmux sessions, windows, panes, sending keystrokes, and reading pane output locally or over SSH, enabling real-time collaborative pairing with AI.7120 PyPI6MIT
- AlicenseBqualityDmaintenanceLocal + remote terminal interaction control MCP Server. Lets AI agents control interactive TUI programs the way a human would.297 npmMIT