Skip to content

Commit 8c35b32

Browse files
committed
mcp(feat[resources]): Add tmux:// URI resources for hierarchy browsing
why: MCP resources let agents browse tmux state via URI patterns. what: - tmux://sessions - list all sessions - tmux://sessions/{session_name} - session detail with windows - tmux://sessions/{session_name}/windows - windows in session - tmux://sessions/{session_name}/windows/{window_index} - window with panes - tmux://panes/{pane_id} - pane details - tmux://panes/{pane_id}/content - captured pane text
1 parent 45e23d3 commit 8c35b32

2 files changed

Lines changed: 186 additions & 0 deletions

File tree

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
"""MCP resource 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_resources(mcp: FastMCP) -> None:
12+
"""Register all resource modules with the FastMCP instance."""
13+
from libtmux.mcp.resources import hierarchy
14+
15+
hierarchy.register(mcp)
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
"""MCP resources for tmux object hierarchy."""
2+
3+
from __future__ import annotations
4+
5+
import json
6+
import logging
7+
import typing as t
8+
9+
from libtmux.mcp._utils import (
10+
_get_server,
11+
_serialize_pane,
12+
_serialize_session,
13+
_serialize_window,
14+
)
15+
16+
if t.TYPE_CHECKING:
17+
from fastmcp import FastMCP
18+
19+
logger = logging.getLogger(__name__)
20+
21+
22+
def register(mcp: FastMCP) -> None:
23+
"""Register hierarchy resources with the FastMCP instance."""
24+
25+
@mcp.resource("tmux://sessions")
26+
def get_sessions() -> str:
27+
"""List all tmux sessions.
28+
29+
Returns
30+
-------
31+
str
32+
JSON array of session objects.
33+
"""
34+
try:
35+
server = _get_server()
36+
sessions = [_serialize_session(s) for s in server.sessions]
37+
return json.dumps(sessions, indent=2)
38+
except Exception as e:
39+
return json.dumps({"error": str(e)})
40+
41+
@mcp.resource("tmux://sessions/{session_name}")
42+
def get_session(session_name: str) -> str:
43+
"""Get details of a specific tmux session.
44+
45+
Parameters
46+
----------
47+
session_name : str
48+
The session name.
49+
50+
Returns
51+
-------
52+
str
53+
JSON object with session info and its windows.
54+
"""
55+
try:
56+
server = _get_server()
57+
session = server.sessions.get(session_name=session_name, default=None)
58+
if session is None:
59+
return json.dumps({"error": f"Session not found: {session_name}"})
60+
61+
result = _serialize_session(session)
62+
result["windows"] = [_serialize_window(w) for w in session.windows]
63+
return json.dumps(result, indent=2)
64+
except Exception as e:
65+
return json.dumps({"error": str(e)})
66+
67+
@mcp.resource("tmux://sessions/{session_name}/windows")
68+
def get_session_windows(session_name: str) -> str:
69+
"""List all windows in a tmux session.
70+
71+
Parameters
72+
----------
73+
session_name : str
74+
The session name.
75+
76+
Returns
77+
-------
78+
str
79+
JSON array of window objects.
80+
"""
81+
try:
82+
server = _get_server()
83+
session = server.sessions.get(session_name=session_name, default=None)
84+
if session is None:
85+
return json.dumps({"error": f"Session not found: {session_name}"})
86+
87+
windows = [_serialize_window(w) for w in session.windows]
88+
return json.dumps(windows, indent=2)
89+
except Exception as e:
90+
return json.dumps({"error": str(e)})
91+
92+
@mcp.resource("tmux://sessions/{session_name}/windows/{window_index}")
93+
def get_window(session_name: str, window_index: str) -> str:
94+
"""Get details of a specific window in a session.
95+
96+
Parameters
97+
----------
98+
session_name : str
99+
The session name.
100+
window_index : str
101+
The window index within the session.
102+
103+
Returns
104+
-------
105+
str
106+
JSON object with window info and its panes.
107+
"""
108+
try:
109+
server = _get_server()
110+
session = server.sessions.get(session_name=session_name, default=None)
111+
if session is None:
112+
return json.dumps({"error": f"Session not found: {session_name}"})
113+
114+
window = session.windows.get(window_index=window_index, default=None)
115+
if window is None:
116+
return json.dumps({"error": f"Window not found: index {window_index}"})
117+
118+
result = _serialize_window(window)
119+
result["panes"] = [_serialize_pane(p) for p in window.panes]
120+
return json.dumps(result, indent=2)
121+
except Exception as e:
122+
return json.dumps({"error": str(e)})
123+
124+
@mcp.resource("tmux://panes/{pane_id}")
125+
def get_pane(pane_id: str) -> str:
126+
"""Get details of a specific pane.
127+
128+
Parameters
129+
----------
130+
pane_id : str
131+
The pane ID (e.g. '%%1').
132+
133+
Returns
134+
-------
135+
str
136+
JSON object of pane details.
137+
"""
138+
try:
139+
server = _get_server()
140+
pane = server.panes.get(pane_id=pane_id, default=None)
141+
if pane is None:
142+
return json.dumps({"error": f"Pane not found: {pane_id}"})
143+
144+
return json.dumps(_serialize_pane(pane), indent=2)
145+
except Exception as e:
146+
return json.dumps({"error": str(e)})
147+
148+
@mcp.resource("tmux://panes/{pane_id}/content")
149+
def get_pane_content(pane_id: str) -> str:
150+
"""Capture and return the content of a pane.
151+
152+
Parameters
153+
----------
154+
pane_id : str
155+
The pane ID (e.g. '%%1').
156+
157+
Returns
158+
-------
159+
str
160+
Plain text captured pane content.
161+
"""
162+
try:
163+
server = _get_server()
164+
pane = server.panes.get(pane_id=pane_id, default=None)
165+
if pane is None:
166+
return f"Error: Pane not found: {pane_id}"
167+
168+
lines = pane.capture_pane()
169+
return "\n".join(lines)
170+
except Exception as e:
171+
return f"Error: {e}"

0 commit comments

Comments
 (0)