Skip to main content
Glama

hyperpanes-mcp

CI

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 hyperpanes to open it.

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_layouts

List the tab layouts (auto, single, columns, rows, grid, main-stack) with descriptions.

validate_workspace

Validate a workspace spec against the schema. Returns { valid, errors?, summary } (window/tab/pane counts).

build_workspace

Validate + return canonical workspace JSON; optionally write it to a .json file; include the equivalent hyperpanes CLI command when losslessly expressible.

launch_workspace

Launch hyperpanes with a workspace, from a .json path or an inline spec. Defaults to a lossless temp-file launch; mode:"cli" compiles to flags. Needs HYPERPANES_BIN.

Inspect a running instance

Tool

Description

control_status

Is the control API reachable? Reports app pid/version, whether the app allows input, the bridge-side send_input gate, and the control.json path. Call this first.

list_panes

All panes across windows/tabs, with status, activity (busy/idle/exited), org metadata, tab/window context (tabId, tabTitle, tabColor — the tab's manual colour or null), and each pane's output-resource URI.

read_pane

A pane's terminal output. mode:"screen" = the rendered cell grid (clean TUI transcript, no overdraw/spinner spam); tail = last N lines; strip = ANSI-stripped raw text; waitForIdle = block until the pane goes output-quiet (settleMs/timeoutMs) so you read a reply without polling; since = a byte cursor for delta reads (only new output). Every read returns the current cursor.

read_messages

Drain a pane's durable message inbox past a cursor (after = highest seq seen).

whoami

Identify the pane this bridge is running inside (HYPERPANES_PANE_ID) and its org metadata — so a manager-agent-in-a-pane can learn who it is before driving sub-workers.

Drive panes

Tool

Description

open_pane

Open a new pane beside the caller: in the calling pane's own tab (from HYPERPANES_PANE_ID), even when another tab is active. Outside hyperpanes, or with only windowId, it opens in that window's active tab (default: the first window). Returns the new paneId; accepts meta (org metadata) and env (e.g. a scoped token) at spawn. project + projectTab:true opens the pane in that project's tab (reused or created) instead of the caller's, and tabId opens it in that existing tab; spawn_workers takes the same options. Either way the pane never shows up in the active tab first, so don't open a pane and then move_pane it. Pass args (a string array) to run command directly with that verbatim argv — no shell, no re-parse — the reliable way to pass arguments containing spaces/quotes (e.g. command:"claude", args:["--append-system-prompt","…persona…"]).

open_tab

Attach one or more new tabs to a window, each with fresh shells; returns the new tab ids in the caller's window (as:"panes" merges the panes into an existing tab instead: tabId, else the caller's own tab). Each group is { title?, color?, layout?, panes }: a group color ("#rrggbb"/"#rgb") is the new tab's manual colour, the same one recolor_tab sets; a pane color is that pane's frame colour.

set_layout

Set a tab's tiling layout (defaults to the first window's active tab).

focus_pane

Focus a pane (and its tab/window).

close_pane

Close a pane, terminating its shell.

restart_pane

Kill and respawn a pane's shell.

rename_pane

Change a pane's label and (optionally) subtitle, live.

recolor_pane

Change a pane's frame color, live (any CSS color).

rename_tab

Change a tab's title, live. Address the tab by the tabId from list_panes. Maps 1:1 to the app's renameTab command; the app trims the title and rejects an empty or over-80-char one.

recolor_tab

Set a tab's colour (a stripe and tint on its tab chip), live and saved with the session. color is "#rrggbb" or "#rgb"; null clears it. A manual tab colour overrides a pane-group tab's project colour; clearing it shows the group colour again. Maps 1:1 to the app's recolorTab command.

move_pane

Move a live pane to another tab or window without restarting it (same pty, session, paneId, label, subtitle, meta, inbox). Exactly one target: tabId, newTab ({title?, color?}, opened in the background) or project (id, name or absolute path: that project's tab, reused or created with the project's name and colour). windowId picks the window for newTab/project. Returns the target tabId. Maps 1:1 to the app's movePane command.

set_meta

Attach/update a pane's free-form metadata (merge; null deletes a key). How an orchestrator records the org chart as data.

set_talk

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_speech

Set global speech settings: muted mutes/unmutes all speech, focusedOnly restricts speaking to the focused pane. Provide at least one.

stop_speech

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

send_input ⚠️

Type text. submit:true writes your text, then a separate Enter a beat later — the reliable way to submit a TUI line (a trailing \n in one write is read as a bracketed paste, not Enter).

send_keys ⚠️

Send named keys as the right terminal bytes: enter, escape, tab, shift+tab, arrows, home/end, pageup/pagedown, backspace, delete, space, ctrl+<letter>. For menus, y/n & trust prompts, and cancelling.

prompt_pane ⚠️

One full turn in one call: type → submit → wait for the pane to settle → return the rendered transcript + whether it's now awaitingInput. The way to converse with a TUI agent in a pane.

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 claude that won't poll its inbox). Drive the terminal directly. The one-call path is prompt_pane; under the hood that's send_input({ submit:true }) to type a line, read_pane({ waitForIdle:true }) to block until the reply lands, and read_pane({ mode:"screen" }) to read it back cleanly. Use send_keys(["enter"]) to clear a first-run trust dialog, and watch awaitingInput on 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

send_message

Enqueue a structured message to a pane's durable inbox (at-least-once delivery).

send_to_parent

Message this pane's org parent (resolved from meta.parent).

broadcast_subtree

Message every pane in an org subtree (all panes whose meta.parent chain leads back to a root).

mint_token

Mint a subtree-scoped control token (no escalation) to hand a child via open_pane env — the child controls only its subtree and never sees the master token.

lock_pane

Take an advisory write lock so only the holder can send_input until it expires.

unlock_pane

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

list_queues

Every queue and its per-state depth.

enqueue_task

Append a task (payload + optional kind/title/priority/maxAttempts/visibilityTimeoutMs/delayMs/dedupeKey). Returns the task id + seq.

claim_task

Claim the next task(s) for a worker (competing consumers; empty → {tasks:[]}). Each task carries a fencingToken + visibilityDeadline.

ack_task

Complete a claimed task (carry the fencingToken).

nack_task

Fail a task — re-queued until maxAttempts, or requeue:false to fail now; delayMs backs off.

extend_task

Heartbeat a long task: extend its lease so it isn't reaped.

get_task

Fetch one task (state, payload, attempts, lease, timestamps).

list_tasks

List/inspect a queue's tasks with a state filter + after/limit paging.

purge_queue

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

hyperpanes://pane/{paneId}/output

Terminal output — scrollback on read, deltas on subscribe (text/plain).

hyperpanes://pane/{paneId}/messages

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-mcp

Install globally

npm install -g hyperpanes-mcp
hyperpanes-mcp   # runs the stdio server

Also 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

HYPERPANES_BIN

Path to the hyperpanes executable (for launch_workspace). No PATH fallback — it fails loudly rather than spawn the wrong process.

HYPERPANES_LAUNCH_ARGS

Whitespace-separated leading args for the launcher (e.g. a dev runner).

HYPERPANES_CONTROL_FILE

Override the path to the app's control.json (use if the app runs under a non-default data dir).

HYPERPANES_USER_DATA

Override just the userData dir; <dir>/control.json is used.

HYPERPANES_CONTROL_TOKEN / HYPERPANES_CONTROL_PORT

A scoped control token + port for a child pane (set automatically by mint_token / open_pane env). Used instead of reading control.json.

HYPERPANES_PANE_ID

The pane this bridge runs inside — enables whoami and the hierarchy helpers.

HYPERPANES_ALLOW_INPUT

1/true to permit send_input on this bridge (off by default).

HYPERPANES_INPUT_ALLOWLIST

Comma-separated pane ids or labels allowed to receive input.

Default control.json locations:

  • Windows: %APPDATA%\hyperpanes\control.json

  • macOS: ~/Library/Application Support/hyperpanes/control.json

  • Linux: $XDG_CONFIG_HOME/hyperpanes/control.json (or ~/.config/hyperpanes/…)

send_input safety model

send_input types 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:

  1. App-side (enforced by hyperpanes): the control server is loopback + token, disabled by default, and send_input returns 403 unless "Allow agent control → input" is on. The bridge cannot bypass this.

  2. Bridge opt-in: refused unless HYPERPANES_ALLOW_INPUT=1 is set in this server's environment. Optionally HYPERPANES_INPUT_ALLOWLIST restricts which panes accept input.

  3. 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-stack
  • Launch modes. launch_workspace defaults 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 in lossy.

  • Relative cwd. In a workspace file, relative cwd resolves against the file's dir. Inline specs are written to a temp file — prefer absolute cwd for inline specs.

  • Strict validation. Unknown keys are rejected (typo guard), layout must be a known id, fontSize a positive integer, a tab color "#rrggbb"/"#rgb" (or null), 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 files

Releasing

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-mcp

  • GitHub Packages as @eyalm321/hyperpanes-mcp

One-time repo setup

  1. Add an NPM_TOKEN repository secret (an npm automation token). GITHUB_TOKEN is provided automatically for GitHub Packages.

  2. To release: npm version <patch|minor|major>, push with --follow-tags, then create a GitHub Release for the tag (e.g. v0.1.1). The publish workflow 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: true the local claude -p judge reviews git diff <base>...<branch> first; a block verdict 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 done issue 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-run prints would review, would push …, would open draft PR "…" and does none of them.

Available Tools

50 tools
ack_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

    A3.6/5.0

    Scored across 50 tools

    Disambiguation4/5

    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.

    Naming Consistency4/5

    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.

    Tool Count2/5

    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.

    Completeness4/5

    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

    ActivityMaintained
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      D
      maintenance
      Terminal 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 npm
      28
      MIT
    • A
      license
      A
      quality
      B
      maintenance
      Terminal 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.
      10
      30
      Apache 2.0
    • A
      license
      B
      quality
      B
      maintenance
      A 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.
      71
      20 PyPI
      6
      MIT