mcp-tmux-server
Version:
MCP server for TMUX terminal multiplexer operations
154 lines (131 loc) • 4.9 kB
text/typescript
import { exec } from 'child_process';
import { promisify } from 'util';
const execAsync = promisify(exec);
export interface TmuxSession {
name: string;
id: string;
windows: number;
attached: boolean;
created: string;
}
export interface TmuxWindow {
id: string;
name: string;
active: boolean;
session: string;
panes: number;
}
export interface TmuxPane {
id: string;
active: boolean;
width: number;
height: number;
command: string;
}
export class TmuxController {
async listSessions(): Promise<TmuxSession[]> {
try {
const { stdout } = await execAsync('tmux list-sessions -F "#{session_name}:#{session_id}:#{session_windows}:#{session_attached}:#{session_created}"');
return stdout.trim().split('\n').filter(line => line).map(line => {
const [name, id, windows, attached, created] = line.split(':');
return {
name,
id,
windows: parseInt(windows),
attached: attached === '1',
created: new Date(parseInt(created) * 1000).toISOString()
};
});
} catch (error: any) {
if (error.stderr?.includes('no server running')) {
return [];
}
throw error;
}
}
async createSession(name: string, command?: string): Promise<string> {
const cmd = command
? `tmux new-session -d -s "${name}" "${command}"`
: `tmux new-session -d -s "${name}"`;
await execAsync(cmd);
return `Session "${name}" created successfully`;
}
async killSession(name: string): Promise<string> {
await execAsync(`tmux kill-session -t "${name}"`);
return `Session "${name}" killed successfully`;
}
async listWindows(session?: string): Promise<TmuxWindow[]> {
const target = session ? `-t "${session}"` : '';
const { stdout } = await execAsync(`tmux list-windows ${target} -F "#{window_id}:#{window_name}:#{window_active}:#{session_name}:#{window_panes}"`);
return stdout.trim().split('\n').filter(line => line).map(line => {
const [id, name, active, sessionName, panes] = line.split(':');
return {
id,
name,
active: active === '1',
session: sessionName,
panes: parseInt(panes)
};
});
}
async createWindow(session: string, name?: string, command?: string): Promise<string> {
let cmd = `tmux new-window -t "${session}"`;
if (name) cmd += ` -n "${name}"`;
if (command) cmd += ` "${command}"`;
await execAsync(cmd);
return `Window created in session "${session}"${name ? ` with name "${name}"` : ''}`;
}
async killWindow(target: string): Promise<string> {
await execAsync(`tmux kill-window -t "${target}"`);
return `Window "${target}" killed successfully`;
}
async sendKeys(target: string, keys: string, enter: boolean = true): Promise<string> {
const cmd = enter
? `tmux send-keys -t "${target}" "${keys}" C-m`
: `tmux send-keys -t "${target}" "${keys}"`;
await execAsync(cmd);
return `Sent keys "${keys}" to "${target}"`;
}
async splitWindow(target: string, vertical: boolean = false, command?: string): Promise<string> {
let cmd = `tmux split-window -t "${target}"`;
if (vertical) cmd += ' -v';
if (command) cmd += ` "${command}"`;
await execAsync(cmd);
return `Split window in "${target}" ${vertical ? 'vertically' : 'horizontally'}`;
}
async listPanes(target: string): Promise<TmuxPane[]> {
const { stdout } = await execAsync(`tmux list-panes -t "${target}" -F "#{pane_id}:#{pane_active}:#{pane_width}:#{pane_height}:#{pane_current_command}"`);
return stdout.trim().split('\n').filter(line => line).map(line => {
const [id, active, width, height, command] = line.split(':');
return {
id,
active: active === '1',
width: parseInt(width),
height: parseInt(height),
command
};
});
}
async getSessionInfo(name: string): Promise<any> {
const { stdout } = await execAsync(`tmux display-message -t "${name}" -p "#{session_name}:#{session_id}:#{session_windows}:#{session_attached}:#{session_created}:#{client_width}:#{client_height}"`);
const [sessionName, id, windows, attached, created, width, height] = stdout.trim().split(':');
return {
name: sessionName,
id,
windows: parseInt(windows),
attached: attached === '1',
created: new Date(parseInt(created) * 1000).toISOString(),
dimensions: {
width: parseInt(width),
height: parseInt(height)
}
};
}
async capturePane(target: string, startLine?: number, endLine?: number): Promise<string> {
let cmd = `tmux capture-pane -t "${target}" -p`;
if (startLine !== undefined) cmd += ` -S ${startLine}`;
if (endLine !== undefined) cmd += ` -E ${endLine}`;
const { stdout } = await execAsync(cmd);
return stdout;
}
}