Skip to content

Commit 6432646

Browse files
committed
mcp(refactor[server]): structure base instructions as composable segments
``_BASE_INSTRUCTIONS`` had grown into a six-paragraph string literal mixing positive guidance (HIERARCHY, READ-TOOLS, WAIT-DON'T-POLL) with *gap-explainers* (HOOKS-ARE-READ-ONLY, BUFFERS) — sentences that exist to tell agents why an expected tool is absent. Appending more gap-explainers to one literal makes "should this be a server segment or a tool description?" easy to skip. Split into named module constants joined by ``"\n\n"``: ``_INSTR_HIERARCHY``, ``_INSTR_METADATA_VS_CONTENT``, ``_INSTR_READ_TOOLS``, ``_INSTR_WAIT_NOT_POLL``, ``_INSTR_HOOKS_GAP``, ``_INSTR_BUFFERS_GAP``. The module-level comment names the gap-explainer pattern explicitly so the next contributor sees the "prefer the tool description" decision before adding another segment. Output text is byte-identical (verified via ``repr`` diff); existing substring assertions in ``tests/test_server.py`` continue to pass unchanged.
1 parent e5926bd commit 6432646

1 file changed

Lines changed: 55 additions & 6 deletions

File tree

src/libtmux_mcp/server.py

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,46 +42,95 @@
4242
#: :func:`libtmux_mcp._utils._get_server`.
4343
_ServerCacheKey: t.TypeAlias = tuple[str | None, str | None, str | None]
4444

45-
_BASE_INSTRUCTIONS = (
45+
# ---------------------------------------------------------------------------
46+
# _BASE_INSTRUCTIONS — composed from named segments.
47+
#
48+
# The string handed to FastMCP grew organically from "what does this server
49+
# do?" toward a hybrid of positive guidance (HIERARCHY, READ_TOOLS,
50+
# WAIT_NOT_POLL) and *gap-explainers* (HOOKS_GAP, BUFFERS_GAP) that document
51+
# why a tool the agent might expect is absent. Splitting into named
52+
# constants keeps additions deliberate: when a new ``_GAP`` segment feels
53+
# tempting, prefer first to push the explanation into the relevant tool's
54+
# docstring/description (where the agent encounters it at call time) and
55+
# only fall back to a server-level segment when the gap is *server-shaped*
56+
# (e.g. an entire tool family is intentionally missing).
57+
#
58+
# Output text is byte-identical to the previous monolith; tests assert on
59+
# substrings of ``_BASE_INSTRUCTIONS``, so keeping the join shape stable
60+
# matters.
61+
# ---------------------------------------------------------------------------
62+
63+
_INSTR_HIERARCHY = (
4664
"libtmux MCP server for programmatic tmux control. "
4765
"tmux hierarchy: Server > Session > Window > Pane. "
4866
"Use pane_id (e.g. '%1') as the preferred targeting method - "
4967
"it is globally unique within a tmux server. "
5068
"Use send_keys to execute commands and capture_pane to read output. "
5169
"Targeted tmux tools accept an optional socket_name parameter "
5270
"(defaults to LIBTMUX_SOCKET env var); list_servers discovers "
53-
"sockets via TMUX_TMPDIR plus optional extra_socket_paths instead.\n\n"
71+
"sockets via TMUX_TMPDIR plus optional extra_socket_paths instead."
72+
)
73+
74+
_INSTR_METADATA_VS_CONTENT = (
5475
"IMPORTANT — metadata vs content: list_windows, list_panes, and "
5576
"list_sessions only search metadata (names, IDs, current command). "
5677
"To find text that is actually visible in terminals — when users ask "
5778
"what panes 'contain', 'mention', 'show', or 'have' — use "
5879
"search_panes to search across all pane contents, or list_panes + "
59-
"capture_pane on each pane for manual inspection.\n\n"
80+
"capture_pane on each pane for manual inspection."
81+
)
82+
83+
_INSTR_READ_TOOLS = (
6084
"READ TOOLS TO PREFER: snapshot_pane returns pane content plus "
6185
"cursor position, mode, and scroll state in one call — use it "
6286
"instead of capture_pane + get_pane_info when you need context. "
6387
"display_message evaluates a tmux format string (e.g. "
6488
"'#{pane_current_command}', '#{session_name}') against a target "
6589
"and returns the expanded value — cheaper than parsing captured "
6690
"output. (The tool is named after the tmux 'display-message -p' "
67-
"verb it wraps; its MCP title is 'Evaluate tmux Format String'.)\n\n"
91+
"verb it wraps; its MCP title is 'Evaluate tmux Format String'.)"
92+
)
93+
94+
_INSTR_WAIT_NOT_POLL = (
6895
"WAIT, DON'T POLL: for 'run command, wait for output' workflows "
6996
"use wait_for_text (matches text/regex on a pane) or "
7097
"wait_for_content_change (waits for any change). These block "
7198
"server-side until the condition is met or the timeout expires, "
7299
"which is dramatically cheaper in agent turns than capture_pane "
73-
"in a retry loop.\n\n"
100+
"in a retry loop."
101+
)
102+
103+
#: Gap-explainer: write-hook tools are intentionally absent. See module
104+
#: comment above for when to add another ``_GAP`` segment vs. push the
105+
#: explanation into a tool description.
106+
_INSTR_HOOKS_GAP = (
74107
"HOOKS ARE READ-ONLY: inspect via show_hooks / show_hook. Write-hook "
75108
"tools are intentionally not exposed — tmux hooks survive process "
76109
"death, so they belong in your tmux config file, not a transient "
77-
"MCP session.\n\n"
110+
"MCP session."
111+
)
112+
113+
#: Gap-explainer: ``list_buffers`` is intentionally absent because tmux
114+
#: buffers can include OS clipboard history. See module comment above.
115+
_INSTR_BUFFERS_GAP = (
78116
"BUFFERS: load_buffer stages content, paste_buffer delivers it into "
79117
"a pane, delete_buffer removes the staged buffer. Track owned "
80118
"buffers via the BufferRef returned from load_buffer — there is no "
81119
"list_buffers tool because tmux buffers may include OS clipboard "
82120
"history (passwords, private snippets)."
83121
)
84122

123+
_BASE_INSTRUCTIONS = "\n\n".join(
124+
(
125+
_INSTR_HIERARCHY,
126+
_INSTR_METADATA_VS_CONTENT,
127+
_INSTR_READ_TOOLS,
128+
_INSTR_WAIT_NOT_POLL,
129+
_INSTR_HOOKS_GAP,
130+
_INSTR_BUFFERS_GAP,
131+
)
132+
)
133+
85134

86135
def _build_instructions(safety_level: str = TAG_MUTATING) -> str:
87136
"""Build server instructions with agent context and safety level.

0 commit comments

Comments
 (0)