Skip to content

Compose / bulk operations: run multiple actions in one MCP tool call (start with send_keys) #49

Closed

Description

@tony

Problem

Every MCP tool call costs at least one round-trip between the agent and the server. When an agent needs to send several keystroke sequences to one pane, or coordinate send_keys across multiple panes, it currently needs one MCP call per operation. That burns agent turns, MCP transport round-trips, and libtmux/tmux subprocess work for sequences that can be represented as a batch.

A composed surface should let an agent request "run these N send-key operations in this declared order, with this error policy" as one MCP call.

tmux and FastMCP support the shape

tmux command queues already provide useful semantics for a future native optimization:

  • Chained commands share a group via cmd_get_group() assignment.
  • A command error removes subsequent commands in that group via cmdq_remove_group() and the error path at cmd-queue.c#L780.
  • send-keys accepts a single target pane through its -t target field, so cross-pane batching requires either multiple libtmux calls or a tmux command chain; see cmd-send-keys.c.
  • tmux command grammar uses semicolon-separated commands; see cmd-parse.y.

FastMCP/Pydantic can expose the MCP input safely:

Recommendation: start with send_keys_batch

Add a homogeneous batch tool first. Keep send_keys unchanged for the common single-operation case.

class SendKeysOp(BaseModel):
    keys: str
    pane_id: str | None = None
    session_name: str | None = None
    session_id: str | None = None
    window_id: str | None = None
    enter: bool = True
    literal: bool = False
    suppress_history: bool = False


class SendKeysOpResult(BaseModel):
    pane_id: str | None
    success: bool
    elapsed_seconds: float
    error: str | None = None


async def send_keys_batch(
    operations: Annotated[list[SendKeysOp], Field(min_length=1, max_length=50)],
    on_error: Literal["stop", "continue"] = "stop",
    socket_name: str | None = None,
    ctx: Context | None = None,
) -> list[SendKeysOpResult]: ...

Default on_error="stop" should short-circuit after the first failed operation. on_error="continue" should record the failure for that item and proceed with later operations in declared order.

Deferred avenues

Acceptance criteria

  • New send_keys_batch tool with Pydantic-validated operations and explicit on_error policy.
  • Per-operation result list preserves declared order and includes success/error/timing metadata.
  • Tests cover input bounds, declared ordering, on_error="stop", on_error="continue", cross-pane operations, and validation error reporting.
  • Docs explain when to use send_keys_batch versus single send_keys, and call out that command completion should still use wait_for_channel when the agent authors the command.
  • CHANGES describes the user-facing capability as one batch send-keys surface, not as internal tmux mechanics.

Activity

  1. tony commented on May 24, 2026

    @tony
    MemberAuthor

    Triage pass (2026-05-24): keep, but action after #55/#54/#53 and after the first #60 design pass.

    Current-code/source check: the proposed homogeneous send_keys_batch shape is compatible with the existing single send_keys surface and with FastMCP/Pydantic list inputs. Pin future source links to tmux command grouping/error behavior at cmd-queue.c#L470-L516 and cmd-queue.c#L772-L781, the target-pane field in send-keys at cmd-send-keys.c#L32-L44, tmux semicolon command parsing at cmd-parse.y#L407-L418, and FastMCP v3.3.1 complex-list examples / schema behavior (complex inputs, _strip_discriminator).

    Overlap/value: this is valuable for round-trip reduction, but it should stay intentionally narrower than #60/#61. Do not expand this into heterogeneous send_keys -> wait -> capture composition; that would duplicate the observation-first work. Recommended action when picked up: implement only homogeneous send_keys_batch, preserve declared order, and keep per-item error reporting clearer than native tmux command-chain optimization.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions