Skip to content

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

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.

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