smart-terminal-mcp 1.2.1 → 1.2.3

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
@@ -1,15 +1,15 @@
1
1
  # smart-terminal-mcp
2
2
 
3
- A Windows-native MCP server that gives AI agents (Claude, Cursor, etc.) real interactive terminal access via pseudo-terminals ([node-pty](https://github.com/microsoft/node-pty)).
3
+ A PTY-based MCP server with strong Windows support that gives AI agents (Claude, Cursor, etc.) interactive terminal access via pseudo-terminals ([node-pty](https://github.com/microsoft/node-pty)).
4
4
 
5
- Unlike simple `exec`-based approaches, this provides full PTY sessions with bidirectional communication, enabling interactive CLI tools, incremental terminal reads, and proper terminal emulation.
5
+ Unlike simple `exec`-based approaches, this provides full PTY sessions with bidirectional communication, enabling interactive CLI tools, incremental terminal reads, and PTY-backed terminal behavior.
6
6
 
7
7
  ## Features
8
8
 
9
- - **Marker-based completion detection** -- 100% reliable command completion via unique markers injected into the shell
10
- - **Robust command echo removal** -- Pre-command marker ensures clean output, handles shell aliases and expansions correctly
9
+ - **Marker-based completion detection** -- Deterministic command completion via unique markers injected into the shell
10
+ - **Robust command echo removal** -- Pre-command marker helps keep output clean even with shell aliases and expansions
11
11
  - **Interactive mode** -- `terminal_write` + `terminal_read` for REPLs, prompts, and interactive programs
12
- - **Safe one-shot commands** -- `terminal_run` executes real binaries with `cmd + args` and `shell=false`
12
+ - **Safer one-shot commands** -- `terminal_run` executes real binaries with `cmd + args` and `shell=false`
13
13
  - **Structured parsers** -- Supported read-only commands can return both `stdout.raw` and `stdout.parsed`
14
14
  - **Paged read-only output** -- `terminal_run_paged` returns a single page of stdout for large command output
15
15
  - **Special key support** -- Send Ctrl+C, Tab, arrow keys, etc. without knowing escape codes
@@ -17,24 +17,14 @@ Unlike simple `exec`-based approaches, this provides full PTY sessions with bidi
17
17
  - **Retry helper** -- Retry flaky terminal commands with bounded backoff and optional output matching
18
18
  - **Output diffing** -- Run two commands in one session and compare their outputs with a unified diff
19
19
  - **CWD tracking** -- Every `terminal_exec` response includes the current working directory
20
- - **Output truncation** -- Large outputs are automatically truncated to head + tail
20
+ - **Output truncation** -- `terminal_exec` and `terminal_read` truncate large outputs to head + tail
21
21
  - **Session management** -- Named sessions, TTL auto-cleanup, max 10 concurrent sessions
22
- - **Anti-blocking** -- Disables pagers (`GIT_PAGER=cat`), progress bars, and sets UTF-8 on Windows
22
+ - **Blocking mitigations** -- Disables pagers (`GIT_PAGER=cat`, `PAGER=cat`), suppresses PowerShell progress output, and sets UTF-8 for `cmd.exe` on Windows
23
23
  - **Best-effort progress notifications** -- Emits MCP `notifications/progress` for long-running `terminal_exec` / `terminal_wait` calls when the client provides a progress token and surfaces those notifications
24
- - **Shell auto-detection** -- Windows: `pwsh.exe` > `powershell.exe` > `cmd.exe`. Linux/macOS: `$SHELL` or `bash`
24
+ - **Shell auto-detection** -- Windows: `pwsh.exe` > `powershell.exe` > `cmd.exe`. Linux/macOS: `$SHELL` > `bash` > `sh`
25
25
 
26
26
  Progress notifications are not the same as full stdout streaming: they currently send periodic status updates for `terminal_exec` and `terminal_wait`, typically based on elapsed time and the latest output line. Whether you actually see them depends on your MCP client.
27
27
 
28
- ## Requirements
29
-
30
- - **Node.js** >= 18
31
-
32
- `node-pty` ships prebuilt binaries for most platforms. If prebuilds are unavailable for your OS/architecture, a C/C++ toolchain is needed as fallback:
33
-
34
- - **Windows**: [Visual Studio Build Tools](https://visualstudio.microsoft.com/visual-cpp-build-tools/) (select "Desktop development with C++")
35
- - **macOS**: Xcode Command Line Tools (`xcode-select --install`)
36
- - **Linux**: `build-essential` and Python 3 (`sudo apt install build-essential python3`)
37
-
38
28
  ## Installation
39
29
 
40
30
  Recommended: run the stable release directly via `npx`:
@@ -118,7 +108,7 @@ Start a new interactive terminal session.
118
108
 
119
109
  ### `terminal_exec`
120
110
 
121
- Execute a command with deterministic completion detection. If the MCP client sends a `progressToken`, long-running calls may also emit best-effort `notifications/progress` updates.
111
+ Execute a command with deterministic completion detection. Large outputs are truncated to head + tail based on `maxLines`. If the MCP client sends a `progressToken`, long-running calls may also emit best-effort `notifications/progress` updates.
122
112
 
123
113
  | Param | Type | Default | Description |
124
114
  |-------|------|---------|-------------|
@@ -131,7 +121,7 @@ Execute a command with deterministic completion detection. If the MCP client sen
131
121
 
132
122
  ### `terminal_run`
133
123
 
134
- Run a one-shot non-interactive command using `cmd + args` with `shell=false`. Safer than `terminal_exec` for predictable automation. Shell built-ins such as `dir` or `cd` are not supported. On Windows, `terminal_run` resolves `PATH`/`PATHEXT` and launches `.cmd` / `.bat` wrappers via `cmd.exe` when needed.
124
+ Run a one-shot non-interactive command using `cmd + args` with `shell=false`. Safer than `terminal_exec` for predictable automation. Output is capped by `maxOutputBytes` rather than head + tail truncation. Shell built-ins such as `dir` or `cd` are not supported. On Windows, `terminal_run` resolves `PATH`/`PATHEXT` and launches `.cmd` / `.bat` wrappers via `cmd.exe` when needed.
135
125
 
136
126
  | Param | Type | Default | Description |
137
127
  |-------|------|---------|-------------|
@@ -148,7 +138,7 @@ Run a one-shot non-interactive command using `cmd + args` with `shell=false`. Sa
148
138
 
149
139
  ### `terminal_run_paged`
150
140
 
151
- Run a read-only one-shot command using `cmd + args` with `shell=false` and return a single page of stdout lines. Paged mode does not parse partial output, but it can return a concise summary for supported read-only commands when `summary: true`.
141
+ Run a read-only one-shot command using `cmd + args` with `shell=false` and return a single page of stdout lines. This uses paging rather than head + tail truncation. Paged mode does not parse partial output, but it can return a concise summary for supported read-only commands when `summary: true`.
152
142
 
153
143
  | Param | Type | Default | Description |
154
144
  |-------|------|---------|-------------|
@@ -174,7 +164,7 @@ Write raw data to a terminal (for interactive programs). Follow with `terminal_r
174
164
 
175
165
  ### `terminal_read`
176
166
 
177
- Read buffered output with idle detection.
167
+ Read buffered output with idle detection. Large outputs are truncated to head + tail based on `maxLines`.
178
168
 
179
169
  | Param | Type | Default | Description |
180
170
  |-------|------|---------|-------------|
@@ -213,7 +203,7 @@ Send a named special key.
213
203
 
214
204
  ### `terminal_wait`
215
205
 
216
- Wait for a specific pattern in the output stream. If the MCP client sends a `progressToken`, long-running waits may also emit best-effort `notifications/progress` updates.
206
+ Wait for a specific pattern in the output stream. By default, responses return only the last `tailLines`; use `returnMode: "full"` for the full matched output or `"match-only"` to suppress output entirely. If the MCP client sends a `progressToken`, long-running waits may also emit best-effort `notifications/progress` updates.
217
207
 
218
208
  | Param | Type | Default | Description |
219
209
  |-------|------|---------|-------------|
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smart-terminal-mcp",
3
- "version": "1.2.1",
3
+ "version": "1.2.3",
4
4
  "description": "MCP PTY server providing AI agents with real interactive terminal access",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -16,6 +16,23 @@ export const DEFAULT_HISTORY_FORMAT = 'lines';
16
16
  const DEFAULT_WAIT_RETURN_MODE = 'tail';
17
17
  const DEFAULT_WAIT_TAIL_LINES = 50;
18
18
 
19
+ export function buildSessionEnv(customEnv = {}, platformName = platform()) {
20
+ const env = {
21
+ ...process.env,
22
+ ...customEnv,
23
+ GIT_PAGER: 'cat',
24
+ PAGER: 'cat',
25
+ LESS: '-FRX',
26
+ TERM: 'xterm-256color',
27
+ };
28
+
29
+ if (platformName !== 'win32') {
30
+ env.DEBIAN_FRONTEND = 'noninteractive';
31
+ }
32
+
33
+ return env;
34
+ }
35
+
19
36
  function escapeRegExp(value) {
20
37
  return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
21
38
  }
@@ -100,18 +117,7 @@ export class PtySession {
100
117
  /** @type {((data: string) => void)[]} */
101
118
  this._dataListeners = [];
102
119
 
103
- const env = {
104
- ...process.env,
105
- ...customEnv,
106
- GIT_PAGER: 'cat',
107
- PAGER: 'cat',
108
- LESS: '-FRX',
109
- TERM: 'xterm-256color',
110
- };
111
-
112
- if (platform() !== 'win32') {
113
- env.DEBIAN_FRONTEND = 'noninteractive';
114
- }
120
+ const env = buildSessionEnv(customEnv);
115
121
 
116
122
  this.process = pty.spawn(shell, shellArgs, {
117
123
  name: 'xterm-256color',
@@ -1,6 +1,6 @@
1
1
  import test from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
- import { PtySession } from '../src/pty-session.js';
3
+ import { buildSessionEnv, PtySession } from '../src/pty-session.js';
4
4
 
5
5
  function createSession() {
6
6
  return Object.create(PtySession.prototype);
@@ -14,6 +14,23 @@ function createWaitSession(buffer = '') {
14
14
  return session;
15
15
  }
16
16
 
17
+ test('buildSessionEnv applies anti-blocking environment defaults', () => {
18
+ const env = buildSessionEnv({ CUSTOM_ENV: 'yes', GIT_PAGER: 'less' }, 'linux');
19
+
20
+ assert.equal(env.CUSTOM_ENV, 'yes');
21
+ assert.equal(env.GIT_PAGER, 'cat');
22
+ assert.equal(env.PAGER, 'cat');
23
+ assert.equal(env.LESS, '-FRX');
24
+ assert.equal(env.TERM, 'xterm-256color');
25
+ assert.equal(env.DEBIAN_FRONTEND, 'noninteractive');
26
+ });
27
+
28
+ test('buildSessionEnv skips noninteractive override on Windows', () => {
29
+ const env = buildSessionEnv({}, 'win32');
30
+
31
+ assert.equal(env.DEBIAN_FRONTEND, undefined);
32
+ });
33
+
17
34
  test('PowerShell wrapper uses safe marker interpolation', () => {
18
35
  const session = createSession();
19
36
  session.shellType = 'powershell';
@@ -24,6 +41,40 @@ test('PowerShell wrapper uses safe marker interpolation', () => {
24
41
  assert.match(command, /__CWD_\$\(\(Get-Location\)\.Path\)__/);
25
42
  });
26
43
 
44
+ test('_initShell suppresses PowerShell progress output', async () => {
45
+ const session = createSession();
46
+ const writes = [];
47
+ let resetCalls = 0;
48
+ session.shellType = 'powershell';
49
+ session.process = { write: (value) => writes.push(value) };
50
+ session._readUntilIdle = async () => '';
51
+ session._resetBuffer = () => {
52
+ resetCalls++;
53
+ };
54
+
55
+ await session._initShell();
56
+
57
+ assert.deepEqual(writes, ["$ProgressPreference = 'SilentlyContinue'\r"]);
58
+ assert.equal(resetCalls, 1);
59
+ });
60
+
61
+ test('_initShell sets cmd sessions to UTF-8', async () => {
62
+ const session = createSession();
63
+ const writes = [];
64
+ let resetCalls = 0;
65
+ session.shellType = 'cmd';
66
+ session.process = { write: (value) => writes.push(value) };
67
+ session._readUntilIdle = async () => '';
68
+ session._resetBuffer = () => {
69
+ resetCalls++;
70
+ };
71
+
72
+ await session._initShell();
73
+
74
+ assert.deepEqual(writes, ['chcp 65001 > nul\r']);
75
+ assert.equal(resetCalls, 1);
76
+ });
77
+
27
78
  test('_parseOutput ignores echoed wrapper text and keeps real output', () => {
28
79
  const session = createSession();
29
80
  const preMarker = '__MCP_PRE_abc__';
@@ -48,6 +99,16 @@ test('_parseOutput ignores echoed wrapper text and keeps real output', () => {
48
99
  });
49
100
  });
50
101
 
102
+ test('_truncateOutput keeps the head and tail when output exceeds maxLines', () => {
103
+ const session = createSession();
104
+ const output = ['line 1', 'line 2', 'line 3', 'line 4', 'line 5', 'line 6'].join('\n');
105
+
106
+ assert.equal(
107
+ session._truncateOutput(output, 4),
108
+ ['line 1', 'line 2', '', '... 2 lines omitted ...', '', 'line 5', 'line 6'].join('\n')
109
+ );
110
+ });
111
+
51
112
  test('read returns unread buffered output once', async () => {
52
113
  const session = createSession();
53
114
  session.alive = true;