Skip to content

mcp(feat[teardown]): Ask before every kill tool - #154

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

tony merged 4 commits into
mainfrom
mcp-2026-001

Conversation

@tony

@tony tony commented Nov 7, 2026

Copy link
Copy Markdown
Member

Stacked on #149, which adds the kill_session confirmation this extends. Merge #149 first.

Fixes #150, fixes #152, fixes #153.

Summary

  • Add elicitation confirmation to kill_window, kill_pane and kill_server.
  • Name the window, the pane's running command, or the session count in each question.
  • Share one confirmation helper across all four kill tools.
  • Fail a call_read_tools_batch row whose tool asks for user input.
  • Pin the response limiter's pass-through of a confirmation question with a test.
  • Name all four kill tools in the prompting guide's sample prompt.

Changes by area

Teardown confirmation

  • src/libtmux_mcp/_utils.py: _confirm_teardown holds the two-era elicitation flow, and _ask_before_teardown adds the decline error and the identity recheck. _tmux_identity reads #{pid}:#{start_time}:<id> from any tmux object.
  • src/libtmux_mcp/tools/window_tools.py, src/libtmux_mcp/tools/pane_tools/lifecycle.py, src/libtmux_mcp/tools/server_tools.py: kill_window, kill_pane and kill_server become async and ask through the helper.
  • src/libtmux_mcp/tools/session_tools.py: kill_session uses the same helper.

Batch

  • src/libtmux_mcp/tools/batch_tools.py: _ensure_tool_result raises a row error for fastmcp's InputRequiredToolResult and tells the agent to call the tool directly.

Docs

  • docs/tools/window/kill-window.md, docs/tools/pane/kill-pane.md, docs/tools/server/kill-server.md: a Confirmation note on each page.
  • docs/topics/prompting.md: the sample prompt names all four kill tools. A note says the confirm-first sentence can be dropped in clients that ask through elicitation.
  • CHANGES: the kill-confirmation entry covers all four tools.

Design decisions

  • No tool schema changes. FastMCP injects ctx and hides it from the schema, and the tool-surface snapshot is unchanged.
  • kill_server has no object id to compare, so its identity is the server's #{pid}:#{start_time}. A server restarted on the same socket during the question gets a new pid and is left alone.
  • The batch refuses a question instead of relaying it, since a row has no channel back to the user. call_read_tools_batch admits only inspect tools, none of which ask today, so the guard is for the next one that does.
  • The response limiter stays as it is. fastmcp's ResponseLimitingMiddleware returns an InputRequiredToolResult untouched, and our subclass rebuilds only results it trimmed.

Test plan

  • kill_window, kill_pane and kill_server accept, decline, and client-cannot-ask cases on 2025-11-25 and 2026-07-28 (test_kill_tools_ask_before_killing)
  • A target recreated while the user is asked is not killed, through the shared helper (test_kill_session_confirmation)
  • A batched tool that asks for input fails its row with the direct-call hint (test_batch_refuses_a_tool_that_asks_for_input)
  • The response limiter passes a question through as input-required (test_response_limiter_passes_a_question_through)
  • Tool-surface snapshot unchanged (test_tool_surface_matches_snapshot)
  • Full suite, ruff format --check, ruff check, and mypy .
  • just build-docs with no warnings
  • Confirmation prompts in a live elicitation client
  • 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 87.08%. Comparing base (c57314c) to head (2ee0683).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #154      +/-   ##
==========================================
+ Coverage   86.97%   87.08%   +0.10%     
==========================================
  Files          47       47              
  Lines        3947     3964      +17     
  Branches      596      601       +5     
==========================================
+ Hits         3433     3452      +19     
+ Misses        354      353       -1     
+ Partials      160      159       -1     

☔ 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
tony changed the base branch from mcp-2026-000 to main November 7, 2026 22:55
tony added 4 commits November 7, 2026 16:55
why: A nested tool that returns an InputRequiredResult, the 2026-07-28
way to ask the user a question, came back from call_read_tools_batch
as a row with success=true and empty content. The question never
reached the user and the agent was told the call succeeded. No
inspect tool asks today; the first one that does would hit it.

what:
- Raise ExpectedToolError in _ensure_tool_result for fastmcp's
  InputRequiredToolResult, so the row fails and names the tool to
  call directly; on_error="continue" keeps the other rows running
- Test the batch refusal, and pin that the tail-preserving response
  limiter returns a question untouched

Fixes #150
why: kill_session's confirmation (elicitation on both protocol eras
plus a restart-proof target identity) lived in session_tools.py, and
the other kill tools need the same flow.

what:
- Move the confirmation into _utils.py as _ask_before_teardown, which
  returns the 2026-07-28 question, raises on a decline or a changed
  target, and lets the kill proceed when the client cannot ask
- Generalize the identity to _tmux_identity(target, id_format):
  #{pid}:#{start_time} plus the object's id format, or nothing for
  the server itself
- kill_session calls the helper; its behaviour and tests are unchanged
why: Only kill_session asked the user before killing. kill_window,
kill_pane and kill_server end processes just as surely: every pane
in a window, the process in a pane, every session on a server.

what:
- Make kill_window, kill_pane and kill_server async and confirm through
  _ask_before_teardown; each question names what would be lost: the
  window's name, the pane's running command, the server's socket and
  session count
- Identify the target as #{pid}:#{start_time} plus #{window_id} or
  #{pane_id}, or bare for the server; display-message -p needs no
  attached client, so kill_server carries one too
- Clients without elicitation kill without asking, as before; the wire
  schema is unchanged, as the tool-surface snapshot confirms
- Test accept, decline and no capability for each tool on both eras,
  and await the tools in the existing direct-call tests
- Note the confirmation on the kill-window, kill-pane and kill-server
  doc pages

Fixes #152
why: The safe-behaviour sample prompt named only kill_session and
kill_server, and now that every kill tool asks through elicitation,
an agent following it makes the user confirm twice in clients that
support elicitation.

what:
- Name all four kill tools in the sample prompt and keep the
  instruction for clients without elicitation
- Add a note that the last sentence can be dropped where the client
  supports elicitation
- Widen the unreleased CHANGES entry from kill_session to every kill
  tool

Fixes #153
@tony
tony merged commit d503159 into main Nov 7, 2026
10 checks passed
@tony
tony deleted the mcp-2026-001 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

2 participants