Skip to content

Commit ec00963

Browse files
authored
docs(adr): Define tmux MCP capability contract (#130)
Define a durable conformance contract for tmux MCP implementations without turning the ADR into an implementation dashboard. - Separate socket-scoped object selection, direct process reach, tmux effects, and whole-call annotations. - Resolve one startup tool surface with named override and aggregate-authority rules. - State containment, ambient execution, shared-socket, output, matching, and redaction boundaries.
2 parents d69ec57 + 2af0b0c commit ec00963

4 files changed

Lines changed: 438 additions & 0 deletions

File tree

Lines changed: 376 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,376 @@
1+
(adr-capability-model)=
2+
3+
# ADR 0001: Capability boundaries for tmux MCP implementations
4+
5+
## Abstract
6+
7+
This record defines the shared capability contract for tmux Model Context
8+
Protocol (MCP) implementations. It specifies how implementations describe
9+
available operations, how a tmux socket limits the structured tool namespace,
10+
and what MCP annotations do and do not express. It does not prescribe an
11+
implementation language, internal class design, or rollout sequence.
12+
13+
## Status
14+
15+
Proposed. Supersedes the former three-level capability model.
16+
17+
## Context and problem
18+
19+
A tmux MCP gives an agent a terminal. The implementation must describe that
20+
authority without confusing tmux object selection, process execution, client
21+
consent, and operating-system confinement.
22+
23+
One ordered safety scale cannot describe those independent concerns. Running a
24+
shell command and deleting a tmux window are different capabilities, not
25+
different amounts of one capability. MCP also defines
26+
[`readOnlyHint`, `destructiveHint`, `idempotentHint`, and `openWorldHint`](https://github.com/modelcontextprotocol/python-sdk/blob/v1.29.1/src/mcp/types.py#L1247-L1294)
27+
for client presentation. Reusing those annotations as severity labels would
28+
change their protocol meaning.
29+
30+
## Scope and non-goals
31+
32+
This record applies to MCP servers that expose structured tmux operations. It
33+
defines their shared capability vocabulary, tool-surface behavior, annotation
34+
semantics, execution boundary, and disclosure obligations.
35+
36+
This record does not define operating-system isolation, decide which shell
37+
commands an operator permits, or make the model an enforcement boundary. It
38+
does not track implementation progress or prescribe a programming language,
39+
framework, storage format, or build system.
40+
41+
## Terminology
42+
43+
**tmux MCP implementation**
44+
: An MCP server implementation that exposes structured tools for tmux.
45+
46+
**server process**
47+
: One running instance of a tmux MCP implementation.
48+
49+
**structured tool**
50+
: A typed MCP operation that targets tmux or a program running in a tmux pane.
51+
52+
**selected socket**
53+
: The single tmux server socket chosen by a server process at startup.
54+
55+
**direct operation**
56+
: The operation a structured tool requests, excluding behavior already
57+
configured in tmux, such as command aliases and hooks.
58+
59+
**workload process**
60+
: A pane or host process whose behavior can be influenced by caller input. A
61+
control-plane process used only to issue a tmux request is not a workload
62+
process.
63+
64+
**whole-call MCP annotation**
65+
: A standard MCP annotation describing the observable tool call as a whole,
66+
including configured tmux behavior activated by the direct operation.
67+
68+
**structured tool surface**
69+
: The fixed set of tools a server process advertises and accepts.
70+
71+
**host command**
72+
: Client-authored executable input handed directly to a process outside a tmux
73+
pane.
74+
75+
**minimal tmux configuration**
76+
: Configuration supplied by the implementation that loads no user configuration,
77+
plugin, hook, or status job.
78+
79+
## Conformance
80+
81+
In this record, uppercase **MUST**, **MUST NOT**, **SHOULD**, **SHOULD NOT**, and
82+
**MAY** use the meanings defined by
83+
[BCP 14](https://datatracker.ietf.org/doc/html/rfc8174). Lowercase forms have
84+
their ordinary English meanings.
85+
86+
A **conforming tmux MCP implementation** satisfies every applicable **MUST** and
87+
**MUST NOT** requirement in this record. The Terminology, Conformance,
88+
Architectural decisions, and Security and reliability considerations sections
89+
are normative. Sections and paragraphs labeled informative do not define
90+
conformance requirements.
91+
92+
## Architectural decisions
93+
94+
### CM-1: A tmux socket limits addressable tmux objects; it is not a security boundary
95+
96+
A server process **MUST** select exactly one tmux socket before exposing
97+
structured tools. The server process **MUST** retain that socket for its
98+
lifetime. A structured tool **MUST NOT** accept a per-call socket selector. A
99+
structured tool **MUST NOT** address tmux objects outside the selected socket.
100+
101+
The default configuration **MUST** select a product-scoped dedicated socket. A
102+
server process that creates the tmux server on that socket **MUST** use a minimal
103+
tmux configuration. Selecting an inherited or user-configured socket **MUST**
104+
require explicit operator configuration. A server process that finds an existing
105+
server on the selected socket **MUST NOT** claim that server has minimal
106+
configuration provenance.
107+
108+
The selected socket limits which tmux sessions, windows, and panes structured
109+
tools can name. It does not restrict filesystem access, process execution,
110+
network access, credentials, or access through commands running in a pane.
111+
112+
**Rationale, informative.** Socket pinning prevents accidental cross-server
113+
object selection. It does not provide operating-system isolation and must not be
114+
described as a sandbox.
115+
116+
### CM-2: Independent properties describe each tool's direct capability
117+
118+
Every structured tool **MUST** declare its direct process reach, direct tmux
119+
effect, and output classes. An implementation **MUST NOT** collapse those
120+
properties into an ordered safety level.
121+
122+
Direct process reach **MUST** use one of these values:
123+
124+
- `none`: starts no workload process and delivers no client-controlled input to
125+
one.
126+
- `configured-process`: starts a pane workload process without accepting a
127+
command payload or client-controlled tmux-format input.
128+
- `pane-input`: delivers client-controlled keys or text to a pane program.
129+
- `pane-command`: runs a client-authored shell command in a pane.
130+
131+
A conforming implementation **MUST** reserve `host-command` to describe a
132+
prohibited public capability.
133+
134+
Direct tmux effect **MUST** be a set containing one or more of `observe`,
135+
`change`, and `delete`. Output classes **MUST** be a set drawn from
136+
`tmux-metadata`, `terminal-content`, `process-environment`, and
137+
`configured-command`. An implementation **MUST** separately record whether a
138+
tool may expose secrets or return untrusted content.
139+
140+
These properties describe the direct operation. Whole-call MCP annotations
141+
separately account for execution or mutation added by configured aliases and
142+
hooks.
143+
144+
**Example, informative.** `rename_window` changes tmux state without accepting
145+
executable input. `run_shell_command` also changes tmux state and accepts a
146+
client-authored pane command. Their tmux effects overlap; their process reach
147+
does not.
148+
149+
### CM-3: One startup decision defines the advertised and callable tool surface
150+
151+
Every structured tool **MUST** belong to exactly one unordered toolset:
152+
`inspect`, `manage`, `execute`, or `teardown`.
153+
154+
On the default dedicated socket with minimal tmux configuration, an
155+
implementation **MUST** enable all four toolsets by default. On an inherited or
156+
user-configured socket, an implementation **MUST** require explicit operator
157+
selection before enabling `teardown`. It **MUST** apply the same requirement to
158+
an existing server whose configuration provenance is unknown.
159+
160+
At startup, an implementation **MUST** expand the selected toolsets. It **MUST**
161+
then add named inclusions. It **MUST** then remove named exclusions. A named
162+
exclusion **MUST** win over every inclusion path.
163+
164+
An implementation **MUST** reject unknown toolset and tool names at startup. It
165+
**MUST** freeze the effective structured tool surface for the server process's
166+
lifetime. A tool outside that surface **MUST NOT** be advertised. A tool outside
167+
that surface **MUST NOT** be callable by name.
168+
169+
An aggregate tool **MAY** invoke a nested operation that is not separately
170+
advertised. The aggregate tool **MUST** declare that nested operation as part of
171+
its own authority. A named exclusion **MUST** remove an operation from every
172+
aggregate tool's nested authority.
173+
174+
The toolsets support inventory configuration, context reduction, model routing,
175+
and documentation navigation. They do not restrict what an enabled pane-input
176+
or pane-command tool can cause a pane program to do.
177+
178+
**Rationale, informative.** Unordered toolsets permit combinations such as
179+
`inspect,teardown`. Applying the same effective surface to discovery and
180+
invocation prevents a hidden tool from remaining callable.
181+
182+
### CM-4: MCP annotations describe an entire tool call
183+
184+
An implementation **MUST** use standard MCP annotations only with their protocol
185+
meanings. A whole-call MCP annotation **MUST** account for the direct operation
186+
and configured tmux behavior that the operation activates. An implementation
187+
**MUST NOT** use standard annotations as authorization decisions or project
188+
severity labels.
189+
190+
Direct process reach, direct tmux effect, and output classes **MUST** remain
191+
separate from standard MCP annotations. Without evidence for the whole-call
192+
claim, an implementation **MUST** set `readOnlyHint` to `false`. Without evidence
193+
for the whole-call claim, an implementation **MUST** set `destructiveHint` to
194+
`true`. Without evidence for the whole-call claim, an implementation **MUST** set
195+
`idempotentHint` to `false`. Without evidence for the whole-call claim, an
196+
implementation **MUST** set `openWorldHint` to `true`.
197+
198+
An implementation **MUST** set `readOnlyHint` to `true` only when the whole call
199+
cannot modify its environment. It **MUST** set `destructiveHint` to `false` only
200+
when the whole call performs additive updates at most. It **MUST** set
201+
`idempotentHint` to `true` only when repeating the call with the same arguments
202+
has no additional effect. It **MUST** set `openWorldHint` to `false` only when the
203+
whole call cannot interact with external entities.
204+
205+
MCP annotations support client consent interfaces. They do not enforce tool
206+
authorization, operating-system confinement, or command policy.
207+
208+
### CM-5: One authoritative capability definition governs every public claim
209+
210+
An implementation **MUST** maintain one authoritative, machine-readable
211+
capability definition for its structured tools. Tool registration, the effective
212+
structured tool surface, tool descriptions, documentation, and capability
213+
reporting **MUST** agree with that definition.
214+
215+
The capability definition **MUST** classify every caller-controlled input by the
216+
interpreter boundary it reaches. An implementation **MUST** validate capability
217+
claims against those input sinks rather than infer behavior from parameter
218+
names.
219+
220+
**Rationale, informative.** tmux expands formats in argument positions whose
221+
names do not reveal the interpreter boundary. A shared manifest generated or
222+
validated during CI is one implementation approach, not a required storage
223+
format.
224+
225+
### CM-6: Public tools do not execute client-authored host commands
226+
227+
A conforming implementation **MUST NOT** expose a structured tool with
228+
`host-command` process reach. It **MUST NOT** hand caller text directly to a
229+
host-side shell. Client-authored shell commands **MAY** run through a
230+
`pane-command` tool, where the process is represented by a tmux pane.
231+
232+
This rule constrains the direct MCP surface. It does not prevent a pane command
233+
from invoking tmux's
234+
[`run-shell`](https://github.com/tmux/tmux/blob/3.2a/cmd-run-shell.c#L177-L181),
235+
installing a
236+
[`#()` status job](https://github.com/tmux/tmux/blob/3.2a/format.c#L392-L399),
237+
or starting any process available to the tmux user's account.
238+
239+
### CM-7: Capability disclosure is part of the product contract
240+
241+
Every structured tool description **MUST** begin with a plain-language statement
242+
of its direct process reach and tmux effect. An implementation **MUST** publish
243+
its effective structured tool surface and selected socket. Installation and
244+
trust documentation **MUST** state that execute tools run with the tmux user's
245+
authority.
246+
247+
Documentation **MUST** distinguish socket-scoped object selection from
248+
operating-system confinement. Documentation **MUST** distinguish tool-surface
249+
filtering from authorization. Documentation **MUST** describe whole-call MCP
250+
annotations as consent metadata rather than enforcement.
251+
252+
An implementation **MUST NOT** describe a dedicated socket, restricted tool
253+
surface, payload filter, or MCP annotation as a sandbox or security boundary.
254+
255+
## Consequences
256+
257+
Conservative whole-call annotations may cause clients to prompt more often.
258+
That cost preserves the protocol meaning of the annotations.
259+
260+
Pinning one socket per server process requires separate configured MCP entries
261+
to control separate tmux sockets. The selected socket reduces accidental object
262+
selection without claiming exclusive ownership.
263+
264+
Maintaining one authoritative capability definition adds review and validation
265+
work. It also makes capability drift detectable across registration,
266+
documentation, and runtime disclosure.
267+
268+
Stable tool names become part of the client-consent surface. Renaming a public
269+
tool therefore requires an explicit migration rather than an internal refactor.
270+
271+
## Rejected alternatives
272+
273+
**An ordered safety scale.** One rank cannot represent independent process
274+
reach, tmux effect, and output sensitivity without hiding one of them.
275+
276+
**Tool filtering as authorization.** An enabled pane-input or pane-command tool
277+
can express operations omitted from the structured tool surface. Filtering is
278+
still useful for inventory control and accident reduction.
279+
280+
**Payload blocklists.** Shell and terminal input are composable. A filter that
281+
recognizes selected strings cannot establish a command boundary and would invite
282+
operators to rely on incomplete protection.
283+
284+
**Per-call socket selection.** A caller-selected socket expands every tool's
285+
object namespace and makes one server process represent several trust contexts.
286+
287+
**Public host-command tools.** Host-side commands would bypass the observable
288+
pane process and its completion boundary.
289+
290+
**Generic mutating or destructive aggregates.** A wrapper hides the names on
291+
which client consent policies depend and turns one approval into authority over
292+
unrelated operations.
293+
294+
## Security and reliability considerations
295+
296+
### Ambient tmux behavior
297+
298+
**Background, informative.** tmux
299+
[expands command aliases](https://github.com/tmux/tmux/blob/3.2a/cmd-parse.y#L698-L715)
300+
before dispatch and
301+
[runs after-hooks](https://github.com/tmux/tmux/blob/3.2a/cmd-queue.c#L617-L627)
302+
after many commands. A nominally observational direct operation can therefore
303+
execute or mutate through existing configuration. Independent pane processes,
304+
plugins, event hooks, and status jobs can also run without an MCP call.
305+
306+
A conforming implementation **MUST NOT** claim that it prevents all subprocess
307+
execution. A conforming implementation **MUST** describe ambient tmux behavior
308+
separately from direct process reach.
309+
310+
### Shared sockets
311+
312+
**Boundary, informative.** Every process with access to the selected socket can
313+
create or alter objects on it. A dedicated socket separates the structured
314+
namespace from another tmux server; it does not establish exclusive ownership.
315+
316+
An implementation **MUST NOT** claim ownership of every object on a shared
317+
socket. A self-kill guard **MUST** be described as protection against direct
318+
teardown tools only, not against equivalent pane commands or other clients.
319+
320+
**Shutdown behavior, informative.** With the default `exit-empty` enabled and
321+
`exit-unattached` disabled
322+
([tmux option defaults](https://github.com/tmux/tmux/blob/3.2a/options-table.c#L256-L268)),
323+
tmux waits for every session to be removed and for clients to disconnect before
324+
exiting
325+
([tmux exit logic](https://github.com/tmux/tmux/blob/3.2a/server.c#L268-L286)).
326+
Removing one MCP instance's sessions does not stop a shared server while another
327+
session remains. Socket-wide termination remains an operator action because it
328+
may destroy sessions the caller does not own.
329+
330+
### Untrusted and sensitive output
331+
332+
**Background, informative.** Terminal output, environment values, configured
333+
commands, and tmux metadata can contain secrets or untrusted instructions.
334+
335+
An `inspect` tool **MUST NOT** be described as safe merely because its direct
336+
operation is observational. An implementation **MUST** disclose when a tool may
337+
return untrusted content. It **MUST** disclose when a tool may expose secrets.
338+
339+
### Aggregate authority
340+
341+
An aggregate tool **MUST** disclose the complete set of nested tools it can
342+
invoke. Its effective authority **MUST NOT** exceed its advertised nested tool
343+
set. A conforming implementation **MUST NOT** expose a generic mutating or
344+
destructive aggregate.
345+
346+
### Bounded operations
347+
348+
An implementation that claims bounded pattern matching **MUST** bound both input
349+
size and matching execution time. A capture or transport deadline **MUST NOT** be
350+
misreported as a matching timeout.
351+
352+
### Redaction and history suppression
353+
354+
**Boundary, informative.** Audit redaction applies only to the audit record.
355+
History suppression is best-effort shell hygiene.
356+
357+
An implementation **MUST NOT** claim that either mechanism removes data from
358+
client transcripts, pane scrollback, process arguments, shell history outside
359+
its control, or operating-system observation surfaces.
360+
361+
## When to reconsider
362+
363+
Reconsider this decision if MCP gains enforceable authorization or nested-call
364+
annotation semantics, if tmux exposes a public command mode that suppresses all
365+
relevant ambient behavior, or if deployments require a stronger multi-user
366+
boundary than one operating-system account and socket can provide.
367+
368+
Reconsider the shared vocabulary if an implementation cannot express a required
369+
capability without weakening an existing term. Add a new independent property
370+
instead of stretching an old one into a severity rank.
371+
372+
## References
373+
374+
- [Issue #127: tool visibility is not operating-system confinement](https://github.com/tmux-python/libtmux-mcp/issues/127)
375+
- [Source decision for ADR 0001](https://github.com/tmux-python/libtmux-mcp/issues/127#issuecomment-5463431049)
376+
- [Detailed target-state capability model](https://github.com/tmux-python/libtmux-mcp/issues/127#issuecomment-5463166342)

‎docs/dev/adr/index.md‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
(architecture-decisions)=
2+
3+
# Architecture decisions
4+
5+
Architecture decision records document product-wide choices whose trade-offs and
6+
consequences outlive one implementation.
7+
8+
```{toctree}
9+
:maxdepth: 1
10+
11+
0001-capability-model
12+
```

0 commit comments

Comments
 (0)