npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@libtmux/mcp

v0.1.0-alpha.12

Published

Model Context Protocol server for tmux, built on libtmux.

Downloads

987

Readme

@libtmux/mcp

A Model Context Protocol server that gives an AI agent a real tmux server.

npm downloads typescript tmux license

Part of libtmux for Bun and TypeScript. Built on libtmux.

[!WARNING] Alpha. Prerelease software: tool names and arguments can change between alpha releases without a deprecation cycle. Pin an exact version.

Why this exists

An agent driving a terminal needs three things a shell tool does not give it: somewhere durable to run commands, a way to wait for output without burning a call per poll, and an answer it can act on when the wait does not go its way.

This gets all three from tmux's control mode. One connection per watched session carries both the commands and the notifications that say what happened, so waiting costs nothing while nothing is happening — no polling, no command per read.

Three properties fall out of that, and they are the reason to prefer this over a capture loop:

A command's output is never its echo. A pane repeats what is typed into it, so waiting for text that appears in the command matches immediately and reports your own command back to you. run_shell_command frames what it sends so the marker it waits for cannot appear in what it typed or in the command's inherited shell state, and reports the real exit status.

A wait that fails is still an answer. Every wait reports why it ended — matched, timed_out, pane_died, cancelled — along with everything the pane printed, what it shows now, and a cursor to carry on from. There is no result that leaves an agent with nothing but "no". Cancel the request and the wait stops there and then, rather than holding its connection for the rest of a deadline nobody is waiting on.

Reading twice costs less than reading once. capture_since hands back a cursor; pass it next time and you are charged only for what arrived since, not for the whole screen again.

Install

$ npx -y @libtmux/[email protected]

Nothing to install ahead of time: the server speaks MCP over stdio, so an MCP client launches it as a subprocess. To pin it in a project instead:

$ bun add --exact @libtmux/[email protected]

Closing the subprocess's stdin cancels pending waits and joins its control connections before exit. Existing tmux sessions and panes keep running. Backend cleanup failures produce a nonzero exit status.

Requires Node 22+ or Bun 1.3.14+, and tmux 3.2a or newer.

Linux is the only supported host for real tmux control. The macOS CI lane checks package artifacts without exercising tmux; macOS runtime behavior is unproven. WSL is untested.

Configure your client

This is the whole configuration:

{
  "mcpServers": {
    "tmux": {
      "command": "npx",
      "args": ["-y", "@libtmux/[email protected]"],
      "env": { "LIBTMUX_SOCKET": "agent" }
    }
  }
}

Naming a socket is choosing one, and an explicitly chosen server gets inspect,manage,execute — so this configuration ships without the teardown tools: no kill_pane, kill_window, kill_session or clear_pane_scrollback. That is the intended default for a socket the server did not create itself. Toolsets and trust says how to opt in.

$ claude mcp add tmux --env LIBTMUX_SOCKET=agent -- \
    npx -y @libtmux/[email protected]
$ bun packages/mcp/src/server.ts

Repository contributors can preview, apply, and exactly revert registrations across supported clients with the native MCP swap helper.

Point it at a server

An MCP client supplies an environment and a command line, and nothing else, so the environment is the only place a socket can come from. The library itself never reads these — a library that picks up ambient configuration surprises its caller — so the reading happens here, at the edge that has a process.

| Variable | Effect | | --------------------- | ---------------------------------------------------------- | | LIBTMUX_SOCKET | Socket name, resolved under tmux's own directory | | LIBTMUX_SOCKET_PATH | Absolute path to the tmux socket; mutually exclusive above | | LIBTMUX_TMUX_CONFIG | Absolute path to the config used for a new server | | LIBTMUX_TMUX_BIN | The tmux executable to use |

The executable and socket selector are validated and frozen before any tmux connection. ASCII control characters and DEL are refused there; apostrophes and non-ASCII characters pass through unchanged. Snapshots, live tails, and framed commands all use that selected server route.

LIBTMUX_SOCKET_NAME is retired. Replace it with LIBTMUX_SOCKET; its presence stops startup instead of selecting a socket.

Give the agent its own socket name. Sharing the one you are attached to means an agent's cleanup can reap the session you are working in.

With no socket or config variables, the executable selects libtmux-mcp and starts that daemon with the shipped minimal configuration. The launch records a unique marker inside tmux, then reads it back before it exposes tools. Only the process that actually created the daemon can claim minimal provenance; a second process racing for the same socket sees an existing server and defaults without teardown.

An explicit socket is different. The MCP process never assumes how an existing daemon was configured. A new daemon on that socket follows tmux's normal config behavior unless you explicitly name a file, and it is never reported as having the shipped minimal provenance. LIBTMUX_TMUX_CONFIG is passed to tmux as written and must be a nonempty absolute path.

Tune what it will do

| Variable | Default | Effect | | -------------------------------- | --------------------- | -------------------------------------------------- | | LIBTMUX_TOOLSETS | depends on the server | Comma-separated toolsets to include | | LIBTMUX_TOOLS | none | Comma-separated tools to add | | LIBTMUX_EXCLUDE_TOOLS | none | Comma-separated tools to remove last | | LIBTMUX_MCP_WAIT_MAX_MS | 30000 | Ceiling on a wait that blocks the caller | | LIBTMUX_MCP_COMMAND_TIMEOUT_MS | 30000 | Deadline for each internal tmux request | | LIBTMUX_MCP_MAX_RESULT_LINES | 200 | Lines a result may carry before it trims and links | | LIBTMUX_MCP_LIVE | on | Set to 0 to forbid control-mode connections |

With live connections disabled, wait_for_text stays in the selected surface but returns a no_stream tool error; capture_since returns a bounded capture with streaming: false.

Cancelling a request stops its wait. An over-large tool timeout is never an error: it is clamped, and every result reports the effectiveTimeoutMs it actually used. The command timeout is capped at JavaScript's timer range.

Toolsets and trust

Tools belong to four unordered sets: inspect, manage, execute, and teardown. A new dedicated socket with the shipped minimal configuration gets all four by default. An existing, explicitly selected, or otherwise unknown server defaults to inspect,manage,execute.

LIBTMUX_TOOLSETS chooses sets, LIBTMUX_TOOLS adds individual tools, and LIBTMUX_EXCLUDE_TOOLS removes tools last. An explicitly empty value selects none. Unknown names, empty list entries, and the retired LIBTMUX_SAFETY variable stop startup before tmux is contacted.

The retired LIBTMUX_MCP_TOOLS allowlist fails there too.

Existing configurations migrate as follows:

| Previous setting | Current setting | | ---------------------------- | -------------------------------------------------- | | LIBTMUX_SAFETY=readonly | LIBTMUX_TOOLSETS=inspect | | LIBTMUX_SAFETY=mutating | LIBTMUX_TOOLSETS=inspect,manage,execute | | LIBTMUX_SAFETY=destructive | LIBTMUX_TOOLSETS=inspect,manage,execute,teardown | | LIBTMUX_MCP_TOOLS= | LIBTMUX_TOOLSETS= with LIBTMUX_TOOLS omitted | | LIBTMUX_MCP_TOOLS=a,b | LIBTMUX_TOOLSETS= plus LIBTMUX_TOOLS=a,b |

The old safety variable and allowlist both fail on mere presence. An empty LIBTMUX_TOOLSETS is important in the allowlist rows: LIBTMUX_TOOLS adds to the selected sets, while the retired variable was an exact allowlist.

For an agent that can inspect and run pane commands but cannot rearrange or end topology:

$ LIBTMUX_TOOLSETS=inspect,execute npx -y @libtmux/[email protected]

An empty toolset plus named inclusions makes a smaller purpose-built surface:

$ LIBTMUX_TOOLSETS= LIBTMUX_TOOLS=list_sessions,capture_pane npx -y @libtmux/[email protected]

Exclusions always win, including inside call_read_tools_batch. A tool removed there is neither listed, directly callable, nor available as a nested batch operation. The selection is resolved once before the protocol starts and does not change with later environment or tmux state.

This shapes the MCP interface; it is not an authorization or confidentiality boundary. Every advertised tool runs with the tmux user's authority. Use a dedicated socket and read the security boundary before serving an untrusted client.

The startup line on stderr names the selected socket, effective toolsets, named inclusions, and exclusions. Stdout remains exclusively MCP JSON-RPC, so that diagnostic cannot corrupt the protocol stream.

The verb says whether this process started that tmux (serving new) or attached to one already running (serving existing) — which is both why the toolsets differ between the two and the only notice that an agent is sharing a daemon with whoever else is on that socket.

On a fresh default socket, that diagnostic is:

$ libtmux-mcp
libtmux-mcp 0.1.0-alpha.12 serving new libtmux-mcp, toolsets execute,inspect,manage,teardown, 0 named inclusions, 0 exclusions

Tools

Grouped by what you are trying to do. Every tool returns typed structuredContent alongside its text, and carries the same deliberately conservative MCP annotations — so a host that auto-approves on annotations alone approves none of them. What distinguishes one tool from another is the capability row in tmux://capabilities, described under Toolsets and trust.

These existing names are unchanged: list_sessions, list_windows, list_panes, capture_pane, search_panes, send_keys, paste_text, wait_for_text, respawn_pane, rename_session, rename_window, resize_pane, resize_window, select_pane, select_window, select_layout, swap_pane, move_window, set_pane_title, show_hooks, show_environment, kill_pane, kill_window, and kill_session.

Routes removed or renamed since the previous alpha migrate as follows:

| Previous route | Current route or replacement | | -------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | | get_pane | get_pane_info | | whoami | list_panes exposes caller and watched panes; inspect one with get_pane_info | | server_info | get_server_info | | observe | Use snapshot_pane once, or seed capture_since without a cursor and reuse the cursor it returns | | run_command | run_shell_command | | build_workspace | Compose create_session, create_window, split_window, layout, title, and selection tools | | new_session | create_session | | new_window | create_window | | split_pane | split_window | | show_options | show_option with an explicit scope and option name | | set_option | Use set_mouse_enabled, set_history_limit, or set_synchronize_panes for the supported changes | | unset_option | No generic replacement; set the supported value explicitly outside MCP when inheritance is required | | display_message | No generic format evaluator; use get_tmux_variables, show_option, or another typed inspector | | pipe_pane | No durable host-command pipe; use run_shell_command for a bounded pane command | | move_pane | No direct replacement; swap_pane preserves pane identity in a window and move_window moves a whole window | | swap_window | No direct replacement; use move_window when moving between sessions is sufficient | | set_environment | No MCP environment writer; configure the dedicated daemon outside the MCP process | | list_buffers, show_buffer, load_buffer, save_buffer, paste_buffer, delete_buffer | No public buffer CRUD; use paste_text for literal input and capture tools for pane output |

Find your way around

| Tool | Answers | | ----------------------- | --------------------------------------------- | | list_sessions | What sessions exist | | list_windows | What windows exist, optionally in one session | | list_panes | What panes exist and what each runs | | get_server_info | Socket, tmux version, daemon pid, and totals | | get_session_info | One session's metadata | | get_window_info | One window's metadata | | get_pane_info | One pane's metadata | | find_pane_by_position | The pane at a window coordinate |

Pane rows identify the active pane, the MCP caller's pane when that identity can be proven, and panes watched by attached clients. That context matters before a write or teardown call: the server refuses its own pane unless force confirms that exact caller, and always refuses panes watched by an attached client.

Read what panes show

| Tool | Answers | | ----------------------- | --------------------------------------------------- | | capture_pane | The rendered screen, or into the scrollback | | capture_since | Only what is new since your cursor | | snapshot_pane | Bounded content, pane metadata, and cursor position | | search_panes | Which panes contain a bounded pattern | | call_read_tools_batch | Typed results from up to 16 eligible inspect calls |

Use search_panes to discover which pane contains a value without capturing every pane. Once the pane is known, capture_pane reads its rendered screen; negative start and end values reach into retained history. snapshot_pane returns bounded content plus mode, cursor position, scroll position, and pane metadata in one MCP response. Its metadata and content come from separate tmux requests, so do not treat them as an atomic view.

For repeated observation, seed capture_since without a cursor and pass its opaque cursor back on later calls. A nonzero missedBytes means retained stream data was lost before the read; use capture_pane to recover whatever remains in tmux's bounded history. The capture tools read output without entering or changing a client mode.

Search is one bounded operation even when it spans the whole socket: at most 200 panes, 20,000 captured lines, 256 KiB of matched input, and five seconds of matching work. The result states which aggregate limit stopped it, so a caller can narrow by session, window, or pane instead of retrying the same broad scan.

call_read_tools_batch accepts only its declared 16 read operations. Each row keeps its input index, success flag, error or result, and row-truncation flag. The outer result records onError, succeeded and failed counts, stoppedAt, and whole-result truncation bytes. The complete MCP result is capped at 1,000,000 bytes; an oversized row is represented explicitly rather than silently omitted. Inner operations receive no separate approval, so the batch capability metadata is the union of every still-eligible nested tool.

A serialized request ID may use at most 512 KiB. A larger ID receives a bounded invalid-request response before any tool runs.

Do things

| Tool | Does | | ------------------- | ------------------------------------------------------- | | run_shell_command | Runs a shell command, waits for it, reports exit status | | send_keys | Sends keystrokes: TUIs, C-c, partial lines | | send_keys_batch | Sends typed key operations in one bounded call | | paste_text | Sends text with nothing read as a key name |

A pane keeps history-limit lines and capture_since keeps a bounded buffer, so output larger than either is gone before anything asks for it.

paste_text loads the text and optional newline into one uniquely named private buffer, then pastes directly to the named pane. Unlike key input, that stays target-only when synchronized input is enabled. It checks the target before buffer setup and checks it again immediately before paste; cleanup is attempted on every path. send_keys_batch validates and bounds every child operation before sending the first one, then takes a fresh snapshot for each row.

When a source pane's effective synchronize-panes value is off, its configured input cohort is only that source. When it is on, the cohort is the sorted, deduplicated set of same-window panes whose own effective value is on. Every member must be alive, input-enabled, and outside a human-owned mode. Caller, attended-pane, and active input protections also apply to every member. force can confirm only the exact MCP caller pane; it never overrides the other checks.

Input results disclose that configured membership at the immediate preflight, not observed recipients or a delivery receipt. State can change after any snapshot; each send_keys_batch row takes its own check so an earlier row cannot authorize a later one.

run_shell_command additionally requires a singular configured cohort and a supported POSIX foreground shell. It checks once before reservation and live tail setup, then takes one fresh check after setup immediately before the first and only wrapper dispatch. A change in pane identity, daemon, placement, liveness, input state, mode, shell, or cohort refuses the command without sending the wrapper. The final check also reapplies caller, attended-pane, and competing-input policy. These are exactly two observational checks and one dispatch, not an atomic tmux transaction, so another process can still race the final check.

The frame uses the shell's command printf primitive so an inherited printf alias or function cannot forge its bookkeeping. The caller's command still sees inherited shell options and traps, and its subshell keeps command-defined functions, traps, directory changes, and exports out of the parent. This is a correctness protocol inside a trusted interactive shell, not a sandbox against a hostile shell. It trusts the selected tmux executable, the daemon and its loaded configuration, and the supported foreground shell.

Wait

| Tool | Does | | ------------------ | ----------------------------------------------- | | wait_for_text | Blocks until a pane prints something | | wait_for_channel | Blocks until a tmux channel is signalled | | signal_channel | Signals a tmux channel for another waiting task |

Build and arrange

create_session, create_window, split_window, respawn_pane, rename_session, rename_window, resize_pane, resize_window, select_pane, select_window, select_layout, swap_pane, move_window, and set_pane_title.

select_layout accepts tmux's named layouts, unique abbreviations for the running daemon, and checksummed saved layouts. The native core validates the input before dispatch; tmux owns geometry and may resize or prune saved cells. The result's window.layout comes from a fresh snapshot. Check metadataComplete before reusing it as input.

Copy mode and other client modes are human-owned, modal state. A nonzero snapshot_pane.inMode reports that state; input may be interpreted by the active tmux key table instead of reaching the pane's program. Report the mode and wait for its owner to leave it. The MCP server deliberately neither enters nor cancels client modes.

move_window moves a complete window between sessions without restarting its panes; pane and window identities remain stable. swap_pane exchanges pane positions without changing the programs running inside them. Use those when identity continuity matters instead of killing and recreating topology.

A detached session has no client to size it, so tmux gives it 80 columns and every program in it formats to that. create_session takes width and height, and resize_window changes one afterwards — resize_pane only redistributes space inside a window. A program that formats to its terminal width truncates at the source, where no capture option recovers the columns.

Configure

get_tmux_variables, show_option, show_environment, and show_hooks inspect configuration. set_mouse_enabled, set_history_limit, and set_synchronize_panes expose the bounded changes MCP clients need without a generic option or environment writer.

Options may contain tmux formats and commands. The read tools disclose those configured-command outputs rather than executing caller-supplied host commands.

Options live at server, session, global-session, window, global-window, and pane scope. show_option requires the scope explicitly so a value inherited from a global table is not mistaken for one set on the target. get_tmux_variables accepts validated variable names and resolves only those names; arbitrary tmux formats are not accepted as an executable-shaped escape hatch.

Environment rows can contain credentials and hook or option rows can contain configured shell commands. Their capability metadata declares process-environment or configured-command output so an MCP host can route the returned content appropriately.

End things

clear_pane_scrollback, kill_pane, kill_window, and kill_session belong to the teardown toolset.

Those tools refuse to end the pane hosting the MCP process unless force confirms that exact caller, and always refuse a pane an attached person is watching. This protects direct teardown calls; it is not a sandbox. A command typed into another pane still has the authority of the tmux user and may perform an equivalent action.

To opt into teardown on an explicitly selected server, name it deliberately:

$ LIBTMUX_SOCKET=agent LIBTMUX_TOOLSETS=inspect,teardown npx -y @libtmux/[email protected]

Choosing the right tool

The three mistakes that cost an agent a turn, and what to do instead.

Do not poll. A send_keys then capture_pane loop spends a call per read and still cannot tell you when the command finished.

$ run_shell_command  paneId=%1  command='cargo build'

That waits through tmux's notifications and comes back with exitStatus, outcome, and the output — one call.

Do not wait for text you sent. The pane echoes it; wait_for_text discounts your own typing while it is unsubmitted or was submitted within the last ten seconds, but a command that runs longer than that, or the same value typed and waited for again later, can still surface its own echo. run_shell_command is immune by construction; wait_for_text is for output somebody else wrote.

Do not re-read the screen. Use snapshot_pane when one response needs bounded content and pane metadata. For later deltas, call capture_since without a cursor once, then pass back the opaque cursor it returns:

$ snapshot_pane  paneId=%1
$ capture_since  paneId=%1
$ capture_since  paneId=%1  cursor=ltxc1.0123456789abcdef0123456789abcdef.4096  waitMs=10000

The second call is charged only for what arrived after the opaque cursor.

Do not infer success from visible text. A compiler can print an error and a shell can still leave old success text on screen. Use run_shell_command when the MCP call owns the command; its framed completion reports the exit status. Use wait_for_text only when some other process owns the work and the text itself is the condition.

Resources

The server exposes one static resource: tmux://capabilities. It reports the effective startup-frozen tool surface, each tool's capability row, the selected socket and its provenance, and the trust boundary. It does not expose live tmux topology or pane content as resources.

The report includes schemaVersion, frozen, the selected toolsets and exact effective tool names, and a socket record with selector, selection provenance, server state, configuration provenance, and namespace boundary. Its boundary record states that socket selection is process-wide, resources are static, and host-command execution is absent. A client can read this once to explain the surface it received without probing tmux or reconstructing launch arguments.

Former dynamic resource workflows have typed replacements:

| Previous resource | Current workflow | | ----------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | | tmux://sessions, tmux://windows, tmux://panes | list_sessions, list_windows, and list_panes | | tmux://clients | list_panes reports caller and watched-pane identity | | tmux://sessions/{sessionId}, tmux://windows/{windowId}, tmux://panes/{paneId} | The corresponding get_session_info, get_window_info, or get_pane_info tool | | tmux://panes/{paneId}/content and subscriptions | capture_pane, snapshot_pane, capture_since, or wait_for_text |

Resource-template completion/complete support has no direct replacement. Discover ids with the list tools, then pass the exact id through the typed tool schema.

The retired prompts remain available as explicit tool workflows:

| Previous prompt | Current workflow | | ----------------- | ------------------------------------------------------------------------------------------------------------------------ | | run-and-check | run_shell_command | | watch-until | Seed capture_since without a cursor, then reuse its cursor with capture_since or wait_for_text | | diagnose-pane | get_pane_info, capture_pane or snapshot_pane, show_option, show_hooks, and show_environment | | build-workspace | create_session, create_window, split_window, select_layout, set_pane_title, select_window, and select_pane |

For run-and-check, a timed_out outcome means the command may still be running. Continue observing it; do not run the command again.

For watch-until, carry the returned cursor into the next capture after a timeout. A pane_died outcome means retrying cannot produce more output.

For diagnose-pane, inspect currentCommand, dead, bounded scrollback and its cursor, the last command, and the last non-empty output. Give a root-cause hypothesis and propose the single cheapest confirming command, but do not execute it.

For build-workspace, report every pane ID returned while creating the topology.

Capability disclosure

Each advertised tool carries _meta["com.git-pull.libtmux-mcp/capability"]. The row declares its process reach, effects, possible outputs, input literalization, and whether it can amplify future pane input. Every tool also uses the same conservative MCP annotations: non-read-only, potentially destructive, non-idempotent, and open-world.

Process reach is one of none, configured-process, pane-input, or pane-command; no public tool has host-command reach. Effects are drawn from observe, change, and delete. Output classes distinguish tmux metadata, terminal content, process environment, and configured commands.

The internal manifest classifies where every caller-controlled value goes and tests those sink claims against each schema. Public capability rows expose the resulting controls: fields that tmux would otherwise interpret as formats declare the double-hash-once literalization strategy. Validated variable names are called out separately because they control a format lookup without accepting a free-form format expression.

Embedding it

The server is a library too, so a host that already has a Server can mount tmux tools on its own MCP surface:

import { createTmuxMcpServer, serverFromEnvironment } from "@libtmux/mcp";

const mcp = createTmuxMcpServer(serverFromEnvironment());

Embedded, the default is the conservative one — inspect, manage, execute — because a host passing its own Server is naming a daemon this process did not create. That is a smaller surface than the CLI on a socket it started itself, which also gets teardown. To choose deliberately, pass environment: { LIBTMUX_TOOLSETS: ... } or a whole policy; there is no toolsets option.

To drive it in-process — a test, or a host that is both ends — link a transport pair rather than spawning anything. This is a literal excerpt of examples/mcp-agent/mcp-agent.ts, which the integration suite runs against a real tmux server:

import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";

import { createTmuxMcpServer } from "@libtmux/mcp";

const client = new Client({ name: "example", version: "0.0.0" });
const [clientSide, serverSide] = InMemoryTransport.createLinkedPair();
await Promise.all([
  createTmuxMcpServer(server, {
    environment: { LIBTMUX_TOOLSETS: "inspect,execute" },
  }).connect(serverSide),
  client.connect(clientSide),
]);

environment configures the MCP tool policy. Caller identity is read separately from process.env when the factory is constructed; an embedded test can override that complete input with callerEnvironment.

Embedding freezes the same surface as the executable. Pass a complete policy environment when constructing the server; changing the object later cannot add a hidden tool or alter batch authority. If the host wants the executable's automatic minimal-daemon provenance decision, launch the executable rather than claiming that provenance for an arbitrary embedded Server.

What this does not do

capture_since reports a pane's byte stream in the order it was written. A program that draws by moving the cursor — a progress bar, a full-screen TUI — reads jumbled there, because resolving cursor addressing would mean emulating a terminal. capture_pane reads tmux's rendered grid and is the answer when that matters.

The MCP surface does not drive attachment-bound client UX such as copy mode, clock mode, choose-tree, command prompts, menus, popups, or mouse gestures. Those interactions depend on a person's key table, client, clipboard, and timing. Read their visible result with capture_pane, inspect mode through snapshot_pane, and leave entry and cleanup to the attached client.

There are no dynamic topology resources, subscriptions, prompts, background jobs, generic option or environment mutation, public buffer management, or host-command tools. These omissions are deliberate: the typed surface covers the bounded tmux operations an agent needs without creating a second scripting language beside tmux itself.

License

MIT