Skip to content

mcp(feat): Confirm kill_session, add pane viewer and icons - #149

Merged
tony merged 7 commits into
mainfrom
mcp-2026-000
Nov 7, 2026
Merged

tony merged 7 commits into
mainfrom
mcp-2026-000

Conversation

@tony

@tony tony commented Nov 7, 2026

Copy link
Copy Markdown
Member

Summary

  • Add user confirmation before kill_session on both MCP protocol eras.
  • Prevent a confirmed kill from reaching a same-name session recreated mid-confirmation.
  • Add an MCP Apps pane viewer for live snapshot_pane results.
  • Add SVG and PNG server icons.
  • Record confirmation rounds in the audit log as outcome=input_required.
  • Add a snapshot test of the agent-facing tool surface.

Changes by area

Teardown confirmation

  • src/libtmux_mcp/tools/session_tools.py: kill_session becomes async and asks through ctx.elicit() on 2025-11-25 connections and through an InputRequiredResult on 2026-07-28 connections. A decline returns an error result and leaves the session in place.
  • src/libtmux_mcp/tools/session_tools.py: the target is identified by #{pid}:#{start_time}:#{session_id}, carried in request_state on 2026-07-28 and rechecked after the await on 2025-11-25.
  • src/libtmux_mcp/middleware.py: AuditMiddleware logs outcome=input_required when the result is fastmcp's InputRequiredToolResult.

Pane viewer

  • src/libtmux_mcp/resources/viewer.py: ui://libtmux-mcp/pane-viewer.html draws a snapshot as a terminal screen with the cursor marked, and re-runs snapshot_pane every 2s while visible and "live" is checked.
  • src/libtmux_mcp/tools/pane_tools/__init__.py: snapshot_pane links the page through meta["ui"]. Clients without the Apps extension receive the same result as before.

Server

  • src/libtmux_mcp/server.py: SVG and 192px PNG icons hosted on the docs site.

Tests and docs

  • tests/test_server.py: test_tool_surface_matches_snapshot, a syrupy JSON snapshot holding a hash and byte size per tool, and test_server_icons_reach_client.
  • tests/test_session_tools.py: confirmation cases on both eras.
  • tests/test_resources_integration.py, tests/test_middleware.py: the viewer link and CSP, and the audit outcome.
  • docs/resources.md, docs/tools/session/kill-session.md, docs/tools/pane/snapshot-pane.md, CHANGES.

Design decisions

  • The session identity includes the tmux server's pid. tmux numbers sessions from $0 on every server start, so killing the last session and creating one with the same name reuses the id, and #{start_time} has one-second resolution.
  • A client without the elicitation capability kills without asking, as it does today. Agent CLIs already gate tool calls behind their own approval prompt, and refusing would break every non-eliciting client.
  • The viewer link goes through meta["ui"], the dict FastMCP's app= writes. sphinx-autodoc-fastmcp's collector rejects app= and would drop every pane tool from the docs. The tool-surface snapshot shows the wire output is identical.
  • The viewer uses fastmcp's low-level fastmcp.apps API, so it adds no Prefab or fastmcp[apps] dependency. It loads the ext-apps SDK pinned at 2.0.3 from unpkg, the one origin its CSP admits.
  • Icons are hosted, not embedded as data: URIs, so the wheel carries no image data and the handshake stays small. One mark serves both themes, as in the docs sidebar.

Test plan

  • kill_session accept, decline, no elicitation capability, and target recreated while asking, on 2025-11-25 and 2026-07-28 (test_kill_session_confirmation)
  • Both protocol eras receive both icons (test_server_icons_reach_client)
  • snapshot_pane links the viewer, served as text/html;profile=mcp-app with its CSP (test_pane_viewer_linked_from_snapshot_pane)
  • A confirmation round never logs outcome=ok (test_audit_middleware_marks_input_required)
  • Tool-surface snapshot matches, and fails on a one-word description edit (test_tool_surface_matches_snapshot)
  • Viewer page renders a live pane and refreshes it through callServerTool in a browser host built on the ext-apps AppBridge
  • Full suite, ruff format --check, ruff check, and mypy .
  • just build-docs with no warnings; pane tool pages and the pane viewer section render
  • Viewer inside Claude Desktop or VS Code
  • Full suite across the CI tmux-version matrix

@codecov-commenter

codecov-commenter commented Nov 7, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 86.97%. Comparing base (cc6d932) to head (d0702ed).
⚠️ Report is 8 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #149      +/-   ##
==========================================
+ Coverage   86.81%   86.97%   +0.16%     
==========================================
  Files          46       47       +1     
  Lines        3899     3947      +48     
  Branches      587      596       +9     
==========================================
+ Hits         3385     3433      +48     
  Misses        354      354              
  Partials      160      160              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

tony added 7 commits November 7, 2026 16:52
why: Nothing caught an unintended change to what clients receive for a
tool: a reworded description, a schema field, a moved toolset tag. Each
costs every agent session context, and none failed a test.

what:
- Add test_tool_surface_matches_snapshot, a syrupy JSON snapshot of
  every registered tool, disabled ones included, keyed by name with a
  short sha256 of its wire form and its size in bytes
- Build the server in a subprocess with LIBTMUX_* scrubbed, since
  several of those variables reshape schemas at import
- Regenerate after review with --snapshot-update
why: Hosts that show a server logo, such as VS Code, had none to show.

what:
- Declare the docs site's SVG mark and its 192px PNG on the FastMCP
  constructor; clients must render PNG and should render SVG
- Use one mark for both themes, as the docs sidebar does
- Host the images rather than embed them, so the wheel carries no image
  data and the handshake stays small
- Test that both protocol eras receive both icons
why: On the 2026-07-28 protocol a tool that asks the user a question
returns an InputRequiredResult and is called again with the answer.
AuditMiddleware logged that first round as outcome=ok, identical to
the round that performed the action, so the audit log could not tell
"asked to kill" from "killed".

what:
- Log outcome=input_required when the result is fastmcp's
  InputRequiredToolResult
- Test that such a round never records outcome=ok
why: kill_session ends every process in the session's panes, and the
server had no way to involve the user. MCP elicitation lets the server
ask, but the two protocol eras ask differently: 2025-11-25 sends a
request mid-call, 2026-07-28 returns the question and is called again.

what:
- Ask through ctx.elicit() on handshake-era connections and through an
  InputRequiredResult on 2026-07-28 ones; a decline returns an error
  result and leaves the session
- Keep the kill unconfirmed when the client declares no elicitation
  capability, so non-eliciting clients behave as before
- Identify the target by #{pid}:#{start_time}:#{session_id}. tmux
  numbers sessions from $0 on every server start, so a same-name
  session recreated while the user is asked can carry the same id;
  start_time has one-second resolution and pid tells the two servers
  apart. The 2026-07-28 path carries the identity in request_state and
  the handshake path rechecks it after the await
- Make kill_session async for the Context it needs; the wire schema
  is unchanged, as the tool-surface snapshot confirms
- Test accept, decline, no capability, and a recreated target on both
  eras
why: Hosts that render MCP Apps (Claude Desktop, VS Code) could show a
pane's screen beside the conversation instead of a JSON blob, and keep
it current while a command runs.

what:
- Add ui://libtmux-mcp/pane-viewer.html, a page that renders a
  snapshot_pane result as a terminal screen with the cursor marked and
  re-runs snapshot_pane every 2s while visible and "live" is checked
- Link it from snapshot_pane through AppConfig; clients without the
  Apps extension ignore the link and receive the same result as before
- Load the ext-apps SDK pinned at 2.0.3 from unpkg, the one origin the
  page's CSP admits
- Pad rows out to the cursor row: snapshot content drops trailing
  blank rows, where the cursor often sits
- Use fastmcp's low-level apps API, which needs no Prefab or
  fastmcp[apps] extra
- Test the link, the resource's MIME type and CSP; the tool-surface
  snapshot records snapshot_pane's added _meta
why: sphinx-autodoc-fastmcp collects tools by calling each module's
register() with a stand-in whose tool() accepts title, annotations,
tags and meta only. The app= keyword on snapshot_pane raised a
TypeError there, so the docs build skipped every pane tool. The build
still exited 0, with a single warning.

what:
- Pass the viewer link as meta["ui"], the dict FastMCP's app= writes;
  clients receive the same tool, as the unchanged tool-surface
  snapshot confirms
- just build-docs is warning-free again
why: The server gained a confirmation step on kill_session, an MCP Apps
pane viewer and icons, and the docs still said every resource is a
tmux:// template.

what:
- Add What's new entries for kill_session confirmation, the pane
  viewer and server icons, and a Development entry for the
  tool-surface snapshot
- Document the ui:// pane viewer in the resources page, limiting the
  template and tmux-query statements to tmux:// resources
- Note the confirmation on the kill-session page and the viewer on the
  snapshot-pane page
@tony
tony merged commit c57314c into main Nov 7, 2026
10 checks passed
tony added a commit that referenced this pull request Nov 7, 2026
why: #149 made kill_session ask the user before killing, which left
kill_window, kill_pane and kill_server, each as destructive, acting on
the agent's word alone (#152). A tool that asks also exposed that
call_read_tools_batch would report a nested question as a successful
row with empty content (#150), and the prompting guide told agents to
confirm only kill_session and kill_server themselves (#153).

what:
- Ask the user before kill_window, kill_pane and kill_server kill, on
  both protocol eras, as kill_session does. Each question names what
  would be lost: the window's name and id, the pane's id and running
  command, or the server's socket and session count. A decline returns
  an error and leaves the target in place. A client without the
  elicitation capability kills without asking, as before.
- Share one confirmation flow in _utils.py. _confirm_teardown runs the
  two-era elicitation: ctx.elicit() on 2025-11-25 connections, and an
  InputRequiredResult carrying the target's identity in request_state
  on 2026-07-28 connections. _ask_before_teardown adds the decline
  error and rechecks the identity before the kill, and _tmux_identity
  reads #{pid}:#{start_time}:<id> from any tmux object. kill_session
  moves onto the same helper.
- Key each tool's identity on its own id (#{window_id}, #{pane_id}),
  and kill_server on the server's #{pid}:#{start_time} alone, since it
  has no object id. A target replaced while the user is being asked,
  including a server restarted on the same socket, is left alone.
- Leave every tool schema unchanged: FastMCP injects ctx and hides it
  from the schema, and the tool-surface snapshot does not move.
- Fail a call_read_tools_batch row whose tool asks for user input, with
  a hint to call the tool directly, since a row has no channel back to
  the user. The batch admits only inspect tools and none of them ask
  today, so the guard covers the first one that does.
- Pin with a test that the response limiter passes a question through
  as input-required. fastmcp's ResponseLimitingMiddleware returns an
  InputRequiredToolResult untouched, and the tail-preserving subclass
  rebuilds only results it trimmed.
- Test accept, decline and client-cannot-ask on both eras for each of
  the three tools, and the batch refusal with its hint.
- Add a Confirmation note to the kill-window, kill-pane and
  kill-server pages, name all four kill tools in the prompting guide's
  sample prompt with a note that its confirm-first sentence can be
  dropped in clients that ask through elicitation, and widen the
  CHANGES entry to all four tools.
@tony
tony deleted the mcp-2026-000 branch November 7, 2026 23:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants