mcp-interactive-terminal 1.0.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/LICENSE +21 -0
- package/README.md +202 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +173 -0
- package/dist/index.js.map +1 -0
- package/dist/sandbox.d.ts +33 -0
- package/dist/sandbox.d.ts.map +1 -0
- package/dist/sandbox.js +99 -0
- package/dist/sandbox.js.map +1 -0
- package/dist/session-manager.d.ts +31 -0
- package/dist/session-manager.d.ts.map +1 -0
- package/dist/session-manager.js +158 -0
- package/dist/session-manager.js.map +1 -0
- package/dist/terminal.d.ts +24 -0
- package/dist/terminal.d.ts.map +1 -0
- package/dist/terminal.js +342 -0
- package/dist/terminal.js.map +1 -0
- package/dist/tools/close-session.d.ts +16 -0
- package/dist/tools/close-session.d.ts.map +1 -0
- package/dist/tools/close-session.js +13 -0
- package/dist/tools/close-session.js.map +1 -0
- package/dist/tools/confirm-dangerous-command.d.ts +19 -0
- package/dist/tools/confirm-dangerous-command.d.ts.map +1 -0
- package/dist/tools/confirm-dangerous-command.js +48 -0
- package/dist/tools/confirm-dangerous-command.js.map +1 -0
- package/dist/tools/create-session.d.ts +31 -0
- package/dist/tools/create-session.d.ts.map +1 -0
- package/dist/tools/create-session.js +39 -0
- package/dist/tools/create-session.js.map +1 -0
- package/dist/tools/list-sessions.d.ts +4 -0
- package/dist/tools/list-sessions.d.ts.map +1 -0
- package/dist/tools/list-sessions.js +4 -0
- package/dist/tools/list-sessions.js.map +1 -0
- package/dist/tools/read-output.d.ts +16 -0
- package/dist/tools/read-output.d.ts.map +1 -0
- package/dist/tools/read-output.js +21 -0
- package/dist/tools/read-output.js.map +1 -0
- package/dist/tools/send-command.d.ts +22 -0
- package/dist/tools/send-command.d.ts.map +1 -0
- package/dist/tools/send-command.js +101 -0
- package/dist/tools/send-command.js.map +1 -0
- package/dist/tools/send-control.d.ts +16 -0
- package/dist/tools/send-control.d.ts.map +1 -0
- package/dist/tools/send-control.js +92 -0
- package/dist/tools/send-control.js.map +1 -0
- package/dist/types.d.ts +117 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +55 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/audit-logger.d.ts +23 -0
- package/dist/utils/audit-logger.d.ts.map +1 -0
- package/dist/utils/audit-logger.js +37 -0
- package/dist/utils/audit-logger.js.map +1 -0
- package/dist/utils/danger-detector.d.ts +14 -0
- package/dist/utils/danger-detector.d.ts.map +1 -0
- package/dist/utils/danger-detector.js +59 -0
- package/dist/utils/danger-detector.js.map +1 -0
- package/dist/utils/output-detector.d.ts +19 -0
- package/dist/utils/output-detector.d.ts.map +1 -0
- package/dist/utils/output-detector.js +85 -0
- package/dist/utils/output-detector.js.map +1 -0
- package/dist/utils/sanitizer.d.ts +31 -0
- package/dist/utils/sanitizer.d.ts.map +1 -0
- package/dist/utils/sanitizer.js +82 -0
- package/dist/utils/sanitizer.js.map +1 -0
- package/dist/utils/secret-redactor.d.ts +10 -0
- package/dist/utils/secret-redactor.d.ts.map +1 -0
- package/dist/utils/secret-redactor.js +36 -0
- package/dist/utils/secret-redactor.js.map +1 -0
- package/package.json +53 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 mcp-interactive-terminal contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# mcp-interactive-terminal
|
|
2
|
+
|
|
3
|
+
MCP server for interactive shell sessions. Run REPLs, SSH, database clients, and any interactive CLI through AI agents like Claude Code, Cursor, and others.
|
|
4
|
+
|
|
5
|
+
## The Problem
|
|
6
|
+
|
|
7
|
+
AI coding agents can't handle interactive commands — no PTY, no stdin streaming. You can't run `rails console`, `python`, `psql`, `ssh`, or any REPL through them.
|
|
8
|
+
|
|
9
|
+
## The Solution
|
|
10
|
+
|
|
11
|
+
This MCP server spawns real pseudo-terminals (PTY) and uses xterm-headless (the same terminal emulator as VS Code) to render clean text output. The AI sees exactly what a human would see — no raw ANSI escape codes.
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
AI Agent (Claude Code, Cursor, etc.)
|
|
15
|
+
↕ MCP (JSON-RPC over stdio)
|
|
16
|
+
MCP Terminal Server
|
|
17
|
+
↕ node-pty (pseudo-terminal)
|
|
18
|
+
Interactive Process (rails console, python, psql, ssh, bash...)
|
|
19
|
+
↕ xterm-headless (terminal emulation)
|
|
20
|
+
Clean text output
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
### Claude Code
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
claude mcp add terminal -- npx -y mcp-interactive-terminal
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Manual Configuration
|
|
32
|
+
|
|
33
|
+
Add to `~/.claude.json` or `.mcp.json`:
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
{
|
|
37
|
+
"mcpServers": {
|
|
38
|
+
"terminal": {
|
|
39
|
+
"command": "npx",
|
|
40
|
+
"args": ["-y", "mcp-interactive-terminal"]
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Tools
|
|
47
|
+
|
|
48
|
+
### `create_session` — Spawn an interactive process
|
|
49
|
+
|
|
50
|
+
```json
|
|
51
|
+
{ "command": "python3", "name": "my-python", "cwd": "/project" }
|
|
52
|
+
→ { "session_id": "a1b2c3d4", "name": "my-python", "pid": 12345 }
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Parameters:** `command` (required), `args?`, `name?`, `cwd?`, `env?`, `cols?` (default 120), `rows?` (default 40)
|
|
56
|
+
|
|
57
|
+
### `send_command` — Send input and get output
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
{ "session_id": "a1b2c3d4", "input": "1 + 1" }
|
|
61
|
+
→ { "output": "2", "is_complete": true, "is_alive": true }
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**Parameters:** `session_id`, `input`, `timeout_ms?` (default 5000), `max_output_chars?`
|
|
65
|
+
|
|
66
|
+
Dangerous commands (rm -rf, DROP TABLE, curl|bash, etc.) are blocked and require `confirm_dangerous_command` first.
|
|
67
|
+
|
|
68
|
+
### `read_output` — Read terminal screen (read-only)
|
|
69
|
+
|
|
70
|
+
```json
|
|
71
|
+
{ "session_id": "a1b2c3d4" }
|
|
72
|
+
→ { "output": ">>> ", "is_alive": true }
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**Parameters:** `session_id`, `full_screen?` (default false)
|
|
76
|
+
|
|
77
|
+
### `list_sessions` — List active sessions (read-only)
|
|
78
|
+
|
|
79
|
+
```json
|
|
80
|
+
→ [{ "session_id": "a1b2c3d4", "name": "my-python", "command": "python3", "pid": 12345, "is_alive": true, "created_at": "..." }]
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### `close_session` — Kill a session
|
|
84
|
+
|
|
85
|
+
```json
|
|
86
|
+
{ "session_id": "a1b2c3d4" }
|
|
87
|
+
→ { "success": true }
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**Parameters:** `session_id`, `signal?` (default SIGTERM)
|
|
91
|
+
|
|
92
|
+
### `send_control` — Send control characters
|
|
93
|
+
|
|
94
|
+
```json
|
|
95
|
+
{ "session_id": "a1b2c3d4", "control": "ctrl+c" }
|
|
96
|
+
→ { "output": "^C\n>>>" }
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**Supported keys:** ctrl+c, ctrl+d, ctrl+z, ctrl+l, ctrl+r, tab, escape, up, down, left, right, enter, backspace, delete, home, end, and more.
|
|
100
|
+
|
|
101
|
+
### `confirm_dangerous_command` — Two-step safety confirmation
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
{ "session_id": "a1b2c3d4", "input": "rm -rf /tmp/old", "justification": "Cleaning up stale temp files from failed build" }
|
|
105
|
+
→ { "output": "...", "is_complete": true, "is_alive": true }
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Required when `send_command` detects a dangerous pattern. The AI must provide a justification.
|
|
109
|
+
|
|
110
|
+
## Security
|
|
111
|
+
|
|
112
|
+
Seven-layer defense-in-depth model:
|
|
113
|
+
|
|
114
|
+
| Layer | What It Does | Default |
|
|
115
|
+
|-------|-------------|---------|
|
|
116
|
+
| MCP Tool Annotations | `readOnlyHint`/`destructiveHint` on each tool | Always on |
|
|
117
|
+
| Confirmation flow | Dangerous patterns require `confirm_dangerous_command` | Always on |
|
|
118
|
+
| Input pattern detection | Detect rm -rf, DROP TABLE, curl\|bash, etc. | Always on |
|
|
119
|
+
| Command blocklist/allowlist | Block/allow specific commands | Configurable |
|
|
120
|
+
| OS-level sandbox | Kernel-level process sandboxing | Off (opt-in) |
|
|
121
|
+
| Secret redaction | Redact AWS keys, tokens, private keys in output | Off (opt-in) |
|
|
122
|
+
| Resource limits | Max sessions, output cap, idle timeout | Always on |
|
|
123
|
+
|
|
124
|
+
**Recommended permission configuration** — only auto-approve read-only tools:
|
|
125
|
+
|
|
126
|
+
```json
|
|
127
|
+
{
|
|
128
|
+
"permissions": {
|
|
129
|
+
"allow": [
|
|
130
|
+
"mcp__terminal__list_sessions",
|
|
131
|
+
"mcp__terminal__read_output"
|
|
132
|
+
]
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Configuration
|
|
138
|
+
|
|
139
|
+
All configuration via environment variables:
|
|
140
|
+
|
|
141
|
+
| Variable | Default | Description |
|
|
142
|
+
|----------|---------|-------------|
|
|
143
|
+
| `MCP_TERMINAL_MAX_SESSIONS` | `10` | Max concurrent sessions |
|
|
144
|
+
| `MCP_TERMINAL_MAX_OUTPUT` | `20000` | Max output chars per read |
|
|
145
|
+
| `MCP_TERMINAL_DEFAULT_TIMEOUT` | `5000` | Default wait timeout (ms) |
|
|
146
|
+
| `MCP_TERMINAL_BLOCKED_COMMANDS` | (none) | Comma-separated blocklist |
|
|
147
|
+
| `MCP_TERMINAL_ALLOWED_COMMANDS` | (none) | Comma-separated allowlist |
|
|
148
|
+
| `MCP_TERMINAL_REDACT_SECRETS` | `false` | Redact detected secrets |
|
|
149
|
+
| `MCP_TERMINAL_LOG_INPUTS` | `false` | Log all inputs to stderr |
|
|
150
|
+
| `MCP_TERMINAL_IDLE_TIMEOUT` | `0` | Auto-close idle sessions (ms, 0=disabled) |
|
|
151
|
+
| `MCP_TERMINAL_DANGER_DETECTION` | `true` | Enable dangerous command detection |
|
|
152
|
+
|
|
153
|
+
Example with env vars:
|
|
154
|
+
|
|
155
|
+
```json
|
|
156
|
+
{
|
|
157
|
+
"mcpServers": {
|
|
158
|
+
"terminal": {
|
|
159
|
+
"command": "npx",
|
|
160
|
+
"args": ["-y", "mcp-interactive-terminal"],
|
|
161
|
+
"env": {
|
|
162
|
+
"MCP_TERMINAL_ALLOWED_COMMANDS": "bash,python3,node,psql",
|
|
163
|
+
"MCP_TERMINAL_REDACT_SECRETS": "true",
|
|
164
|
+
"MCP_TERMINAL_IDLE_TIMEOUT": "300000"
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## How It Works
|
|
172
|
+
|
|
173
|
+
### Clean Output via xterm-headless
|
|
174
|
+
|
|
175
|
+
Instead of dumping raw ANSI escape codes, PTY output is fed into xterm-headless (the same terminal emulator used by VS Code, but headless). The buffer is read as plain text — properly wrapped, cursor-positioned, clean.
|
|
176
|
+
|
|
177
|
+
### Smart "Command Done" Detection
|
|
178
|
+
|
|
179
|
+
Layered strategy (most to least reliable):
|
|
180
|
+
|
|
181
|
+
1. **Process exit** — If the process died, command is done
|
|
182
|
+
2. **Prompt detection** — Auto-detects the session's prompt at startup (bash `$`, python `>>>`, psql `#`, etc.), watches for it to reappear
|
|
183
|
+
3. **Output settling** — No new output for 300ms
|
|
184
|
+
4. **Timeout** — Always returns after `timeout_ms` with `is_complete: false`
|
|
185
|
+
|
|
186
|
+
## Development
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
npm install
|
|
190
|
+
npm run build
|
|
191
|
+
npm test
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Test with MCP Inspector:
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
npx @modelcontextprotocol/inspector dist/index.js
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## License
|
|
201
|
+
|
|
202
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* MCP Interactive Terminal Server
|
|
4
|
+
*
|
|
5
|
+
* Provides AI agents with interactive terminal sessions via the
|
|
6
|
+
* Model Context Protocol. Supports REPLs, SSH, databases, and
|
|
7
|
+
* any interactive CLI.
|
|
8
|
+
*/
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA;;;;;;GAMG"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* MCP Interactive Terminal Server
|
|
4
|
+
*
|
|
5
|
+
* Provides AI agents with interactive terminal sessions via the
|
|
6
|
+
* Model Context Protocol. Supports REPLs, SSH, databases, and
|
|
7
|
+
* any interactive CLI.
|
|
8
|
+
*/
|
|
9
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
10
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
11
|
+
import { loadConfig } from "./types.js";
|
|
12
|
+
import { SessionManager } from "./session-manager.js";
|
|
13
|
+
import { createSessionSchema, handleCreateSession } from "./tools/create-session.js";
|
|
14
|
+
import { sendCommandSchema, handleSendCommand } from "./tools/send-command.js";
|
|
15
|
+
import { readOutputSchema, handleReadOutput } from "./tools/read-output.js";
|
|
16
|
+
import { handleListSessions } from "./tools/list-sessions.js";
|
|
17
|
+
import { closeSessionSchema, handleCloseSession } from "./tools/close-session.js";
|
|
18
|
+
import { sendControlSchema, handleSendControl } from "./tools/send-control.js";
|
|
19
|
+
import { confirmDangerousCommandSchema, handleConfirmDangerousCommand, } from "./tools/confirm-dangerous-command.js";
|
|
20
|
+
import { initSandbox, resetSandbox } from "./sandbox.js";
|
|
21
|
+
import { configureAudit, audit } from "./utils/audit-logger.js";
|
|
22
|
+
const config = loadConfig();
|
|
23
|
+
const sessionManager = new SessionManager(config);
|
|
24
|
+
const server = new McpServer({
|
|
25
|
+
name: "mcp-interactive-terminal",
|
|
26
|
+
version: "1.0.0",
|
|
27
|
+
});
|
|
28
|
+
// --- Tool Registration ---
|
|
29
|
+
server.tool("create_session", "Spawn an interactive terminal session (REPL, shell, database client, SSH, etc.). Returns a session_id for subsequent commands.", createSessionSchema.shape, { title: "Create Session", readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }, async ({ command, args, name, cwd, env, cols, rows }) => {
|
|
30
|
+
try {
|
|
31
|
+
const result = await handleCreateSession({ command, args, name, cwd, env, cols, rows }, sessionManager, config);
|
|
32
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
return {
|
|
36
|
+
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
37
|
+
isError: true,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
server.tool("send_command", "Send a command/input to an interactive session and wait for output. Appends newline automatically. Returns clean text output (no ANSI codes). If a dangerous command is detected, you must use confirm_dangerous_command first.", sendCommandSchema.shape, { title: "Send Command", readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true }, async ({ session_id, input, timeout_ms, max_output_chars }) => {
|
|
42
|
+
try {
|
|
43
|
+
const result = await handleSendCommand({ session_id, input, timeout_ms, max_output_chars }, sessionManager, config);
|
|
44
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
return {
|
|
48
|
+
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
49
|
+
isError: true,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
server.tool("read_output", "Read the current terminal screen without sending any input. Safe read-only operation.", readOutputSchema.shape, { title: "Read Output", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }, async ({ session_id, full_screen }) => {
|
|
54
|
+
try {
|
|
55
|
+
const result = await handleReadOutput({ session_id, full_screen }, sessionManager, config);
|
|
56
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
return {
|
|
60
|
+
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
61
|
+
isError: true,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
server.tool("list_sessions", "List all active interactive terminal sessions. Safe read-only operation.", {}, { title: "List Sessions", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }, async () => {
|
|
66
|
+
try {
|
|
67
|
+
const result = await handleListSessions(sessionManager);
|
|
68
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
return {
|
|
72
|
+
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
73
|
+
isError: true,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
server.tool("close_session", "Close/kill an interactive terminal session.", closeSessionSchema.shape, { title: "Close Session", readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }, async ({ session_id, signal }) => {
|
|
78
|
+
try {
|
|
79
|
+
const result = await handleCloseSession({ session_id, signal }, sessionManager);
|
|
80
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
return {
|
|
84
|
+
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
85
|
+
isError: true,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
server.tool("send_control", "Send a control character or special key to a session (e.g., ctrl+c to interrupt, ctrl+d to send EOF, arrow keys, tab for completion).", sendControlSchema.shape, { title: "Send Control", readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false }, async ({ session_id, control }) => {
|
|
90
|
+
try {
|
|
91
|
+
const result = await handleSendControl({ session_id, control }, sessionManager, config);
|
|
92
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
return {
|
|
96
|
+
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
97
|
+
isError: true,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
server.tool("confirm_dangerous_command", "Execute a command that was flagged as dangerous by send_command. Requires a justification explaining WHY the command is necessary. This is a separate confirmation step for safety.", confirmDangerousCommandSchema.shape, { title: "Confirm Dangerous Command", readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true }, async ({ session_id, input, justification }) => {
|
|
102
|
+
try {
|
|
103
|
+
const result = await handleConfirmDangerousCommand({ session_id, input, justification }, sessionManager, config);
|
|
104
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
return {
|
|
108
|
+
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
109
|
+
isError: true,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
// --- Lifecycle ---
|
|
114
|
+
async function main() {
|
|
115
|
+
const transport = new StdioServerTransport();
|
|
116
|
+
// Initialize audit logger
|
|
117
|
+
if (config.auditLog) {
|
|
118
|
+
configureAudit(config.auditLog);
|
|
119
|
+
}
|
|
120
|
+
// Initialize sandbox if enabled
|
|
121
|
+
if (config.sandbox) {
|
|
122
|
+
await initSandbox(config);
|
|
123
|
+
}
|
|
124
|
+
// Cleanup on shutdown
|
|
125
|
+
process.on("SIGINT", async () => {
|
|
126
|
+
audit("server_stop");
|
|
127
|
+
sessionManager.closeAll();
|
|
128
|
+
await resetSandbox();
|
|
129
|
+
process.exit(0);
|
|
130
|
+
});
|
|
131
|
+
process.on("SIGTERM", async () => {
|
|
132
|
+
audit("server_stop");
|
|
133
|
+
sessionManager.closeAll();
|
|
134
|
+
await resetSandbox();
|
|
135
|
+
process.exit(0);
|
|
136
|
+
});
|
|
137
|
+
console.error("[mcp-terminal] Starting MCP Interactive Terminal Server v1.0.0");
|
|
138
|
+
console.error(`[mcp-terminal] Config: maxSessions=${config.maxSessions}, maxOutput=${config.maxOutput}, defaultTimeout=${config.defaultTimeout}ms`);
|
|
139
|
+
if (config.dangerDetection) {
|
|
140
|
+
console.error("[mcp-terminal] Dangerous command detection: ENABLED");
|
|
141
|
+
}
|
|
142
|
+
if (config.redactSecrets) {
|
|
143
|
+
console.error("[mcp-terminal] Secret redaction: ENABLED");
|
|
144
|
+
}
|
|
145
|
+
if (config.blockedCommands.length > 0) {
|
|
146
|
+
console.error(`[mcp-terminal] Blocked commands: ${config.blockedCommands.join(", ")}`);
|
|
147
|
+
}
|
|
148
|
+
if (config.allowedCommands.length > 0) {
|
|
149
|
+
console.error(`[mcp-terminal] Allowed commands: ${config.allowedCommands.join(", ")}`);
|
|
150
|
+
}
|
|
151
|
+
if (config.allowedPaths.length > 0) {
|
|
152
|
+
console.error(`[mcp-terminal] Allowed paths: ${config.allowedPaths.join(", ")}`);
|
|
153
|
+
}
|
|
154
|
+
if (config.auditLog) {
|
|
155
|
+
console.error(`[mcp-terminal] Audit log: ${config.auditLog}`);
|
|
156
|
+
}
|
|
157
|
+
audit("server_start", undefined, {
|
|
158
|
+
maxSessions: config.maxSessions,
|
|
159
|
+
dangerDetection: config.dangerDetection,
|
|
160
|
+
sandbox: config.sandbox,
|
|
161
|
+
redactSecrets: config.redactSecrets,
|
|
162
|
+
allowedCommands: config.allowedCommands,
|
|
163
|
+
blockedCommands: config.blockedCommands,
|
|
164
|
+
allowedPaths: config.allowedPaths,
|
|
165
|
+
});
|
|
166
|
+
await server.connect(transport);
|
|
167
|
+
}
|
|
168
|
+
main().catch((err) => {
|
|
169
|
+
console.error("[mcp-terminal] Fatal error:", err);
|
|
170
|
+
sessionManager.closeAll();
|
|
171
|
+
process.exit(1);
|
|
172
|
+
});
|
|
173
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optional OS-level sandbox integration via @anthropic-ai/sandbox-runtime.
|
|
3
|
+
*
|
|
4
|
+
* When enabled (MCP_TERMINAL_SANDBOX=true), wraps spawned commands with
|
|
5
|
+
* kernel-level filesystem and network restrictions using sandbox-exec (macOS)
|
|
6
|
+
* or bubblewrap (Linux).
|
|
7
|
+
*
|
|
8
|
+
* This is an optional feature — if the package isn't installed or the platform
|
|
9
|
+
* isn't supported, it logs a warning and falls back to unsandboxed execution.
|
|
10
|
+
*/
|
|
11
|
+
import type { ServerConfig } from "./types.js";
|
|
12
|
+
/**
|
|
13
|
+
* Initialize the sandbox if enabled in config.
|
|
14
|
+
* Call this once at startup. Safe to call even if the package isn't installed.
|
|
15
|
+
*/
|
|
16
|
+
export declare function initSandbox(config: ServerConfig): Promise<boolean>;
|
|
17
|
+
/**
|
|
18
|
+
* Wrap a command string with sandbox restrictions.
|
|
19
|
+
* Returns the original command unchanged if sandbox is not available.
|
|
20
|
+
*/
|
|
21
|
+
export declare function wrapCommand(command: string): Promise<{
|
|
22
|
+
command: string;
|
|
23
|
+
useShell: boolean;
|
|
24
|
+
}>;
|
|
25
|
+
/**
|
|
26
|
+
* Check if sandbox is currently active.
|
|
27
|
+
*/
|
|
28
|
+
export declare function isSandboxActive(): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Cleanup sandbox resources on shutdown.
|
|
31
|
+
*/
|
|
32
|
+
export declare function resetSandbox(): Promise<void>;
|
|
33
|
+
//# sourceMappingURL=sandbox.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sandbox.d.ts","sourceRoot":"","sources":["../src/sandbox.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAQ/C;;;GAGG;AACH,wBAAsB,WAAW,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,CA+CxE;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,CAAC,CAYlG;AAED;;GAEG;AACH,wBAAgB,eAAe,IAAI,OAAO,CAEzC;AAED;;GAEG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAQlD"}
|
package/dist/sandbox.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optional OS-level sandbox integration via @anthropic-ai/sandbox-runtime.
|
|
3
|
+
*
|
|
4
|
+
* When enabled (MCP_TERMINAL_SANDBOX=true), wraps spawned commands with
|
|
5
|
+
* kernel-level filesystem and network restrictions using sandbox-exec (macOS)
|
|
6
|
+
* or bubblewrap (Linux).
|
|
7
|
+
*
|
|
8
|
+
* This is an optional feature — if the package isn't installed or the platform
|
|
9
|
+
* isn't supported, it logs a warning and falls back to unsandboxed execution.
|
|
10
|
+
*/
|
|
11
|
+
import { audit } from "./utils/audit-logger.js";
|
|
12
|
+
// Lazy-loaded sandbox manager instance
|
|
13
|
+
let sandboxManager = null;
|
|
14
|
+
let sandboxInitialized = false;
|
|
15
|
+
let sandboxAvailable = false;
|
|
16
|
+
/**
|
|
17
|
+
* Initialize the sandbox if enabled in config.
|
|
18
|
+
* Call this once at startup. Safe to call even if the package isn't installed.
|
|
19
|
+
*/
|
|
20
|
+
export async function initSandbox(config) {
|
|
21
|
+
if (!config.sandbox)
|
|
22
|
+
return false;
|
|
23
|
+
try {
|
|
24
|
+
const { SandboxManager } = await import("@anthropic-ai/sandbox-runtime");
|
|
25
|
+
sandboxManager = SandboxManager;
|
|
26
|
+
// Check platform support
|
|
27
|
+
if (!SandboxManager.isSupportedPlatform()) {
|
|
28
|
+
console.error("[mcp-terminal] Sandbox: platform not supported, sandbox disabled");
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
// Build config from our env vars
|
|
32
|
+
const allowWrite = config.sandboxAllowWrite.length > 0
|
|
33
|
+
? config.sandboxAllowWrite
|
|
34
|
+
: ["/tmp"];
|
|
35
|
+
const allowedDomains = config.sandboxAllowNetwork[0] === "*"
|
|
36
|
+
? [] // Empty = no restriction (but sandbox-runtime treats empty as "block all")
|
|
37
|
+
: config.sandboxAllowNetwork;
|
|
38
|
+
// If user wants unrestricted network ("*"), we use a permissive config
|
|
39
|
+
const networkConfig = config.sandboxAllowNetwork[0] === "*"
|
|
40
|
+
? { allowedDomains: ["*"], deniedDomains: [] }
|
|
41
|
+
: { allowedDomains, deniedDomains: [] };
|
|
42
|
+
await SandboxManager.initialize({
|
|
43
|
+
network: networkConfig,
|
|
44
|
+
filesystem: {
|
|
45
|
+
denyRead: [],
|
|
46
|
+
allowWrite,
|
|
47
|
+
denyWrite: [],
|
|
48
|
+
},
|
|
49
|
+
allowPty: true,
|
|
50
|
+
});
|
|
51
|
+
sandboxInitialized = true;
|
|
52
|
+
sandboxAvailable = true;
|
|
53
|
+
audit("sandbox_init", undefined, { allowWrite, network: config.sandboxAllowNetwork });
|
|
54
|
+
console.error(`[mcp-terminal] Sandbox: ENABLED (write: ${allowWrite.join(", ")}, network: ${config.sandboxAllowNetwork.join(", ")})`);
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
audit("sandbox_fail", undefined, { error: String(err) });
|
|
59
|
+
console.error(`[mcp-terminal] Sandbox: failed to initialize (${err}). Sandbox disabled.`);
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Wrap a command string with sandbox restrictions.
|
|
65
|
+
* Returns the original command unchanged if sandbox is not available.
|
|
66
|
+
*/
|
|
67
|
+
export async function wrapCommand(command) {
|
|
68
|
+
if (!sandboxAvailable || !sandboxManager) {
|
|
69
|
+
return { command, useShell: false };
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
const wrapped = await sandboxManager.wrapWithSandbox(command);
|
|
73
|
+
return { command: wrapped, useShell: true };
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
console.error(`[mcp-terminal] Sandbox: wrapWithSandbox failed (${err}), running unsandboxed`);
|
|
77
|
+
return { command, useShell: false };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Check if sandbox is currently active.
|
|
82
|
+
*/
|
|
83
|
+
export function isSandboxActive() {
|
|
84
|
+
return sandboxAvailable;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Cleanup sandbox resources on shutdown.
|
|
88
|
+
*/
|
|
89
|
+
export async function resetSandbox() {
|
|
90
|
+
if (sandboxManager && sandboxInitialized) {
|
|
91
|
+
try {
|
|
92
|
+
await sandboxManager.reset();
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
// Ignore cleanup errors
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=sandbox.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sandbox.js","sourceRoot":"","sources":["../src/sandbox.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session lifecycle management.
|
|
3
|
+
* Handles creation, lookup, idle cleanup, and resource limits.
|
|
4
|
+
*/
|
|
5
|
+
import { type TerminalOptions } from "./terminal.js";
|
|
6
|
+
import type { Session, SessionInfo, ServerConfig } from "./types.js";
|
|
7
|
+
export declare class SessionManager {
|
|
8
|
+
private config;
|
|
9
|
+
private sessions;
|
|
10
|
+
private idleTimers;
|
|
11
|
+
constructor(config: ServerConfig);
|
|
12
|
+
get sessionCount(): number;
|
|
13
|
+
createSession(options: TerminalOptions & {
|
|
14
|
+
name?: string;
|
|
15
|
+
}): Promise<Session>;
|
|
16
|
+
getSession(id: string): Session;
|
|
17
|
+
listSessions(): SessionInfo[];
|
|
18
|
+
closeSession(id: string, signal?: string): void;
|
|
19
|
+
closeAll(): void;
|
|
20
|
+
touchSession(id: string): void;
|
|
21
|
+
/**
|
|
22
|
+
* Check if an absolute path is within the allowed paths list.
|
|
23
|
+
* Returns true if no allowedPaths are configured (unrestricted).
|
|
24
|
+
*/
|
|
25
|
+
isPathAllowed(targetPath: string): boolean;
|
|
26
|
+
private validatePath;
|
|
27
|
+
private validateCommand;
|
|
28
|
+
private resetIdleTimer;
|
|
29
|
+
private clearIdleTimer;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=session-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-manager.d.ts","sourceRoot":"","sources":["../src/session-manager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,EAAkB,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AACrE,OAAO,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAGrE,qBAAa,cAAc;IAIb,OAAO,CAAC,MAAM;IAH1B,OAAO,CAAC,QAAQ,CAA8B;IAC9C,OAAO,CAAC,UAAU,CAAoD;gBAElD,MAAM,EAAE,YAAY;IAExC,IAAI,YAAY,IAAI,MAAM,CAEzB;IAEK,aAAa,CAAC,OAAO,EAAE,eAAe,GAAG;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC;IA+CnF,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAU/B,YAAY,IAAI,WAAW,EAAE;IAW7B,YAAY,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI;IAS/C,QAAQ,IAAI,IAAI;IAUhB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAU9B;;;OAGG;IACH,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;IAS1C,OAAO,CAAC,YAAY;IAUpB,OAAO,CAAC,eAAe;IAgBvB,OAAO,CAAC,cAAc;IAgBtB,OAAO,CAAC,cAAc;CAOvB"}
|