@arearseth/tmux-mcp 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,7 +7,7 @@ Model Context Protocol server that enables Claude Desktop to interact with and v
7
7
  - List and search tmux sessions
8
8
  - View and navigate tmux windows and panes
9
9
  - Capture and expose terminal content from any pane
10
- - Execute commands in tmux panes and retrieve results (use it at your own risk ⚠️)
10
+ - Execute commands in tmux panes and retrieve results across bash, zsh, fish, and tclsh shells (use it at your own risk ⚠️)
11
11
  - Create new tmux sessions and windows
12
12
  - Split panes horizontally or vertically with customizable sizes
13
13
  - Kill tmux sessions, windows, and panes
@@ -40,7 +40,7 @@ Add this MCP server to your Claude Desktop configuration:
40
40
 
41
41
  ### MCP server options
42
42
 
43
- You can optionally specify the command line shell you are using, if unspecified it defaults to `bash`
43
+ You can optionally specify the default shell the server should assume when wrapping commands. If unspecified it defaults to `bash`.
44
44
 
45
45
  ```json
46
46
  "mcpServers": {
@@ -51,7 +51,7 @@ You can optionally specify the command line shell you are using, if unspecified
51
51
  }
52
52
  ```
53
53
 
54
- The MCP server needs to know the shell only when executing commands, to properly read its exit status.
54
+ The CLI flag only sets the server-wide default. You can still override individual panes (or change the default at runtime) with the `set-shell-type` tool described below. The MCP server needs to know the shell when executing commands so it can wrap and read exit statuses correctly.
55
55
 
56
56
  ## Available Resources
57
57
 
@@ -72,6 +72,6 @@ The MCP server needs to know the shell only when executing commands, to properly
72
72
  - `kill-session` - Kill a tmux session by ID
73
73
  - `kill-window` - Kill a tmux window by ID
74
74
  - `kill-pane` - Kill a tmux pane by ID
75
+ - `set-shell-type` - Configure the shell used for command execution (supports bash, zsh, fish, tclsh). Provide a paneId to override a single pane, or omit to adjust the default.
75
76
  - `execute-command` - Execute a command in a tmux pane
76
77
  - `get-command-result` - Get the result of an executed command
77
-
package/build/index.js CHANGED
@@ -112,16 +112,30 @@ server.tool("list-panes", "List panes in a tmux window", {
112
112
  }
113
113
  });
114
114
  // Capture pane content - Tool
115
- server.tool("capture-pane", "Capture content from a tmux pane with configurable lines count and optional color preservation", {
115
+ server.tool("capture-pane", "Capture content from a tmux pane. Defaults to the last N lines, but you can provide tmux-style start/end offsets (like 0 and -) to walk the full scrollback.", {
116
116
  paneId: z.string().describe("ID of the tmux pane"),
117
- lines: z.string().optional().describe("Number of lines to capture"),
117
+ lines: z.string().optional().describe("Number of trailing lines to capture when start/end offsets are omitted (defaults to 200)"),
118
+ start: z.string().optional().describe("tmux -S offset; use 0 for the oldest line or a negative value to offset from the bottom"),
119
+ end: z.string().optional().describe("tmux -E offset; use - for the newest line or 0 for the active cursor line"),
118
120
  colors: z.boolean().optional().describe("Include color/escape sequences for text and background attributes in output")
119
- }, async ({ paneId, lines, colors }) => {
121
+ }, async ({ paneId, lines, start, end, colors }) => {
120
122
  try {
121
123
  // Parse lines parameter if provided
122
- const linesCount = lines ? parseInt(lines, 10) : undefined;
123
- const includeColors = colors || false;
124
- const content = await tmux.capturePaneContent(paneId, linesCount, includeColors);
124
+ const parsedLines = lines !== undefined ? parseInt(lines, 10) : undefined;
125
+ const includeColors = colors ?? false;
126
+ const options = {
127
+ includeColors
128
+ };
129
+ if (parsedLines !== undefined && !Number.isNaN(parsedLines) && parsedLines > 0) {
130
+ options.lines = parsedLines;
131
+ }
132
+ if (start !== undefined && start !== '') {
133
+ options.start = start;
134
+ }
135
+ if (end !== undefined && end !== '') {
136
+ options.end = end;
137
+ }
138
+ const content = await tmux.capturePaneContent(paneId, options);
125
139
  return {
126
140
  content: [{
127
141
  type: "text",
@@ -287,7 +301,7 @@ server.tool("split-pane", "Split a tmux pane horizontally or vertically", {
287
301
  }
288
302
  });
289
303
  // Configure shell type - Tool
290
- server.tool("set-shell-type", "Configure the shell type used for command execution. Provide paneId to override a specific pane.", {
304
+ server.tool("set-shell-type", "Configure the shell for command execution (bash, zsh, fish, tclsh). Provide paneId to override a specific pane.", {
291
305
  type: shellTypeSchema,
292
306
  paneId: z.string().optional().describe("ID of the tmux pane to override. Omit to change the default shell type.")
293
307
  }, async ({ type, paneId }) => {
@@ -461,7 +475,10 @@ server.resource("Tmux Pane Content", new ResourceTemplate("tmux://pane/{paneId}"
461
475
  // Ensure paneId is a string
462
476
  const paneIdStr = Array.isArray(paneId) ? paneId[0] : paneId;
463
477
  // Default to no colors for resources to maintain clean programmatic access
464
- const content = await tmux.capturePaneContent(paneIdStr, 200, false);
478
+ const content = await tmux.capturePaneContent(paneIdStr, {
479
+ lines: 200,
480
+ includeColors: false
481
+ });
465
482
  return {
466
483
  contents: [{
467
484
  uri: uri.href,
package/build/tmux.js CHANGED
@@ -2,7 +2,7 @@ import { exec as execCallback } from "child_process";
2
2
  import { promisify } from "util";
3
3
  import { v4 as uuidv4 } from 'uuid';
4
4
  const exec = promisify(execCallback);
5
- export const supportedShellTypes = ['bash', 'zsh', 'fish', 'fc_shell'];
5
+ export const supportedShellTypes = ['bash', 'zsh', 'fish', 'tclsh'];
6
6
  const shellConfig = {
7
7
  defaultType: 'bash',
8
8
  paneOverrides: new Map()
@@ -17,12 +17,12 @@ export function setShellConfig(config) {
17
17
  if (config.paneId) {
18
18
  shellConfig.paneOverrides.set(config.paneId, normalized);
19
19
  // Reset cached initialization so the helper can be installed on demand
20
- fcShellInitializedPanes.delete(config.paneId);
20
+ tclshInitializedPanes.delete(config.paneId);
21
21
  return;
22
22
  }
23
23
  shellConfig.defaultType = normalized;
24
- if (normalized !== 'fc_shell') {
25
- fcShellInitializedPanes.clear();
24
+ if (normalized !== 'tclsh') {
25
+ tclshInitializedPanes.clear();
26
26
  }
27
27
  }
28
28
  function resolveShellType(paneId) {
@@ -121,9 +121,16 @@ export async function listPanes(windowId) {
121
121
  /**
122
122
  * Capture content from a specific pane, by default the latest 200 lines.
123
123
  */
124
- export async function capturePaneContent(paneId, lines = 200, includeColors = false) {
125
- const colorFlag = includeColors ? '-e' : '';
126
- return executeTmux(`capture-pane -p ${colorFlag} -t '${paneId}' -S -${lines} -E -`);
124
+ export async function capturePaneContent(paneId, options = {}) {
125
+ const { lines = 200, start, end, includeColors = false } = options;
126
+ const startValue = start !== undefined ? String(start) : `-${lines}`;
127
+ const endValue = end !== undefined ? String(end) : '-';
128
+ const commandParts = ['capture-pane', '-p'];
129
+ if (includeColors) {
130
+ commandParts.push('-e');
131
+ }
132
+ commandParts.push('-t', `'${paneId}'`, '-S', startValue, '-E', endValue);
133
+ return executeTmux(commandParts.join(' '));
127
134
  }
128
135
  /**
129
136
  * Create a new tmux session
@@ -190,8 +197,8 @@ export async function splitPane(targetPaneId, direction = 'vertical', size) {
190
197
  const activeCommands = new Map();
191
198
  const startMarkerText = 'TMUX_MCP_START';
192
199
  const endMarkerPrefix = "TMUX_MCP_DONE_";
193
- // Track fc_shell initialization per pane to keep terminal output minimal
194
- const fcShellInitializedPanes = new Set();
200
+ // Track tclsh initialization per pane to keep terminal output minimal
201
+ const tclshInitializedPanes = new Set();
195
202
  // Execute a command in a tmux pane and track its execution
196
203
  export async function executeCommand(paneId, command, rawMode, noEnter) {
197
204
  // Generate unique ID for this command execution
@@ -202,9 +209,9 @@ export async function executeCommand(paneId, command, rawMode, noEnter) {
202
209
  fullCommand = command;
203
210
  }
204
211
  else {
205
- if (shellType === 'fc_shell') {
206
- await ensureFcShellInitialized(paneId);
207
- fullCommand = buildFcShellCommand(command);
212
+ if (shellType === 'tclsh') {
213
+ await ensureTclshInitialized(paneId);
214
+ fullCommand = buildTclshCommand(command);
208
215
  }
209
216
  else {
210
217
  fullCommand = buildWrappedCommand(command, shellType);
@@ -249,7 +256,7 @@ export async function checkCommandStatus(commandId) {
249
256
  return null;
250
257
  if (command.status !== 'pending')
251
258
  return command;
252
- const content = await capturePaneContent(command.paneId, 1000);
259
+ const content = await capturePaneContent(command.paneId, { lines: 1000 });
253
260
  if (command.rawMode) {
254
261
  command.result = 'Status tracking unavailable for rawMode commands. Use capture-pane to monitor interactive apps instead.';
255
262
  return command;
@@ -307,7 +314,7 @@ function getEndMarkerText(shellType) {
307
314
  if (shellType === 'fish') {
308
315
  return `${endMarkerPrefix}$status`;
309
316
  }
310
- if (shellType === 'fc_shell') {
317
+ if (shellType === 'tclsh') {
311
318
  return `${endMarkerPrefix}$::tmux_mcp_status`;
312
319
  }
313
320
  return `${endMarkerPrefix}$?`;
@@ -316,7 +323,7 @@ function buildWrappedCommand(command, shellType) {
316
323
  const endMarkerText = getEndMarkerText(shellType);
317
324
  return `echo "${startMarkerText}"; ${command}; echo "${endMarkerText}"`;
318
325
  }
319
- function buildFcShellCommand(command) {
326
+ function buildTclshCommand(command) {
320
327
  const escaped = escapeForTcl(command);
321
328
  return `::tmux_mcp::run {${escaped}}`;
322
329
  }
@@ -328,8 +335,8 @@ function escapeForTcl(command) {
328
335
  .replace(/\{/g, '\\{')
329
336
  .replace(/\}/g, '\\}');
330
337
  }
331
- async function ensureFcShellInitialized(paneId) {
332
- if (fcShellInitializedPanes.has(paneId)) {
338
+ async function ensureTclshInitialized(paneId) {
339
+ if (tclshInitializedPanes.has(paneId)) {
333
340
  return;
334
341
  }
335
342
  const definitionCommand = [
@@ -348,5 +355,5 @@ async function ensureFcShellInitialized(paneId) {
348
355
  ].join(' ');
349
356
  const escapedCommand = definitionCommand.replace(/'/g, "'\\''");
350
357
  await executeTmux(`send-keys -t '${paneId}' '${escapedCommand}' Enter`);
351
- fcShellInitializedPanes.add(paneId);
358
+ tclshInitializedPanes.add(paneId);
352
359
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arearseth/tmux-mcp",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "MCP Server for interfacing with tmux sessions",
5
5
  "type": "module",
6
6
  "main": "build/index.js",