Skip to main content
Glama

Tmux MCP Server

MIT License
1
  • Apple
  • Linux
tmux_messenger_uv.py15.1 kB
#!/usr/bin/env python3 import tkinter as tk from tkinter import ttk, messagebox import pyuv import signal import os import time import functools class TmuxMessengerUV: def __init__(self, root): self.root = root self.root.title("Tmux Session Messenger (UV)") self.root.geometry("600x500") self.loop = pyuv.Loop.default_loop() self.timer_handle = None self.auto_cycle_handle = None self.first_launch = True self.setup_ui() self.refresh_sessions() # Setup UV loop integration with Tkinter self.setup_uv_integration() def setup_uv_integration(self): """Integrate UV event loop with Tkinter main loop""" def run_uv_loop(): # Run UV loop in non-blocking mode self.loop.run(pyuv.UV_RUN_NOWAIT) # Schedule next UV loop iteration self.root.after(10, run_uv_loop) # Start UV loop integration self.root.after(10, run_uv_loop) def setup_ui(self): # Main frame main_frame = ttk.Frame(self.root, padding="10") main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)) # Sessions list ttk.Label(main_frame, text="Tmux Sessions:").grid(row=0, column=0, sticky=tk.W, pady=(0, 5)) # Sessions listbox with scrollbar sessions_frame = ttk.Frame(main_frame) sessions_frame.grid(row=1, column=0, columnspan=2, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 10)) self.sessions_listbox = tk.Listbox(sessions_frame, height=8) self.sessions_listbox.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)) sessions_scrollbar = ttk.Scrollbar(sessions_frame, orient="vertical", command=self.sessions_listbox.yview) sessions_scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S)) self.sessions_listbox.configure(yscrollcommand=sessions_scrollbar.set) # Refresh button ttk.Button(main_frame, text="Refresh Sessions", command=self.refresh_sessions).grid(row=2, column=0, sticky=tk.W, pady=(0, 10)) # Message input ttk.Label(main_frame, text="Message to send:").grid(row=3, column=0, sticky=tk.W, pady=(0, 5)) self.message_entry = tk.Text(main_frame, height=4, width=50) self.message_entry.grid(row=4, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(0, 10)) # Timer settings timer_frame = ttk.Frame(main_frame) timer_frame.grid(row=5, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(0, 10)) ttk.Label(timer_frame, text="Send every:").grid(row=0, column=0, sticky=tk.W) self.timer_entry = ttk.Entry(timer_frame, width=10) self.timer_entry.grid(row=0, column=1, padx=(5, 5)) self.timer_entry.insert(0, "5") ttk.Label(timer_frame, text="seconds").grid(row=0, column=2, sticky=tk.W) # Auto cycle controls auto_frame = ttk.LabelFrame(main_frame, text="Auto Exit/Continue Cycle", padding="5") auto_frame.grid(row=6, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(0, 10)) self.start_auto_btn = ttk.Button(auto_frame, text="Start Auto Cycle", command=self.start_auto_cycle) self.start_auto_btn.grid(row=0, column=0, padx=(0, 5)) self.stop_auto_btn = ttk.Button(auto_frame, text="Stop Auto Cycle", command=self.stop_auto_cycle, state="disabled") self.stop_auto_btn.grid(row=0, column=1) # Control buttons buttons_frame = ttk.Frame(main_frame) buttons_frame.grid(row=7, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(0, 10)) self.send_once_btn = ttk.Button(buttons_frame, text="Send Once", command=self.send_once) self.send_once_btn.grid(row=0, column=0, padx=(0, 5)) self.start_timer_btn = ttk.Button(buttons_frame, text="Start Timer", command=self.start_timer) self.start_timer_btn.grid(row=0, column=1, padx=(0, 5)) self.stop_timer_btn = ttk.Button(buttons_frame, text="Stop Timer", command=self.stop_timer, state="disabled") self.stop_timer_btn.grid(row=0, column=2) # Status label self.status_label = ttk.Label(main_frame, text="Ready") self.status_label.grid(row=8, column=0, columnspan=2, sticky=tk.W, pady=(10, 0)) # Configure grid weights main_frame.columnconfigure(0, weight=1) main_frame.rowconfigure(1, weight=1) sessions_frame.columnconfigure(0, weight=1) sessions_frame.rowconfigure(0, weight=1) self.root.columnconfigure(0, weight=1) self.root.rowconfigure(0, weight=1) def get_tmux_sessions(self): """Get tmux sessions using UV process spawning""" sessions = [] output_data = [] def on_exit(proc, exit_status, term_signal): if exit_status == 0: output = ''.join(output_data) for line in output.strip().split('\n'): if line: session_name = line.split(':')[0] sessions.append((session_name, line)) def on_read(handle, data, error): if data: output_data.append(data.decode('utf-8')) # Create pipes for stdout stdout_pipe = pyuv.Pipe(self.loop) # Prepare stdio containers stdio = [] stdio.append(pyuv.StdIO(flags=pyuv.UV_IGNORE)) # stdin stdio.append(pyuv.StdIO(stream=stdout_pipe, flags=pyuv.UV_CREATE_PIPE | pyuv.UV_WRITABLE_PIPE)) # stdout stdio.append(pyuv.StdIO(flags=pyuv.UV_IGNORE)) # stderr # Spawn process proc = pyuv.Process(self.loop) proc.spawn( file="tmux", args=["tmux", "list-sessions"], stdio=stdio, exit_callback=on_exit ) # Start reading from stdout stdout_pipe.start_read(on_read) # Run the loop to complete the operation self.loop.run(pyuv.UV_RUN_DEFAULT) return sessions def refresh_sessions(self): self.sessions_listbox.delete(0, tk.END) sessions = self.get_tmux_sessions() if not sessions: self.sessions_listbox.insert(tk.END, "No tmux sessions found") self.status_label.config(text="No sessions available") else: for session_name, session_info in sessions: self.sessions_listbox.insert(tk.END, session_info) self.status_label.config(text=f"Found {len(sessions)} sessions") def get_selected_session(self): selection = self.sessions_listbox.curselection() if not selection: messagebox.showwarning("Warning", "Please select a tmux session") return None session_line = self.sessions_listbox.get(selection[0]) if session_line == "No tmux sessions found": messagebox.showwarning("Warning", "No valid session selected") return None return session_line.split(':')[0] def send_message_to_session_uv(self, session_name, message, callback=None): """Send message to tmux session using UV process spawning""" def on_exit(proc, exit_status, term_signal): if callback: callback(exit_status == 0) # First send the message proc1 = pyuv.Process(self.loop) proc1.spawn( file="tmux", args=["tmux", "send-keys", "-t", session_name, message], exit_callback=lambda p, s, t: None ) # Then send Enter key proc2 = pyuv.Process(self.loop) proc2.spawn( file="tmux", args=["tmux", "send-keys", "-t", session_name, "C-m"], exit_callback=on_exit ) def send_once(self): session_name = self.get_selected_session() if not session_name: return message = self.message_entry.get("1.0", tk.END).strip() if not message: messagebox.showwarning("Warning", "Please enter a message to send") return def on_sent(success): if success: self.status_label.config(text=f"Message sent to '{session_name}'") else: messagebox.showerror("Error", f"Failed to send message to session '{session_name}'") self.send_message_to_session_uv(session_name, message, on_sent) def start_timer(self): session_name = self.get_selected_session() if not session_name: return message = self.message_entry.get("1.0", tk.END).strip() if not message: messagebox.showwarning("Warning", "Please enter a message to send") return try: interval = float(self.timer_entry.get()) if interval <= 0: raise ValueError("Timer must be positive") except ValueError: messagebox.showerror("Error", "Please enter a valid positive number for timer interval") return self.start_timer_btn.config(state="disabled") self.stop_timer_btn.config(state="normal") self.send_once_btn.config(state="disabled") def timer_callback(timer_handle): self.send_message_to_session_uv(session_name, message) self.status_label.config( text=f"Timer active - Last sent to '{session_name}' at {time.strftime('%H:%M:%S')}" ) # Create and start UV timer self.timer_handle = pyuv.Timer(self.loop) self.timer_handle.start(timer_callback, interval, interval) self.status_label.config(text=f"Timer started - sending every {interval}s to '{session_name}'") def stop_timer(self): if self.timer_handle: self.timer_handle.stop() self.timer_handle = None self.start_timer_btn.config(state="normal") self.stop_timer_btn.config(state="disabled") self.send_once_btn.config(state="normal") self.status_label.config(text="Timer stopped") def send_exit_continue_sequence_uv(self, session_name, callback=None): """Send exit/continue sequence using UV""" sequence_step = [0] def next_step(timer_handle=None): step = sequence_step[0] if step < 5: # Send Ctrl+C proc = pyuv.Process(self.loop) proc.spawn( file="tmux", args=["tmux", "send-keys", "-t", session_name, "C-c"], exit_callback=lambda p, s, t: None ) sequence_step[0] += 1 # Schedule next Ctrl+C after 200ms timer = pyuv.Timer(self.loop) timer.start(next_step, 0.2, 0) elif step == 5: # Wait 1 second then send mullvad reconnect sequence_step[0] += 1 timer = pyuv.Timer(self.loop) timer.start(next_step, 1.0, 0) elif step == 6: # Send mullvad reconnect proc1 = pyuv.Process(self.loop) proc1.spawn( file="tmux", args=["tmux", "send-keys", "-t", session_name, "mullvad reconnect"], exit_callback=lambda p, s, t: None ) proc2 = pyuv.Process(self.loop) proc2.spawn( file="tmux", args=["tmux", "send-keys", "-t", session_name, "Enter"], exit_callback=lambda p, s, t: None ) sequence_step[0] += 1 # Wait 3 seconds for mullvad to reconnect timer = pyuv.Timer(self.loop) timer.start(next_step, 3.0, 0) elif step == 7: # Send claudex -c proc1 = pyuv.Process(self.loop) proc1.spawn( file="tmux", args=["tmux", "send-keys", "-t", session_name, "claudex -c"], exit_callback=lambda p, s, t: None ) proc2 = pyuv.Process(self.loop) proc2.spawn( file="tmux", args=["tmux", "send-keys", "-t", session_name, "Enter"], exit_callback=lambda p, s, t: None ) if callback: callback(True) # Start the sequence next_step() def start_auto_cycle(self): session_name = self.get_selected_session() if not session_name: return self.start_auto_btn.config(state="disabled") self.stop_auto_btn.config(state="normal") self.start_timer_btn.config(state="disabled") self.send_once_btn.config(state="disabled") def auto_cycle_callback(timer_handle=None): self.send_exit_continue_sequence_uv(session_name) self.status_label.config( text=f"Auto cycle active - Last sequence sent to '{session_name}' at {time.strftime('%H:%M:%S')}" ) # If first launch, start immediately if self.first_launch: self.first_launch = False auto_cycle_callback() # Create and start UV timer for 4-minute intervals self.auto_cycle_handle = pyuv.Timer(self.loop) self.auto_cycle_handle.start(auto_cycle_callback, 240.0, 240.0) # 240 seconds = 4 minutes self.status_label.config(text=f"Auto cycle started - Ctrl+C spam + mullvad reconnect + claudex -c every 4 minutes to '{session_name}'") def stop_auto_cycle(self): if self.auto_cycle_handle: self.auto_cycle_handle.stop() self.auto_cycle_handle = None self.start_auto_btn.config(state="normal") self.stop_auto_btn.config(state="disabled") self.start_timer_btn.config(state="normal") self.send_once_btn.config(state="normal") self.status_label.config(text="Auto cycle stopped") def main(): root = tk.Tk() app = TmuxMessengerUV(root) def on_closing(): if app.timer_handle: app.stop_timer() if app.auto_cycle_handle: app.stop_auto_cycle() # Stop UV loop app.loop.stop() root.destroy() root.protocol("WM_DELETE_WINDOW", on_closing) try: root.mainloop() except KeyboardInterrupt: on_closing() if __name__ == "__main__": main()

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/rinadelph/tmux-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server