cowbell
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., "@cowbelllist my herdr workspaces and their panes"
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.
cowbell
Drive herdr fleets from any AI agent.
Properly typed MCP tools for terminal workspace, pane, and agent control.
Why
AI agents don't stick to prose protocols. You can write a detailed playbook for driving a terminal multiplexer, but the agent will improvise. Reviewing shell calls in agent output is error-prone: the agent's native language is tool calls, not command strings.
cowbell exposes herdr's workspace, pane, and agent operations as typed MCP tools with schemas the agent can discover and self-check against. Instead of hoping the agent constructs the right herdr agent prompt --until idle --timeout 90000 target "text" invocation, it calls a tool with named parameters, validated before execution.
When the CLI underneath changes, the drift tripwire names the exact break instead of your fleet silently dying.
Related MCP server: herdr-mcp
Quick start
git clone https://github.com/WSHAPER/cowbell.git
cd cowbell
npm install
npm testAdd to your MCP client config:
{
"mcpServers": {
"cowbell": {
"command": "npx",
"args": ["tsx", "src/bin/stdio.ts"],
"cwd": "/path/to/cowbell"
}
}
}Tools
Workspaces
Tool | Description |
| List herdr workspaces |
| Show one workspace |
| Create a workspace, returns the root pane |
| Close a workspace |
| Focus a workspace |
Panes
Tool | Description |
| Get pane details |
| Read terminal output |
| Type text without Enter |
| Send key presses |
| Type a command and press Enter |
Agents
Tool | Description |
| List recognized agents |
| Show one agent |
| Read agent terminal output (pane view) |
| Read an agent's structured transcript (channel-labeled) |
| One verdict: process liveness + state (blind spots flagged) + transcript error tail |
| Submit a prompt (types + Enter; |
| Block until a status |
| Start an agent in an existing pane ( |
| Focus an agent pane |
| Rename an agent |
| List local opencode models, default marked (discovery) |
Waits (session-owned)
Tool | Description |
| Non-blocking wait until a status; returns |
| Non-blocking report-marker watch: file matches a regex AND is quiet |
| One ( |
| Cancel one ( |
Every herdr-backed tool accepts optional machine or remote parameters (mutually exclusive). Without them, commands run against your local herdr session. Remote workspaces and agents are invisible in the local UI, so callers opt in per call. Exceptions: models_list (local opencode API) and the wait tools (local herdr events socket) take no transport params, and neither do the machine tools; the saved SSH-machine registry lives in the local herdr client by construction.
Tabs
Tool | Description |
| List tabs (active workspace by default) |
| Create a tab (active workspace by default) |
| Show one tab |
| Focus a tab |
| Rename a tab (label keeps spaces verbatim, never split) |
| Close a tab |
Sessions
Tool | Description |
| List named persistent sessions |
| Stop a named persistent session |
| Delete a stopped session by name (stop it first) |
Machines
Tool | Description |
| List machines saved in the local SSH registry |
| Check saved machines without auth prompting (all when label/id omitted) |
| Rename a saved SSH machine (sets the sidebar label) |
| Remove a saved SSH machine from the local registry |
| Enable a saved SSH machine in the local registry |
| Disable a saved SSH machine in the local registry |
The saved-SSH-machine registry lives in the LOCAL herdr client, so these tools take no machine/remote transport params. machine add and machine reconnect are deliberately not wrapped: interactive SSH authentication flows.
Transcript view vs pane view
Two ways to read an agent, two different questions:
agent_messages, the transcript view. Resolves the target throughherdr agent get; recognized agents carry anagent_session{source, agent, kind, value}reference (native opencodeses_...ids). For opencode sessions it queries the local opencode background-service HTTP API (opencode apihandles discovery + auth) and returns a validated, normalized transcript: user/assistant turns with texts, reasoning, tool name + status, plus event entries (system,synthetic,idle,agent-switched, ...). Every result carries an explicitchannelfield:channel: "structured": read from the native session API.channel: "pane_fallback": no native session available (pane-level agent), a non-opencode session, amachine/remotetransport, or the API was unreachable; the result says why inreasonand carries the pane text instead. Pane scraping here is a deliberate keep, not an accident.
agent_read, the pane view. The raw terminal: spinner frames, blocked-state prompts, exactly what is on screen right now. This is what you want for UI-state questions ("is it showing a permission prompt?"); the transcript is what you want for "what has it said/done?"
Queue-of-one prompt delivery
agent_prompt accepts deliver_when:
"now"(default): submit immediately;wait/until/timeout_msbehave as oneherdr agent promptcall."idle": first blocks onherdr agent wait --until idle(event-driven;timeout_msguards that wait), then submits, so the text lands as the agent's next input. Exactly two calls, no general queue, no persisted state, no daemon; cowbell stays a stateless validated wrapper. Combining"idle"withwait/untilis a validation error (the flow already owns the waiting).
Model selection: typed at start, discovered read-only
agent_start accepts model, a non-empty selector like zai/glm-5.3 (optional #variant suffix), compiled per kind after -- in the herdr argv: opencode spawns run mini -m <model> (opencode's full TUI takes no model flag at all, verified live on v2.0.27; mini is its interactive surface that does), every other kind gets the common -m <model>. herdr's argv stays authoritative; cowbell never invents flags. model and args are mutually exclusive (a validation error, no silent precedence): use model for the common case, args when you need full control of the agent binary's argv. The value is NOT checked against a live model list at start time; spawn stays offline-safe, and an unknown selector still spawns (the failure surfaces only when the model is first used), so discover selectors first. One trade-off: opencode model spawns run the minimal interface, and herdr links a native agent_session only for the full TUI, so agent_messages on such agents degrades to its documented pane_fallback channel.
Discovery is the separate, read-only tool: models_list queries the local opencode background service (GET /api/model + GET /api/model/default via the opencode api CLI, which handles discovery + auth) and returns
{
"models": [
{
"model": "zai/glm-5.3",
"provider": "zai",
"name": "GLM-5.3",
"variants": ["low", "high", "max"],
"default": true
}
],
"default": "zai/glm-5.3"
}The model field is exactly the string agent_start wants (and opencode mini -m / opencode run -m accept); the default is returned at the top level and flagged on its entry (null when nothing is configured). No routing, no pool economy; just typed selection plus honest discovery.
Session-owned waits
wait_start / wait_status / wait_cancel fix the collateral/leak/hang class of blocking waits by construction: a wait is a resource owned by the cowbell process that created it.
In-memory, per-process, nothing else. All wait state lives inside one cowbell instance: no files, no daemon, no durability. When the process dies, its waits die with it: an orchestrator's
pkillcan never touch another session's waits, and stale waits can never accumulate anywhere. Cross-session invisibility is the point:wait_statusreports exactly what THIS process owns.Non-blocking by design.
wait_startreturns{wait_id}immediately; pollwait_status(states:pending | fired | timed_out,resultcarries the status that fired). It is not a queue: no ordering, no priority, no delivery semantics; just owned status observation.timeout_msis required and enforced client-side; no wait can outlive its deadline, no matter what the transport does.Direct socket subscription. Waits subscribe to the local herdr socket's
pane.agent_status_changedevents (newline-delimited JSON over~/.config/herdr/herdr.sock, orHERDR_SOCKET_PATH) rather than shelling out toherdr agent wait; that CLI primitive hangs intermittently, so cowbell routes around it. The current status is checked first via an authoritativeagent.listread (already-matched targets fire immediately, no subscription at all).events_losthandling. Per herdr socket semantics, when a subscriber falls behind retained history the server sends anevents_losterror and closes the connection. cowbell then resubscribes and reconciles from a fresh authoritative read; buffered events are never replayed, and the read is the truth after a gap. The same recovery covers plain stream death, and the deadline timer is never cleared during recovery (clearing it would resurrect the hang class).
// wait_status -> { "waits": [ ... ] } or one record:
{
"wait_id": "wait_1f0c…",
"target": "w4C:p1",
"pane_id": "w4C:p1",
"until": ["idle"],
"state": "fired",
"result": "idle",
"started_ms": 1792177611227
}Drift detection
src/herdr/spec*.ts every herdr command, single source (split by domain)
tests/fixtures/herdr-surface.txt captured --help output
tests/upstream.drift.test.ts cross-checks spec vs fixture
src/herdr/socket.ts raw herdr events-socket client (waits)
tests/fixtures/herdr-events-surface.json captured events.subscribe contract
tests/herdr-events.drift.test.ts cross-checks socket tokens vs fixture
src/opencode/spec.ts every opencode api call, single source
tests/fixtures/opencode-session-message.json captured operation
tests/fixtures/opencode-models.json captured model.list + model.default
tests/opencode.drift.test.ts cross-checks spec vs fixture
tests/opencode.models.drift.test.ts cross-checks model surface vs fixtureWhen herdr renames a flag or subcommand:
npm run reference:update # pull upstream source
npm run reference:surface # re-capture --help tree
npm test # names the exact broken builderWhen herdr renames the events socket surface (events.subscribe, the
pane.agent_status_changed subscription variant, or the pushed event
contract the wait registry reads):
npm run reference:herdr-events # re-capture the events contract
npm test # names the exact tokenWhen opencode renames the message operation, its parameters, or the message/part type unions:
bash scripts/dump-opencode-surface.sh # re-capture the operation
npm test # names the exact tokenWhen opencode renames the model operations (model.list,
model.default) or drops a Model.Info key the models view pins:
npm run reference:opencode-models # re-capture both operations
npm test # names the exact tokenRequirements
Node.js >= 22
herdr >= 0.9.5 on PATH (for local tools) or reachable via SSH (for remote tools)
Development
npm run build # compile to dist/
npm run typecheck # tsc --noEmit
npm run lint # eslint (typechecked)
npm run test # vitest
npm run e2e # full loop against live herdr
npm run e2e:wait-own-pane -- /tmp/evidence.json # own-pane wait-until-idle live e2e
npm run bench # e2e + hyperfineLicense
This server cannot be deployed
Maintenance
Related MCP Connectors
Your org's AI agents, tasks, runs, search, and brain files as MCP tools and resources.
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
214 tools for AI agents over one MCP endpoint: image, video, audio, 3D, data, storage, mail, GPUs.
Related MCP Servers
- AlicenseAqualityDmaintenanceMCP server that exposes the Herdr terminal API as callable tools, enabling AI agents to manage terminal workspaces, tabs, panes, and agents. It dynamically generates tools from the Herdr API schema and communicates via Unix socket.20MIT
- FlicenseAqualityBmaintenanceEnables coding agents to control and inspect a running Herdr terminal session, including listing workspaces, tabs, panes, and agents, reading pane output, prompting agents, sending keys and commands, waiting for output or state, and splitting or closing panes.14-
- AlicenseBqualityBmaintenanceEnables MCP clients to remotely control a running Herdr session: see agent status, arrange tabs and panes, start and prompt coding agents, and run worktree-isolated jobs over stdio or Streamable HTTP. Includes policy controls such as allowlists, read-only mode, disabled tools, and an audit log.322MIT
- AlicenseNot gradedqualityCmaintenanceLets MCP clients such as Claude Code, Cursor, and Claude.ai inspect and drive herdr workspaces, panes, and coding agents over a local socket, running either as a local stdio process or as an OAuth-protected HTTP endpoint published through a Cloudflare Tunnel. It exposes tools to create workspaces and tabs, split panes, start and prompt agents, read their output, and wait for results.MIT