smart-terminal-mcp 1.2.7 → 1.2.9
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 +31 -11
- package/package.json +1 -1
- package/src/tools.js +3 -3
- package/test/command-runner.test.js +28 -1
package/README.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# smart-terminal-mcp
|
|
2
2
|
|
|
3
|
-
A PTY-based MCP server with strong Windows support
|
|
3
|
+
A PTY-based MCP server with strong Windows support, giving MCP-capable AI clients and their agents persistent, interactive shell access via pseudo-terminals ([node-pty](https://github.com/microsoft/node-pty)).
|
|
4
4
|
|
|
5
5
|
Unlike simple `exec`-based approaches, this keeps PTY-backed shell sessions alive across steps, with bidirectional communication for interactive CLI tools, incremental reads, and session state that carries forward.
|
|
6
6
|
|
|
7
7
|
## Why use this instead of your AI client's built-in terminal?
|
|
8
8
|
|
|
9
|
-
Install this if you want a more consistent terminal workflow across
|
|
9
|
+
Install this if you want a more consistent terminal workflow across AI clients, instead of relying on whatever built-in terminal behavior a single client happens to provide.
|
|
10
10
|
|
|
11
11
|
This MCP is most useful when you want:
|
|
12
12
|
|
|
@@ -17,15 +17,15 @@ This MCP is most useful when you want:
|
|
|
17
17
|
- **More control over large output** -- Truncate, page, diff, retry, wait for patterns, or fetch history instead of dumping everything at once.
|
|
18
18
|
- **More predictable automation** -- Use deterministic completion markers instead of guessing when a command is done.
|
|
19
19
|
|
|
20
|
-
If your AI client already
|
|
20
|
+
If your AI client already provides a stable, stateful, interactive terminal with good output handling, you may not need this MCP for basic command execution. The main reason to add it is to make terminal-driven workflows more explicit, reusable, and portable across clients.
|
|
21
21
|
|
|
22
22
|
## Features
|
|
23
23
|
|
|
24
|
-
Think of this as a **controlled keyboard + terminal for
|
|
24
|
+
Think of this as a **controlled keyboard + terminal for an agent running inside an MCP client**. It opens a persistent PTY-backed shell session so the agent can send commands and keystrokes, read output, and continue working in the same session.
|
|
25
25
|
|
|
26
26
|
### Core terminal features
|
|
27
27
|
|
|
28
|
-
- **Interactive terminal sessions** -- Keeps a persistent PTY-backed shell session open so the
|
|
28
|
+
- **Interactive terminal sessions** -- Keeps a persistent PTY-backed shell session open so the agent can send input, read output, and pick up where it left off.
|
|
29
29
|
- **Deterministic command completion** -- `terminal_exec` uses unique markers so it can tell when a command has finished.
|
|
30
30
|
- **Clean output** -- Pre-command markers help keep returned output readable, even when shells echo commands or expand aliases.
|
|
31
31
|
- **Working directory tracking** -- `terminal_exec` reports the current folder after each command.
|
|
@@ -36,8 +36,8 @@ Think of this as a **controlled keyboard + terminal for AI**. It opens a persist
|
|
|
36
36
|
- **Pattern waiting** -- `terminal_wait` can pause until specific text appears, such as `server listening on port`.
|
|
37
37
|
- **Retry helper** -- `terminal_retry` can re-run flaky commands with bounded backoff and optional output matching.
|
|
38
38
|
- **Best-effort progress notifications** -- Long `terminal_exec` / `terminal_wait` calls can emit `notifications/progress` when the client provides a progress token.
|
|
39
|
-
- **Output truncation** -- `terminal_exec` and `terminal_read` shorten very large output by
|
|
40
|
-
- **Paged read-only output** -- `terminal_run_paged`
|
|
39
|
+
- **Output truncation** -- `terminal_exec` and `terminal_read` shorten very large output by returning the beginning and the end.
|
|
40
|
+
- **Paged read-only output** -- `terminal_run_paged` returns large read-only output one page at a time instead of sending the full result at once.
|
|
41
41
|
- **Output diffing** -- `terminal_diff` compares two command results and returns a unified diff.
|
|
42
42
|
|
|
43
43
|
### Safety and usability
|
|
@@ -49,7 +49,21 @@ Think of this as a **controlled keyboard + terminal for AI**. It opens a persist
|
|
|
49
49
|
- **Session management** -- Supports named sessions, idle cleanup, and up to 10 concurrent sessions.
|
|
50
50
|
- **Shell auto-detection** -- Windows: `pwsh.exe` > `powershell.exe` > `cmd.exe`. Linux/macOS: `$SHELL` > `bash` > `sh`.
|
|
51
51
|
|
|
52
|
-
Progress notifications are not the same as full stdout streaming
|
|
52
|
+
Progress notifications are not the same as full stdout streaming. They currently send periodic status updates for `terminal_exec` and `terminal_wait`, usually based on elapsed time and the latest output line. Whether you see them depends on your MCP client.
|
|
53
|
+
|
|
54
|
+
## Token efficiency
|
|
55
|
+
|
|
56
|
+
This MCP does not magically compress terminal output, but it **can help agents use fewer tokens in terminal-heavy workflows** by returning smaller, more targeted responses and making it easier to revisit output only when needed.
|
|
57
|
+
|
|
58
|
+
The main benefit is **model-context efficiency**, not guaranteed savings in the underlying command's runtime or total bytes produced.
|
|
59
|
+
|
|
60
|
+
- Use **`terminal_run_paged`** for large read-only output when **the agent** wants one page of the returned result at a time.
|
|
61
|
+
- Lower **`maxLines`**, **`pageSize`**, or **`tailLines`** when **the agent** only needs a narrow slice of the output.
|
|
62
|
+
- Use **`summary: true`** or **`parseOnly: true`** with `terminal_run` when **the agent** benefits more from structured results than raw text.
|
|
63
|
+
- Use **`terminal_wait({ returnMode: "match-only" })`** when the agent only needs to know whether a pattern appeared.
|
|
64
|
+
- Use **`terminal_get_history`** when **the agent** needs to revisit earlier output without re-dumping the whole session into the conversation.
|
|
65
|
+
|
|
66
|
+
In practice, this lets agents inspect terminal state more selectively instead of repeatedly dumping large logs back into the conversation.
|
|
53
67
|
|
|
54
68
|
## Installation
|
|
55
69
|
|
|
@@ -147,7 +161,7 @@ Execute a command with deterministic completion detection. Large outputs are tru
|
|
|
147
161
|
|
|
148
162
|
### `terminal_run`
|
|
149
163
|
|
|
150
|
-
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.
|
|
164
|
+
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. Prefer passing the target executable directly as `cmd` instead of wrapping it in `powershell -Command` or `cmd /c`, especially when Windows paths contain spaces.
|
|
151
165
|
|
|
152
166
|
| Param | Type | Default | Description |
|
|
153
167
|
|-------|------|---------|-------------|
|
|
@@ -164,7 +178,7 @@ Run a one-shot non-interactive command using `cmd + args` with `shell=false`. Sa
|
|
|
164
178
|
|
|
165
179
|
### `terminal_run_paged`
|
|
166
180
|
|
|
167
|
-
Run a read-only one-shot command using `cmd + args` with `shell=false` and return a single page of stdout lines. This
|
|
181
|
+
Run a read-only one-shot command using `cmd + args` with `shell=false` and return a single page of stdout lines from the captured output. This pages the returned result instead of using 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`.
|
|
168
182
|
|
|
169
183
|
| Param | Type | Default | Description |
|
|
170
184
|
|-------|------|---------|-------------|
|
|
@@ -214,7 +228,7 @@ Retrieve past terminal output without consuming it. Non-destructive — returns
|
|
|
214
228
|
|
|
215
229
|
**Returns**: `lines` or `text`, plus `totalLines`, `returnedFrom`, `returnedTo`
|
|
216
230
|
|
|
217
|
-
These defaults favor agent usability while still allowing callers to lower `maxLines` or `pageSize` explicitly when they want tighter responses.
|
|
231
|
+
These defaults favor agent usability while still allowing tool callers to lower `maxLines` or `pageSize` explicitly when they want tighter responses.
|
|
218
232
|
|
|
219
233
|
### `terminal_send_key`
|
|
220
234
|
|
|
@@ -332,6 +346,12 @@ terminal_run({ cmd: "git", args: ["status", "--porcelain=v1", "--branch"] })
|
|
|
332
346
|
-> { ok: true, stdout: { raw: "...", parsed: { branch: {...}, staged: [], modified: [], untracked: [] } } }
|
|
333
347
|
```
|
|
334
348
|
|
|
349
|
+
For Windows tools installed under `Program Files`, prefer this shape over `powershell -Command`:
|
|
350
|
+
|
|
351
|
+
```
|
|
352
|
+
terminal_run({ cmd: "C:\\Program Files\\Vendor\\Tool.exe", args: ["/flag:value", "/other"] })
|
|
353
|
+
```
|
|
354
|
+
|
|
335
355
|
### Page through large read-only output
|
|
336
356
|
|
|
337
357
|
```
|
package/package.json
CHANGED
package/src/tools.js
CHANGED
|
@@ -107,10 +107,10 @@ export function registerTools(server, manager) {
|
|
|
107
107
|
// --- terminal_run ---
|
|
108
108
|
server.tool(
|
|
109
109
|
'terminal_run',
|
|
110
|
-
'Run a
|
|
110
|
+
'Run a binary directly; avoid shell quoting.',
|
|
111
111
|
{
|
|
112
|
-
cmd: z.string().describe('Executable'),
|
|
113
|
-
args: z.array(z.string()).default([]).describe('
|
|
112
|
+
cmd: z.string().describe('Executable path or name'),
|
|
113
|
+
args: z.array(z.string()).default([]).describe('Args passed verbatim'),
|
|
114
114
|
cwd: z.string().optional().describe('Working directory'),
|
|
115
115
|
timeout: z.number().int().min(1000).max(600000).default(DEFAULT_TIMEOUT_MS).describe('Timeout in ms'),
|
|
116
116
|
maxOutputBytes: z.number().int().min(1024).max(1048576).default(DEFAULT_MAX_OUTPUT_BYTES).describe('Max output bytes'),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import test from 'node:test';
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
|
-
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { chmod, mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
5
|
import { join } from 'node:path';
|
|
6
6
|
import { getStructuredParserHint, runCommand } from '../src/command-runner.js';
|
|
@@ -168,6 +168,33 @@ test('runCommand executes explicit .cmd files on Windows', async (t) => {
|
|
|
168
168
|
}
|
|
169
169
|
});
|
|
170
170
|
|
|
171
|
+
test('runCommand handles explicit command paths with spaces', async () => {
|
|
172
|
+
const tempDir = await mkdtemp(join(tmpdir(), 'smart terminal mcp-'));
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
const isWindows = process.platform === 'win32';
|
|
176
|
+
const scriptPath = join(tempDir, isWindows ? 'space wrapper.cmd' : 'space-wrapper.sh');
|
|
177
|
+
const scriptBody = isWindows
|
|
178
|
+
? '@echo off\r\necho [%~1]\r\n'
|
|
179
|
+
: '#!/bin/sh\necho "[$1]"\n';
|
|
180
|
+
|
|
181
|
+
await writeFile(scriptPath, scriptBody);
|
|
182
|
+
if (!isWindows) await chmod(scriptPath, 0o755);
|
|
183
|
+
|
|
184
|
+
const result = await runCommand({
|
|
185
|
+
cmd: scriptPath,
|
|
186
|
+
args: ['hello world'],
|
|
187
|
+
parse: false,
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
assert.equal(result.ok, true);
|
|
191
|
+
assert.equal(result.exitCode, 0);
|
|
192
|
+
assert.match(result.stdout.raw, /\[hello world\]/);
|
|
193
|
+
} finally {
|
|
194
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
|
|
171
198
|
test('runCommand adds a helpful ENOENT hint on Windows for PATH commands', async (t) => {
|
|
172
199
|
if (process.platform !== 'win32') {
|
|
173
200
|
t.skip('Windows-only behavior');
|