Skip to content

Commit 45e23d3

Browse files
committed
mcp(feat[tools]): Add 25 MCP tools for tmux control
why: Provide comprehensive tool coverage for AI agents to manage tmux. what: - server_tools: list_sessions, create_session, kill_server, get_server_info - session_tools: list_windows, create_window, rename_session, kill_session - window_tools: list_panes, split_window, rename_window, kill_window, select_layout, resize_window - pane_tools: send_keys, capture_pane, resize_pane, kill_pane, set_pane_title, get_pane_info, clear_pane - option_tools: show_option, set_option - env_tools: show_environment, set_environment
1 parent cab2190 commit 45e23d3

7 files changed

Lines changed: 1198 additions & 0 deletions

File tree

src/libtmux/mcp/tools/__init__.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
"""MCP tool registration for libtmux."""
2+
3+
from __future__ import annotations
4+
5+
import typing as t
6+
7+
if t.TYPE_CHECKING:
8+
from fastmcp import FastMCP
9+
10+
11+
def register_tools(mcp: FastMCP) -> None:
12+
"""Register all tool modules with the FastMCP instance."""
13+
from libtmux.mcp.tools import (
14+
env_tools,
15+
option_tools,
16+
pane_tools,
17+
server_tools,
18+
session_tools,
19+
window_tools,
20+
)
21+
22+
server_tools.register(mcp)
23+
session_tools.register(mcp)
24+
window_tools.register(mcp)
25+
pane_tools.register(mcp)
26+
option_tools.register(mcp)
27+
env_tools.register(mcp)

src/libtmux/mcp/tools/env_tools.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
"""MCP tools for tmux environment variable management."""
2+
3+
from __future__ import annotations
4+
5+
import json
6+
import typing as t
7+
8+
from libtmux.mcp._utils import (
9+
_get_server,
10+
_resolve_session,
11+
handle_tool_errors,
12+
)
13+
14+
if t.TYPE_CHECKING:
15+
from fastmcp import FastMCP
16+
17+
18+
@handle_tool_errors
19+
def show_environment(
20+
session_name: str | None = None,
21+
session_id: str | None = None,
22+
socket_name: str | None = None,
23+
) -> str:
24+
"""Show tmux environment variables.
25+
26+
Parameters
27+
----------
28+
session_name : str, optional
29+
Session name to query environment for.
30+
session_id : str, optional
31+
Session ID to query environment for.
32+
socket_name : str, optional
33+
tmux socket name.
34+
35+
Returns
36+
-------
37+
str
38+
JSON dict of environment variables.
39+
"""
40+
server = _get_server(socket_name=socket_name)
41+
42+
if session_name is not None or session_id is not None:
43+
session = _resolve_session(
44+
server,
45+
session_name=session_name,
46+
session_id=session_id,
47+
)
48+
env_dict = session.show_environment()
49+
else:
50+
env_dict = server.show_environment()
51+
52+
return json.dumps(env_dict)
53+
54+
55+
@handle_tool_errors
56+
def set_environment(
57+
name: str,
58+
value: str,
59+
session_name: str | None = None,
60+
session_id: str | None = None,
61+
socket_name: str | None = None,
62+
) -> str:
63+
"""Set a tmux environment variable.
64+
65+
Parameters
66+
----------
67+
name : str
68+
Environment variable name.
69+
value : str
70+
Environment variable value.
71+
session_name : str, optional
72+
Session name to set environment for.
73+
session_id : str, optional
74+
Session ID to set environment for.
75+
socket_name : str, optional
76+
tmux socket name.
77+
78+
Returns
79+
-------
80+
str
81+
JSON confirming the variable was set.
82+
"""
83+
server = _get_server(socket_name=socket_name)
84+
85+
if session_name is not None or session_id is not None:
86+
session = _resolve_session(
87+
server,
88+
session_name=session_name,
89+
session_id=session_id,
90+
)
91+
session.set_environment(name, value)
92+
else:
93+
server.set_environment(name, value)
94+
95+
return json.dumps({"name": name, "value": value, "status": "set"})
96+
97+
98+
def register(mcp: FastMCP) -> None:
99+
"""Register environment tools with the MCP instance."""
100+
mcp.tool(annotations={"readOnlyHint": True})(show_environment)
101+
mcp.tool(annotations={"destructiveHint": False})(set_environment)
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
"""MCP tools for tmux option management."""
2+
3+
from __future__ import annotations
4+
5+
import json
6+
import typing as t
7+
8+
from libtmux.constants import OptionScope
9+
from libtmux.mcp._utils import (
10+
_get_server,
11+
_resolve_pane,
12+
_resolve_session,
13+
_resolve_window,
14+
handle_tool_errors,
15+
)
16+
17+
if t.TYPE_CHECKING:
18+
from fastmcp import FastMCP
19+
20+
from libtmux.options import OptionsMixin
21+
22+
_SCOPE_MAP: dict[str, OptionScope] = {
23+
"server": OptionScope.Server,
24+
"session": OptionScope.Session,
25+
"window": OptionScope.Window,
26+
"pane": OptionScope.Pane,
27+
}
28+
29+
30+
def _resolve_option_target(
31+
socket_name: str | None,
32+
scope: str | None,
33+
target: str | None,
34+
) -> tuple[OptionsMixin, OptionScope | None]:
35+
"""Resolve the target object and scope for option operations."""
36+
server = _get_server(socket_name=socket_name)
37+
opt_scope = _SCOPE_MAP.get(scope) if scope is not None else None
38+
39+
if target is not None and opt_scope is not None:
40+
if opt_scope == OptionScope.Session:
41+
return _resolve_session(server, session_name=target), opt_scope
42+
if opt_scope == OptionScope.Window:
43+
return _resolve_window(server, window_id=target), opt_scope
44+
if opt_scope == OptionScope.Pane:
45+
return _resolve_pane(server, pane_id=target), opt_scope
46+
return server, opt_scope
47+
48+
49+
@handle_tool_errors
50+
def show_option(
51+
option: str,
52+
scope: str | None = None,
53+
target: str | None = None,
54+
global_: bool = False,
55+
socket_name: str | None = None,
56+
) -> str:
57+
"""Show a tmux option value.
58+
59+
Parameters
60+
----------
61+
option : str
62+
The tmux option name to query.
63+
scope : str, optional
64+
Option scope: "server", "session", "window", or "pane".
65+
target : str, optional
66+
Target session, window, or pane identifier.
67+
global_ : bool
68+
Whether to query the global option.
69+
socket_name : str, optional
70+
tmux socket name.
71+
72+
Returns
73+
-------
74+
str
75+
JSON with the option name and its value.
76+
"""
77+
obj, opt_scope = _resolve_option_target(socket_name, scope, target)
78+
value = obj.show_option(option, global_=global_, scope=opt_scope)
79+
return json.dumps({"option": option, "value": value})
80+
81+
82+
@handle_tool_errors
83+
def set_option(
84+
option: str,
85+
value: str,
86+
scope: str | None = None,
87+
target: str | None = None,
88+
global_: bool = False,
89+
socket_name: str | None = None,
90+
) -> str:
91+
"""Set a tmux option value.
92+
93+
Parameters
94+
----------
95+
option : str
96+
The tmux option name to set.
97+
value : str
98+
The value to set.
99+
scope : str, optional
100+
Option scope: "server", "session", "window", or "pane".
101+
target : str, optional
102+
Target session, window, or pane identifier.
103+
global_ : bool
104+
Whether to set the global option.
105+
socket_name : str, optional
106+
tmux socket name.
107+
108+
Returns
109+
-------
110+
str
111+
JSON confirming the option was set.
112+
"""
113+
obj, opt_scope = _resolve_option_target(socket_name, scope, target)
114+
obj.set_option(option, value, global_=global_, scope=opt_scope)
115+
return json.dumps({"option": option, "value": value, "status": "set"})
116+
117+
118+
def register(mcp: FastMCP) -> None:
119+
"""Register option tools with the MCP instance."""
120+
mcp.tool(annotations={"readOnlyHint": True})(show_option)
121+
mcp.tool(annotations={"destructiveHint": False})(set_option)

0 commit comments

Comments
 (0)