smart-terminal-mcp 1.2.0 → 1.2.2
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 +6 -14
- package/package.json +1 -1
- package/src/pty-session.js +18 -12
- package/test/pty-session.test.js +52 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
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)).
|
|
4
4
|
|
|
5
|
-
Unlike simple `exec`-based approaches, this provides full PTY sessions with bidirectional communication, enabling interactive CLI tools,
|
|
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.
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
@@ -19,19 +19,11 @@ Unlike simple `exec`-based approaches, this provides full PTY sessions with bidi
|
|
|
19
19
|
- **CWD tracking** -- Every `terminal_exec` response includes the current working directory
|
|
20
20
|
- **Output truncation** -- Large outputs are automatically truncated 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
|
|
23
|
-
- **
|
|
22
|
+
- **Anti-blocking** -- Disables pagers (`GIT_PAGER=cat`, `PAGER=cat`), suppresses PowerShell progress output, and sets UTF-8 for `cmd.exe` on Windows
|
|
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
24
|
- **Shell auto-detection** -- Windows: `pwsh.exe` > `powershell.exe` > `cmd.exe`. Linux/macOS: `$SHELL` or `bash`
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
- **Node.js** >= 18
|
|
29
|
-
|
|
30
|
-
`node-pty` ships prebuilt binaries for most platforms. If prebuilds are unavailable for your OS/architecture, a C/C++ toolchain is needed as fallback:
|
|
31
|
-
|
|
32
|
-
- **Windows**: [Visual Studio Build Tools](https://visualstudio.microsoft.com/visual-cpp-build-tools/) (select "Desktop development with C++")
|
|
33
|
-
- **macOS**: Xcode Command Line Tools (`xcode-select --install`)
|
|
34
|
-
- **Linux**: `build-essential` and Python 3 (`sudo apt install build-essential python3`)
|
|
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.
|
|
35
27
|
|
|
36
28
|
## Installation
|
|
37
29
|
|
|
@@ -116,7 +108,7 @@ Start a new interactive terminal session.
|
|
|
116
108
|
|
|
117
109
|
### `terminal_exec`
|
|
118
110
|
|
|
119
|
-
Execute a command with deterministic completion detection.
|
|
111
|
+
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.
|
|
120
112
|
|
|
121
113
|
| Param | Type | Default | Description |
|
|
122
114
|
|-------|------|---------|-------------|
|
|
@@ -211,7 +203,7 @@ Send a named special key.
|
|
|
211
203
|
|
|
212
204
|
### `terminal_wait`
|
|
213
205
|
|
|
214
|
-
Wait for a specific pattern in the output stream.
|
|
206
|
+
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.
|
|
215
207
|
|
|
216
208
|
| Param | Type | Default | Description |
|
|
217
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__';
|