Trust model¶
This server gives an agent a terminal. What follows is what that does and does not bound.
Toolsets gate MCP tool calls, not tmux¶
Tools are grouped into four sets by what they do:
inspect: Request tmux state or terminal output, or render server-local prompt text. The built-in operation does not pass caller input as a tmux or shell command.
manage: Change tmux-managed structure, presentation, staging, or coordination state. The built-in operation does not supply a shell command, pane input, or a value tmux treats as executable configuration.
execute: Start a pane process, deliver input to one, or store state
that can control later execution. set_option is here, not in
manage: a #(...) job in a status format runs when tmux draws it and repeats
on the status interval, and default-command decides what every future pane
runs.
teardown: Delete tmux objects or retained scrollback. Irreversible at the tmux level.
The sets are unordered. FastMCP visibility and libtmux-mcp middleware both
enforce which MCP tool calls the server advertises and accepts.
LIBTMUX_TOOLSETS=inspect,teardown is therefore a legal surface — an agent
that can look and clean up through this server’s tools, but not type through
them.
LIBTMUX_TOOLSETS, LIBTMUX_TOOLS, and
LIBTMUX_EXCLUDE_TOOLS filter tools only. The tmux:// hierarchy
resources and native prompts remain available when every toolset is disabled.
Dropping a toolset is not containment. It changes what this server
advertises. An enabled execute tool can type the equivalent of anything
you hid, because a pane’s shell runs with your user’s authority. Existing pane
processes and other clients of the same tmux server also remain outside the MCP
call gate.
The tmux server is programmable¶
tmux is a separate, long-lived process. A configured command-alias can
replace a command this server sends, and an after-* hook can run a command
list after many built-in commands. A nominal inspect call can therefore
change state or run a shell without receiving executable input from the MCP
caller.
Hierarchy resource reads are a separate MCP surface, but they send the same
class of tmux queries as inspect tools. A resources/read request can
therefore activate aliases and hooks too. Resources have no ToolAnnotations
and do not produce this server’s tool-call audit record. Native prompts only
return text and do not contact tmux.
Execution can occur without any MCP call. A #(...) job in a status format
runs when tmux redraws the status line and can repeat on the status interval.
No MCP tool filter can intercept work that never passes through this server.
libtmux-mcp startup and shutdown send no tmux commands. Failed tool calls run once: the server does not retry them automatically because tmux may already have applied an alias or hook effect before reporting an error.
A toolset describes the built-in operation its tools request. It does not describe everything the target tmux server may do around that request.
Responsibility by layer¶
Layer |
Owns |
|---|---|
libtmux-mcp |
Input validation and refusal, tmux argv construction, the advertised and callable tool surface, direct-operation classification, wait ceilings, selected high-volume output caps, resource disclosure, and tool-call audit redaction. |
Model or agent |
Chooses requested calls and command text, but is not an enforcement boundary against its own errors or prompt injection. |
MCP client and user |
Whether to request and confirm a call, whether to retry it, and which credentials the agent receives. |
tmux operator |
The target socket, configuration, aliases, hooks, key bindings, status formats, pane programs, and other clients. |
OS and deployment |
Process identity and limits on filesystem, network, credentials, privileges, and resources. |
For local stdio use, the launching client and OS account are the trust context; FastMCP has no OAuth token to authorize. A remote HTTP deployment must authenticate users and enforce authorization on the server as well as asking for client-side confirmation. See FastMCP authorization.
Guarantee by topology¶
Topology |
Strongest guarantee |
|---|---|
Existing or shared tmux server |
The MCP tool-call gate and libtmux-mcp’s input handling; tmux configuration and peer activity remain unknown and mutable. |
Fresh, separately supervised tmux server with a minimal config |
A separate tmux object namespace and known startup configuration for that daemon generation; same-user clients and pane processes can still reconfigure it. |
OS identity, container, or VM boundary |
Effects are limited by the configured process, filesystem, network, credential, privilege, and resource policy. tmux still executes processes inside that boundary. |
A socket alone is an endpoint, not process confinement. Starting a normal tmux
client with -f also does not prove that configuration was used: if the server
already exists, tmux keeps the configuration from that daemon’s startup.
inspect does not mean safe¶
An inspect tool’s built-in command sequence does not pass caller input as a
tmux or shell command. That is a property of these implementations, and it is
the only thing the name claims. A target tmux server may still replace or
extend the requested command through its aliases and hooks.
It is not a claim that the result is harmless. A capture returns whatever the pane holds: credentials someone typed, a command line with a token in it, output from a remote host, text written by another agent. Auto-approving the whole set is a decision to make with that in mind, not one the name endorses. Treat pane and hierarchy-resource output as untrusted data, never as instructions.
Configuration¶
{
"mcpServers": {
"libtmux": {
"command": "uvx",
"args": ["libtmux-mcp"],
"env": {
"LIBTMUX_TOOLSETS": "inspect"
}
}
}
}
LIBTMUX_TOOLSETS is a comma list, defaulting to
inspect,manage,execute. teardown is not in the default: this server
reaches whichever tmux server the environment points at, so deletion is
something you ask for by name. LIBTMUX_TOOLS enables individual
tools regardless of toolset, and LIBTMUX_EXCLUDE_TOOLS refuses
them regardless of every enable above.
An unknown toolset or tool name fails startup rather than being ignored. A typo that silently widened a surface you believed was narrow is worse than a server that will not start.
How it works¶
Two layers use the same tags and names. FastMCP visibility is the primary wire filter: omitted or excluded tools disappear from listings, and direct calls return an unknown-tool error. Middleware rechecks the classification for tools that reach dispatch.
Both fail closed: a tool carrying no recognized toolset is refused, so adding one without classifying it cannot expose it by accident.
Self-kill protection¶
The teardown tools include safeguards against self-harm:
kill_serverteardown refuses to run if the MCP server is inside the target serverkill_sessionteardown refuses to kill the session containing the MCP panekill_windowteardown refuses to kill the window containing the MCP panekill_paneteardown refuses to kill the pane running the MCP server
These protections read both the TMUX and TMUX_PANE environment variables that tmux injects into pane child processes. The TMUX value is formatted socket_path,server_pid,session_id — libtmux-mcp parses the socket path and compares it to the target server’s so the guard only fires when the caller is actually on the same tmux server. A kill across unrelated sockets is allowed; a kill of the caller’s own pane/window/session/server is refused. If the caller’s socket can’t be determined (rare — TMUX_PANE set without TMUX), the guard errs on the side of blocking.
macOS TMUX_TMPDIR caveat¶
The self-kill guard resolves the target server’s socket path in three
steps (_effective_socket_path() in
src/libtmux_mcp/_utils.py):
Use
libtmux.Server.socket_pathif libtmux already has it.Otherwise query the running server via
display-message -p '#{socket_path}'— authoritative because tmux itself reports the path it is actually using, regardless of the MCP process environment. This closes the launchd-vs-interactive-shell gap on macOS whereTMUX_TMPDIRcommonly differs between contexts.Fall back to reconstruction from
TMUX_TMPDIR(or/tmp) + euid + socket name. Only reached when the target server is unreachable (not running), in which case no self-kill is possible anyway and_caller_is_on_server()’s None-socket branch blocks conservatively.
The structural fix shipped in 0.1.x; setting TMUX_TMPDIR explicitly is no longer required for the guard to work, though it remains a useful diagnostic when investigating mismatched-path bug reports.
Footguns inside execute¶
Most manage tools are bounded: resize_pane only
resizes, rename_window only renames. A few have broader
reach because tmux itself exposes broader reach. Treat these as
elevated risk even though the default enables their toolset:
Piping pane output¶
pipe_pane execute pipes a pane’s output through a fixed shell redirection. The
caller chooses the destination path. There is no path allow-list; assume it can
create files anywhere the server process can write.
Mitigations:
Run the server as an unprivileged user with a scoped home directory.
Exclude
executewhen pane control is unnecessary. This narrows the direct tool surface; it does not establish trust or confinement.Audit log records (see below) capture the
output_pathargument so reviewers can spot unexpected destinations.
Setting tmux environment¶
set_environment execute writes into tmux’s global, session, or window environment. Those values propagate into every shell tmux spawns afterwards. An agent that writes PATH, LD_PRELOAD, or AWS_* variables can influence every future command on that scope — including commands the user runs directly, not just commands the agent issues.
Mitigations:
The server audit record replaces the
valueargument with a{len, sha256_prefix}digest, so the value does not appear verbatim inlibtmux_mcp.audit. That redaction does not cover separate library, process, application, or client logs, so operators should still treat the tool as high-privilege.If only a single command needs a non-sensitive env override, prefer having the agent invoke
env VAR=value commandviasend_keysinstead — the blast radius is one command, not every future child. For credentials, pass a reference that the child resolves instead of a literal value through tmux.
Respawning panes¶
respawn_pane execute restarts a pane’s process while preserving the pane id and layout — exactly what an agent wants when a shell wedges. Default kill=True terminates the running process before relaunch. The pane_id and layout are preserved (the point of the tool), but any unsaved REPL state, ssh session, or in-flight job in that pane is lost. Repeated calls are not idempotent — each call kills a new process.
The tool belongs to execute: it terminates one pane process and starts
another, even when the replacement command is omitted.
Mitigations:
pane_idis required (no fallback to “first pane in session/window”). Agents that pass onlysession_nameget anExpectedToolErrorinstead of an unintended kill — resolve vialist_panesinspect first.Any
shellargument is briefly visible in the OS process table and tmux’spane_current_commandmetadata before the spawned shell takes over; the audit log redactsshellpayloads (see below), but do not pass credentials directly even with redaction.The optional
environmentargument accepts either a mapping of string keys and values or a JSON object string, then maps each item to one tmux-e KEY=VALUEflag. For a mapping, the audit log keeps each key visible and replaces each value with a{len, sha256_prefix}digest. A JSON string is redacted as one scalar digest, so its keys are not retained in the audit record. The same OS-process-table caveat asshellapplies:respawn-pane -e DB_PASSWORD=...may briefly appear inpsoutput before the spawned process inherits the env.The same self-pane guard that protects the kill commands also refuses to respawn the pane running the MCP server.
Raw pane input¶
These can execute anything the pane’s shell accepts. There is no shell-syntax
allow-list. The server audit log stores a digest of the content, not the
content itself, so a secret typed via send_keys or
send_keys_batch does not land in that audit record.
History suppression is not secret transport¶
suppress_history on run_command asks the current shell not to persist one space-prefixed command event. suppress_persistent_history=true on the four spawn tools adds best-effort no-disk controls to a new environment. Shell behavior and startup files can defeat either request. History suppression does not isolate the process, does not clear in-memory history or scrollback, and does not hide the command from other observation surfaces:
pane echo and scrollback: the terminal can display input, tmux can retain it in pane history, and an attached terminal can keep its own scrollback.
capture tools and piping:
capture_pane,capture_since,snapshot_pane,search_panes, andpipe_panecan return or route displayed and retained text.hooks: configured tmux hooks, including state visible through
show_hooks, and shell instrumentation can observe process or pane activity independently of shell history.process visibility: command arguments and launch strings can appear in the tmux client argv. Environment values passed to
create_session,create_window,split_window, andrespawn_panecan also remain in a child process environment;create_sessionretains them in tmux session state for future panes, whereshow_environmentcan reveal them. MCP audit redaction does not hide any of these surfaces from host process or tmux environment inspection.MCP client transcripts: clients can retain the original request and response outside the server’s control.
logs:
libtmux_mcp.auditrecords redacted arguments and whether the call succeeded or raised; it does not contain tool return values. Redaction applies only to these audit records and does not rewrite separate records emitted by libtmux, FastMCP, shells, or MCP clients. libtmux DEBUG or error records may contain shell-joined tmux arguments, while MCP client request logs and application logs remain outside the server’s guarantee.
Prefer credential references that a process resolves from a secret manager, scoped file descriptor, or preconfigured host lookup. Avoid literal credentials in command, raw keys or text, shell, and environment arguments; history suppression cannot retract a value after another surface records it.
Audit log¶
Every tool call emits one INFO record on the libtmux_mcp.audit logger carrying:
tool— the tool nameoutcome—okorerror, witherror_typeon failureduration_msclient_id/request_id— from the fastmcp context when availableargs— a summary of arguments. Sensitive scalar keys (keys,text,command,value,content,shell, and string-formenvironment) are replaced by{len, sha256_prefix}. Mapping-formenvironmentkeeps its keys but digests each value individually. Non-sensitive strings over 200 characters are truncated.
Route this logger to a dedicated sink if you want a durable audit trail; it is deliberately namespaced separately from the main libtmux_mcp logger.
Tool annotations¶
MCP defines four standard hints for the behavior of the whole tool call. Clients may use positive hints to skip confirmation or retry a call. This server can target an existing tmux server selected by each call, and it cannot establish that the server has no aliases or hooks or that another client will not add them. Every tool that requests a tmux operation therefore advertises the same conservative static hints:
readOnlyHint |
destructiveHint |
idempotentHint |
openWorldHint |
|---|---|---|---|
false |
true |
false |
true |
These values are hints, not authorization. They do not say that every call
modifies state, destroys data, has an additional effect when repeated, or
reaches outside tmux. They decline to promise otherwise for every target. The
project-owned inspect, manage, execute, and teardown toolsets preserve
the direct-operation distinctions that the standard hints cannot express here.
Clients that ignore project tags cannot recover those distinctions from the
four hints alone; they must use the tool name, schema, description, or an
operator-selected tool surface.
The optional list_prompts and get_prompt adapter tools do not contact tmux.
They belong to inspect because they render server-local prompt text without
changing tmux, and advertise true, false, true, false respectively.