Skip to content

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

@tony

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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions