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 +13 -23
- package/package.json +1 -1
- package/src/pty-session.js +18 -12
- package/test/pty-session.test.js +62 -1
package/README.md
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
# smart-terminal-mcp
|
|
2
2
|
|
|
3
|
-
A
|
|
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
|
|
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** --
|
|
10
|
-
- **Robust command echo removal** -- Pre-command marker
|
|
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
|
-
- **
|
|
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** --
|
|
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
|
-
- **
|
|
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`
|
|
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
package/src/pty-session.js
CHANGED
|
@@ -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',
|
package/test/pty-session.test.js
CHANGED
|
@@ -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;
|