@priyanshumit/macos-terminal-mcp 0.3.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Priyanshu Mittal
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,219 @@
1
+ # macos-terminal-mcp
2
+
3
+ A local MCP server that lets AI agents inspect and drive your macOS Terminal.app tabs — list windows, read scrollback, execute commands, and clear buffers, all gated by a three-tier safety model plus per-call user confirmation for any write operation.
4
+
5
+ ## What it does
6
+
7
+ Eleven MCP tools across three categories:
8
+
9
+ ### Terminal interaction
10
+
11
+ | Tool | Read/Write | Description |
12
+ |---|---|---|
13
+ | `terminal_list` | read | Enumerate every open Terminal.app tab with tty, title, busy state, and foreground processes. |
14
+ | `terminal_read` | read | Return the full buffer + scrollback of a specific tab, identified by tty. |
15
+ | `terminal_execute` | **write** | Type a command into a specific tab and press Enter. Evaluated against the safety policy; runs auto/confirms/refuses depending on level. |
16
+ | `terminal_clear` | **write** | Wipe scrollback of a specific tab via Cmd+K. Briefly steals focus. |
17
+
18
+ ### Safety policy management
19
+
20
+ | Tool | Read/Write | Description |
21
+ |---|---|---|
22
+ | `safety_list` | read | Show all current safety patterns with their levels. |
23
+ | `safety_add` | **write** | Propose a new pattern + level; user approves via dialog before persisting. |
24
+ | `safety_remove` | **write** | Drop a pattern. Warns prominently in the dialog if the pattern is `forbidden`. |
25
+ | `safety_set_level` | **write** | Change the level of an existing pattern. Warns when downgrading from `forbidden`. |
26
+
27
+ ### Async approval queue
28
+
29
+ | Tool | Read/Write | Description |
30
+ |---|---|---|
31
+ | `pending_list` | read | Snapshot of commands currently awaiting approval. |
32
+ | `pending_approve` | **write** | Approve a queued command by id. Triggers its own confirmation dialog. |
33
+ | `pending_deny` | **write** | Deny a queued command. |
34
+
35
+ Write tools are **off by default**. Set `WRITE_TOOLS_ENABLED=1` in the server's environment to enable them. Even when enabled, every non-`safe` operation triggers a native macOS confirmation dialog.
36
+
37
+ ## Prerequisites
38
+
39
+ - macOS (uses JXA / `osascript`)
40
+ - Node ≥ 20
41
+ - Terminal.app (the stock macOS terminal)
42
+
43
+ ## Install
44
+
45
+ ### Option A: via npm (recommended)
46
+
47
+ Once published:
48
+
49
+ ```bash
50
+ # Run directly without installing
51
+ npx -y @priyanshumit/macos-terminal-mcp
52
+
53
+ # Or install globally
54
+ npm install -g @priyanshumit/macos-terminal-mcp
55
+ ```
56
+
57
+ ### Option B: from source
58
+
59
+ ```bash
60
+ git clone https://github.com/priyanshumit/macos-terminal-mcp.git
61
+ cd macos-terminal-mcp
62
+ npm install
63
+ npm run build
64
+ ```
65
+
66
+ ## macOS permissions
67
+
68
+ First time the MCP server controls Terminal.app, macOS will prompt for permission:
69
+
70
+ > *"node" wants access to control "Terminal".*
71
+
72
+ Click **OK**. The setting is remembered in **System Settings → Privacy & Security → Automation**.
73
+
74
+ `terminal_clear` additionally requires **Accessibility** permission (it uses System Events to simulate Cmd+K). Grant under **System Settings → Privacy & Security → Accessibility**.
75
+
76
+ ## Scrollback configuration
77
+
78
+ For `terminal_read` to return meaningful history, set Terminal.app's scrollback to a generous size:
79
+
80
+ **Terminal.app → Settings → Profiles → (your profile) → Window → Scrollback**: set to **Unlimited** or a large fixed number.
81
+
82
+ ## Register with Claude Code
83
+
84
+ ```bash
85
+ claude mcp add macos-terminal --command=npx --args=-y --args=@priyanshumit/macos-terminal-mcp
86
+ ```
87
+
88
+ Or add to `~/.claude.json` / project `.mcp.json` manually:
89
+
90
+ ```json
91
+ {
92
+ "mcpServers": {
93
+ "macos-terminal": {
94
+ "command": "npx",
95
+ "args": ["-y", "@priyanshumit/macos-terminal-mcp"]
96
+ }
97
+ }
98
+ }
99
+ ```
100
+
101
+ To enable write tools, add the env block:
102
+
103
+ ```json
104
+ {
105
+ "mcpServers": {
106
+ "macos-terminal": {
107
+ "command": "npx",
108
+ "args": ["-y", "@priyanshumit/macos-terminal-mcp"],
109
+ "env": { "WRITE_TOOLS_ENABLED": "1" }
110
+ }
111
+ }
112
+ }
113
+ ```
114
+
115
+ Restart Claude Code. You should see the eleven tools listed.
116
+
117
+ ## Three-tier safety model
118
+
119
+ Every `terminal_execute` call is evaluated against a list of regex patterns, each tagged with a level:
120
+
121
+ | Level | Behavior |
122
+ |---|---|
123
+ | `safe` | Auto-run, no confirmation |
124
+ | `requires_approval` | Native confirmation dialog (also enqueued for async approval via `pending_*`) |
125
+ | `forbidden` | **Refused outright** — no dialog can approve it. To run a forbidden command, do it yourself in a real terminal. |
126
+
127
+ **Evaluation rule: highest-restriction wins.** If a command matches both a `safe` pattern and a `forbidden` pattern, it's forbidden. This blocks composite-command bypasses like `ls && rm -rf /tmp/x` — the safe `^ls` match doesn't shield the forbidden `\brm\s+-rf?\b` match.
128
+
129
+ **Default if no pattern matches**: `requires_approval`. New/unknown commands always confirm.
130
+
131
+ ## Default forbidden patterns
132
+
133
+ | Pattern | Why |
134
+ |---|---|
135
+ | `\brm\s+-rf?\b` | Recursive delete |
136
+ | `\bsudo\b` | Privilege escalation — humans only |
137
+ | `\|\s*(bash\|sh\|zsh)\b` | Pipe-to-shell — common attack vector |
138
+ | `\bcurl\b[^\|;]*\|`, `\bwget\b[^\|;]*\|` | Curl/wget piped to anything |
139
+ | `>\s*/etc/`, `>\s*/dev/` | Writing to /etc or /dev |
140
+ | `/etc/passwd`, `/etc/shadow`, `~/.ssh` | System or credential files |
141
+ | `\bdd\s+if=` | dd — can overwrite disks |
142
+ | `\bgit\s+push\s+(--force\|-f)\b` | Force push |
143
+ | `\bgit\s+reset\s+--hard\b` | Discards local work |
144
+ | `\bgit\s+clean\s+-[fdx]+\b` | Destructive git clean |
145
+ | `\bshutdown\b`, `\breboot\b`, `\bkillall\b` | System control |
146
+ | `:\(\)\{:\|:&\};:` | Fork bomb |
147
+
148
+ The full list is in `src/safety/patterns.ts`. Customize via `safety_*` tools or by editing `~/.config/macos-terminal-mcp/safety.json` directly.
149
+
150
+ ## Customizing patterns
151
+
152
+ **From within Claude:**
153
+
154
+ > *"Add `^cargo build` as a safe pattern."* → Claude calls `safety_add({pattern: "^cargo build", level: "safe"})` → you click Allow on the dialog → it's persisted.
155
+
156
+ > *"Show me the current safety policy."* → `safety_list` returns the JSON.
157
+
158
+ > *"Forbidden `^docker\\s+rm`."* → Claude calls `safety_add({pattern: "^docker\\s+rm", level: "forbidden"})` → dialog → persisted.
159
+
160
+ **By editing the file:**
161
+
162
+ `~/.config/macos-terminal-mcp/safety.json`:
163
+
164
+ ```json
165
+ {
166
+ "patterns": [
167
+ { "pattern": "^cargo build\\b", "level": "safe", "description": "Rust builds" },
168
+ { "pattern": "\\bmy-deploy-script\\b", "level": "forbidden", "description": "Never deploy from AI" }
169
+ ]
170
+ }
171
+ ```
172
+
173
+ If the file has the v1 schema (`{"allowlist": [...], "denylist": [...]}`), it's automatically migrated on load.
174
+
175
+ ## Async approval queue
176
+
177
+ When `terminal_execute` triggers `requires_approval`, two things happen in parallel:
178
+ 1. A native macOS dialog pops asking you to Allow/Deny.
179
+ 2. The command is enqueued — visible via `pending_list`, resolvable via `pending_approve(id)` / `pending_deny(id, reason)`.
180
+
181
+ Whichever path resolves first wins. For solo desktop use, the dialog is the canonical signal. For headless/remote/team contexts, the queue tools provide an out-of-band approval path.
182
+
183
+ Pending entries auto-expire after 10 minutes. The queue is in-memory only — a server restart drops all pending entries.
184
+
185
+ ## Usage examples
186
+
187
+ Once registered, from Claude Code:
188
+
189
+ - *"List my open terminals."* → `terminal_list`
190
+ - *"What's the last 50 lines from /dev/ttys062?"* → `terminal_read({tty: "/dev/ttys062", lines: 50})`
191
+ - *"Run `git status` in /dev/ttys054."* → `terminal_execute` (auto-runs, safe pattern)
192
+ - *"Run `rm -rf /tmp/cache` in /dev/ttys054."* → **refused** (matches forbidden pattern)
193
+ - *"Run `cargo test` in /dev/ttys054."* → dialog pops (no safe pattern matches, default requires_approval)
194
+ - *"Show me what's in the approval queue."* → `pending_list`
195
+ - *"Approve queued command abc-123."* → `pending_approve({id: "abc-123"})` — also pops a dialog
196
+
197
+ ## Troubleshooting
198
+
199
+ **"osascript exited 1: ... not authorized"**
200
+ Automation permission has not been granted. Open System Settings → Privacy & Security → Automation, find the process, allow it to control Terminal.
201
+
202
+ **Confirmation dialogs never appear**
203
+ The MCP server is in a context without a GUI session. Test:
204
+ ```bash
205
+ osascript -l JavaScript -e 'Application.currentApplication().includeStandardAdditions = true; Application.currentApplication().displayDialog("test")'
206
+ ```
207
+
208
+ **`terminal_clear` does nothing**
209
+ Requires Accessibility permission, and the target window must accept keyboard focus. If you have multiple Terminal windows, focus may not settle on the intended one before the keystroke fires. Run `terminal_list` first to confirm the tty.
210
+
211
+ **`terminal_read` returns less than expected**
212
+ Terminal.app's scrollback cap is profile-controlled. Increase under Terminal.app → Settings → Profiles → Window → Scrollback.
213
+
214
+ **Command refused as "forbidden" but I have a legit reason**
215
+ Forbidden patterns intentionally cannot be approved via the tool. Either run the command yourself in a real terminal, or use `safety_set_level({pattern: "...", level: "requires_approval"})` to downgrade — you'll see a downgrade warning in the confirmation dialog.
216
+
217
+ ## License
218
+
219
+ MIT — see [LICENSE](./LICENSE).
@@ -0,0 +1,75 @@
1
+ import { spawn } from "node:child_process";
2
+ export class OsascriptError extends Error {
3
+ stderr;
4
+ code;
5
+ timedOut;
6
+ constructor(message, stderr, code, timedOut = false) {
7
+ super(message);
8
+ this.stderr = stderr;
9
+ this.code = code;
10
+ this.timedOut = timedOut;
11
+ this.name = "OsascriptError";
12
+ }
13
+ }
14
+ const DEFAULT_TIMEOUT_MS = 30_000;
15
+ export function runJxa(script, options = {}) {
16
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
17
+ return new Promise((resolve, reject) => {
18
+ const proc = spawn("osascript", ["-l", "JavaScript"], {
19
+ stdio: ["pipe", "pipe", "pipe"],
20
+ });
21
+ let stdout = "";
22
+ let stderr = "";
23
+ let settled = false;
24
+ const timer = setTimeout(() => {
25
+ if (settled)
26
+ return;
27
+ settled = true;
28
+ try {
29
+ proc.kill("SIGKILL");
30
+ }
31
+ catch {
32
+ /* ignore */
33
+ }
34
+ reject(new OsascriptError(`osascript timed out after ${timeoutMs}ms`, stderr.trim(), -1, true));
35
+ }, timeoutMs);
36
+ proc.stdout.on("data", (chunk) => {
37
+ stdout += chunk.toString("utf8");
38
+ });
39
+ proc.stderr.on("data", (chunk) => {
40
+ stderr += chunk.toString("utf8");
41
+ });
42
+ proc.on("error", (err) => {
43
+ if (settled)
44
+ return;
45
+ settled = true;
46
+ clearTimeout(timer);
47
+ reject(err);
48
+ });
49
+ proc.on("close", (code) => {
50
+ if (settled)
51
+ return;
52
+ settled = true;
53
+ clearTimeout(timer);
54
+ if (code === 0) {
55
+ resolve(stdout.replace(/\n$/, ""));
56
+ }
57
+ else {
58
+ reject(new OsascriptError(`osascript exited ${code}: ${stderr.trim()}`, stderr.trim(), code ?? -1));
59
+ }
60
+ });
61
+ proc.stdin.write(script);
62
+ proc.stdin.end();
63
+ });
64
+ }
65
+ export async function runJxaJson(script, options = {}) {
66
+ const output = await runJxa(script, options);
67
+ try {
68
+ return JSON.parse(output);
69
+ }
70
+ catch (err) {
71
+ const preview = output.length > 500 ? `${output.slice(0, 500)}…` : output;
72
+ throw new OsascriptError(`JXA stdout was not valid JSON: ${err.message}\nOutput: ${preview}`, output, -1);
73
+ }
74
+ }
75
+ //# sourceMappingURL=applescript.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"applescript.js","sourceRoot":"","sources":["../src/applescript.ts"],"names":[],"mappings":"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"}
package/dist/index.js ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { registerAll } from "./tools/register.js";
5
+ const server = new McpServer({ name: "macos-terminal-mcp", version: "0.1.0" }, { capabilities: { tools: {} } });
6
+ registerAll(server);
7
+ async function main() {
8
+ const transport = new StdioServerTransport();
9
+ await server.connect(transport);
10
+ process.stderr.write("[macos-terminal-mcp] server ready on stdio\n");
11
+ }
12
+ main().catch((err) => {
13
+ process.stderr.write(`[macos-terminal-mcp] fatal: ${err instanceof Error ? err.message : String(err)}\n`);
14
+ process.exit(1);
15
+ });
16
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAElD,MAAM,MAAM,GAAG,IAAI,SAAS,CAC1B,EAAE,IAAI,EAAE,oBAAoB,EAAE,OAAO,EAAE,OAAO,EAAE,EAChD,EAAE,YAAY,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,CAChC,CAAC;AAEF,WAAW,CAAC,MAAM,CAAC,CAAC;AAEpB,KAAK,UAAU,IAAI;IACjB,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,8CAA8C,CAAC,CAAC;AACvE,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;IAC5B,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,+BAA+B,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CACpF,CAAC;IACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,30 @@
1
+ import { appendFile, mkdir } from "node:fs/promises";
2
+ import { homedir } from "node:os";
3
+ import { dirname, join } from "node:path";
4
+ function resolveDefaultPath() {
5
+ const xdg = process.env.XDG_STATE_HOME;
6
+ const base = xdg && xdg.length > 0 ? xdg : join(homedir(), ".local", "state");
7
+ return join(base, "macos-terminal-mcp", "audit.log");
8
+ }
9
+ export const AUDIT_LOG_PATH = resolveDefaultPath();
10
+ const COMMAND_TRUNCATE = 1000;
11
+ function truncate(s, max) {
12
+ return s.length > max ? `${s.slice(0, max)}…[+${s.length - max}]` : s;
13
+ }
14
+ export async function appendAudit(entry, path = AUDIT_LOG_PATH) {
15
+ const record = {
16
+ timestamp: entry.timestamp ?? new Date().toISOString(),
17
+ ...entry,
18
+ };
19
+ if (record.command !== undefined) {
20
+ record.command = truncate(record.command, COMMAND_TRUNCATE);
21
+ }
22
+ try {
23
+ await mkdir(dirname(path), { recursive: true });
24
+ await appendFile(path, `${JSON.stringify(record)}\n`, "utf8");
25
+ }
26
+ catch (err) {
27
+ process.stderr.write(`[macos-terminal-mcp] audit log write failed: ${err.message}\n`);
28
+ }
29
+ }
30
+ //# sourceMappingURL=audit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit.js","sourceRoot":"","sources":["../../src/safety/audit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAmB1C,SAAS,kBAAkB;IACzB,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;IACvC,MAAM,IAAI,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC9E,OAAO,IAAI,CAAC,IAAI,EAAE,oBAAoB,EAAE,WAAW,CAAC,CAAC;AACvD,CAAC;AAED,MAAM,CAAC,MAAM,cAAc,GAAG,kBAAkB,EAAE,CAAC;AAEnD,MAAM,gBAAgB,GAAG,IAAI,CAAC;AAE9B,SAAS,QAAQ,CAAC,CAAS,EAAE,GAAW;IACtC,OAAO,CAAC,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,MAAM,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;AACxE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,KAA6D,EAC7D,OAAe,cAAc;IAE7B,MAAM,MAAM,GAAe;QACzB,SAAS,EAAE,KAAK,CAAC,SAAS,IAAI,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACtD,GAAG,KAAK;KACT,CAAC;IACF,IAAI,MAAM,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;QACjC,MAAM,CAAC,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,OAAO,EAAE,gBAAgB,CAAC,CAAC;IAC9D,CAAC;IACD,IAAI,CAAC;QACH,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAChD,MAAM,UAAU,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAChE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,gDAAiD,GAAa,CAAC,OAAO,IAAI,CAC3E,CAAC;IACJ,CAAC;AACH,CAAC"}
@@ -0,0 +1,44 @@
1
+ import { runJxa } from "../applescript.js";
2
+ export function isWriteToolsEnabled() {
3
+ return process.env.WRITE_TOOLS_ENABLED === "1";
4
+ }
5
+ const DEFAULT_DIALOG_TIMEOUT_SEC = 300;
6
+ export async function confirmWithUser(req) {
7
+ const allow = req.allowLabel ?? "Allow";
8
+ const deny = req.denyLabel ?? "Deny";
9
+ const timeoutSec = req.timeoutSeconds ?? DEFAULT_DIALOG_TIMEOUT_SEC;
10
+ const script = `
11
+ var app = Application.currentApplication();
12
+ app.includeStandardAdditions = true;
13
+ (function () {
14
+ try {
15
+ var result = app.displayDialog(
16
+ ${JSON.stringify(req.message)},
17
+ {
18
+ buttons: [${JSON.stringify(deny)}, ${JSON.stringify(allow)}],
19
+ defaultButton: ${JSON.stringify(deny)},
20
+ cancelButton: ${JSON.stringify(deny)},
21
+ withTitle: ${JSON.stringify(req.title)},
22
+ withIcon: "caution",
23
+ givingUpAfter: ${timeoutSec}
24
+ }
25
+ );
26
+ if (result.gaveUp) return "TIMEOUT";
27
+ return result.buttonReturned === ${JSON.stringify(allow)} ? "ALLOW" : "DENY";
28
+ } catch (e) {
29
+ return "DENY";
30
+ }
31
+ })();
32
+ `;
33
+ // Outer osascript timeout is dialog timeout + 10s buffer so the dialog's own
34
+ // givingUpAfter has a chance to fire before the spawn-level kill engages.
35
+ const result = await runJxa(script, { timeoutMs: (timeoutSec + 10) * 1000 });
36
+ return result.trim() === "ALLOW";
37
+ }
38
+ export function writeToolsDisabledMessage(toolName) {
39
+ return (`${toolName} is disabled. Write tools (terminal_execute, terminal_clear, safety_*) ` +
40
+ `are off by default for safety. To enable, set environment variable ` +
41
+ `WRITE_TOOLS_ENABLED=1 in the MCP server's env block. Each non-"safe" call ` +
42
+ `still prompts via a native macOS dialog.`);
43
+ }
44
+ //# sourceMappingURL=confirm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"confirm.js","sourceRoot":"","sources":["../../src/safety/confirm.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAE3C,MAAM,UAAU,mBAAmB;IACjC,OAAO,OAAO,CAAC,GAAG,CAAC,mBAAmB,KAAK,GAAG,CAAC;AACjD,CAAC;AAWD,MAAM,0BAA0B,GAAG,GAAG,CAAC;AAEvC,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,GAAmB;IACvD,MAAM,KAAK,GAAG,GAAG,CAAC,UAAU,IAAI,OAAO,CAAC;IACxC,MAAM,IAAI,GAAG,GAAG,CAAC,SAAS,IAAI,MAAM,CAAC;IACrC,MAAM,UAAU,GAAG,GAAG,CAAC,cAAc,IAAI,0BAA0B,CAAC;IACpE,MAAM,MAAM,GAAG;;;;;;QAMT,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC;;oBAEf,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC;yBACzC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;wBACrB,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;qBACvB,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC;;yBAErB,UAAU;;;;uCAII,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC;;;;;CAK3D,CAAC;IACA,6EAA6E;IAC7E,0EAA0E;IAC1E,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,CAAC,UAAU,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC;IAC7E,OAAO,MAAM,CAAC,IAAI,EAAE,KAAK,OAAO,CAAC;AACnC,CAAC;AAED,MAAM,UAAU,yBAAyB,CAAC,QAAgB;IACxD,OAAO,CACL,GAAG,QAAQ,yEAAyE;QACpF,qEAAqE;QACrE,4EAA4E;QAC5E,0CAA0C,CAC3C,CAAC;AACJ,CAAC"}
@@ -0,0 +1,176 @@
1
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import { homedir } from "node:os";
3
+ import { dirname, join } from "node:path";
4
+ export const SAFETY_CONFIG_PATH = join(homedir(), ".config", "macos-terminal-mcp", "safety.json");
5
+ const DEFAULT_PATTERNS = [
6
+ // FORBIDDEN — never run, even with confirmation. The user must run these in a terminal themselves.
7
+ {
8
+ pattern: "\\brm\\s+-rf?\\b",
9
+ level: "forbidden",
10
+ description: "Recursive rm — too destructive to expose to an AI agent",
11
+ },
12
+ {
13
+ pattern: "\\bsudo\\b",
14
+ level: "forbidden",
15
+ description: "Privilege escalation should always be done by a human",
16
+ },
17
+ {
18
+ pattern: "\\|\\s*(bash|sh|zsh)\\b",
19
+ level: "forbidden",
20
+ description: "Piping into a shell — common attack vector",
21
+ },
22
+ {
23
+ pattern: "\\bcurl\\b[^|;]*\\|",
24
+ level: "forbidden",
25
+ description: "curl piped to another command",
26
+ },
27
+ {
28
+ pattern: "\\bwget\\b[^|;]*\\|",
29
+ level: "forbidden",
30
+ description: "wget piped to another command",
31
+ },
32
+ { pattern: ">\\s*/etc/", level: "forbidden", description: "Writing to /etc" },
33
+ { pattern: ">\\s*/dev/", level: "forbidden", description: "Writing to /dev" },
34
+ { pattern: "/etc/passwd", level: "forbidden", description: "Touching /etc/passwd" },
35
+ { pattern: "/etc/shadow", level: "forbidden", description: "Touching /etc/shadow" },
36
+ { pattern: "~/.ssh", level: "forbidden", description: "Touching SSH keys" },
37
+ { pattern: "\\bdd\\s+if=", level: "forbidden", description: "dd — can overwrite disks" },
38
+ { pattern: ":\\(\\)\\{:\\|:&\\};:", level: "forbidden", description: "Fork bomb" },
39
+ { pattern: "\\bshutdown\\b", level: "forbidden", description: "System shutdown" },
40
+ { pattern: "\\breboot\\b", level: "forbidden", description: "System reboot" },
41
+ { pattern: "\\bkillall\\b", level: "forbidden", description: "Mass process kill" },
42
+ {
43
+ pattern: "\\bgit\\s+push\\s+(--force|-f)\\b",
44
+ level: "forbidden",
45
+ description: "Force push — usually a mistake",
46
+ },
47
+ {
48
+ pattern: "\\bgit\\s+reset\\s+--hard\\b",
49
+ level: "forbidden",
50
+ description: "git reset --hard — discards local work",
51
+ },
52
+ {
53
+ pattern: "\\bgit\\s+clean\\s+-[fdx]+\\b",
54
+ level: "forbidden",
55
+ description: "git clean with -f flags — destructive",
56
+ },
57
+ // SAFE — auto-run, no confirmation needed.
58
+ { pattern: "^ls(\\s|$)", level: "safe", description: "List directory contents" },
59
+ { pattern: "^pwd(\\s|$)", level: "safe", description: "Print working directory" },
60
+ { pattern: "^cd(\\s|$)", level: "safe", description: "Change directory" },
61
+ { pattern: "^echo(\\s|$)", level: "safe", description: "Echo arguments" },
62
+ { pattern: "^cat\\s", level: "safe", description: "Print file contents" },
63
+ { pattern: "^less\\s", level: "safe", description: "Page through a file" },
64
+ { pattern: "^head\\s", level: "safe", description: "First N lines of a file" },
65
+ { pattern: "^tail\\s", level: "safe", description: "Last N lines of a file" },
66
+ { pattern: "^which\\s", level: "safe", description: "Locate a command" },
67
+ { pattern: "^type\\s", level: "safe", description: "Describe a command" },
68
+ { pattern: "^date(\\s|$)", level: "safe", description: "Current date/time" },
69
+ { pattern: "^whoami(\\s|$)", level: "safe", description: "Current user" },
70
+ { pattern: "^uptime(\\s|$)", level: "safe", description: "System uptime" },
71
+ {
72
+ pattern: "^git\\s+(status|log|diff|branch|show|remote|stash list)\\b",
73
+ level: "safe",
74
+ description: "Read-only git operations",
75
+ },
76
+ {
77
+ pattern: "^npm\\s+(test|run\\s+test|run\\s+lint|run\\s+typecheck)\\b",
78
+ level: "safe",
79
+ description: "Common npm read-ish operations",
80
+ },
81
+ { pattern: "^node\\s+--version\\b", level: "safe" },
82
+ { pattern: "^python3?\\s+--version\\b", level: "safe" },
83
+ ];
84
+ export async function loadSafetyConfig(path = SAFETY_CONFIG_PATH) {
85
+ try {
86
+ const raw = await readFile(path, "utf8");
87
+ const parsed = JSON.parse(raw);
88
+ return normalizeConfig(parsed);
89
+ }
90
+ catch {
91
+ return { patterns: DEFAULT_PATTERNS };
92
+ }
93
+ }
94
+ export async function saveSafetyConfig(config, path = SAFETY_CONFIG_PATH) {
95
+ await mkdir(dirname(path), { recursive: true });
96
+ await writeFile(path, `${JSON.stringify(config, null, 2)}\n`, "utf8");
97
+ }
98
+ export function defaultPatterns() {
99
+ return DEFAULT_PATTERNS.map((p) => ({ ...p }));
100
+ }
101
+ export function normalizeConfig(raw) {
102
+ if (!raw || typeof raw !== "object")
103
+ return { patterns: DEFAULT_PATTERNS };
104
+ const o = raw;
105
+ if (Array.isArray(o.patterns)) {
106
+ return {
107
+ patterns: o.patterns.filter((e) => isValidEntry(e)),
108
+ };
109
+ }
110
+ // Migrate v1 schema {allowlist: [], denylist: []} → v2 {patterns: [...]}
111
+ if (Array.isArray(o.allowlist) || Array.isArray(o.denylist)) {
112
+ const patterns = [];
113
+ if (Array.isArray(o.denylist)) {
114
+ for (const p of o.denylist) {
115
+ if (typeof p === "string") {
116
+ patterns.push({
117
+ pattern: p,
118
+ level: "requires_approval",
119
+ description: "Migrated from v1 denylist",
120
+ });
121
+ }
122
+ }
123
+ }
124
+ if (Array.isArray(o.allowlist)) {
125
+ for (const p of o.allowlist) {
126
+ if (typeof p === "string") {
127
+ patterns.push({
128
+ pattern: p,
129
+ level: "safe",
130
+ description: "Migrated from v1 allowlist",
131
+ });
132
+ }
133
+ }
134
+ }
135
+ return { patterns: patterns.length > 0 ? patterns : DEFAULT_PATTERNS };
136
+ }
137
+ return { patterns: DEFAULT_PATTERNS };
138
+ }
139
+ function isValidEntry(e) {
140
+ if (!e || typeof e !== "object")
141
+ return false;
142
+ const o = e;
143
+ return (typeof o.pattern === "string" &&
144
+ (o.level === "safe" || o.level === "requires_approval" || o.level === "forbidden") &&
145
+ (o.description === undefined || typeof o.description === "string"));
146
+ }
147
+ const LEVEL_RANK = {
148
+ safe: 0,
149
+ requires_approval: 1,
150
+ forbidden: 2,
151
+ };
152
+ export function evaluateCommand(command, config) {
153
+ let result = null;
154
+ for (const entry of config.patterns) {
155
+ if (!testPattern(entry.pattern, command))
156
+ continue;
157
+ if (!result || LEVEL_RANK[entry.level] > LEVEL_RANK[result.level]) {
158
+ result = {
159
+ level: entry.level,
160
+ matchedPattern: entry.pattern,
161
+ matchedDescription: entry.description,
162
+ };
163
+ }
164
+ }
165
+ // No pattern matched — default is requires_approval (safer than safe)
166
+ return result ?? { level: "requires_approval" };
167
+ }
168
+ function testPattern(pattern, command) {
169
+ try {
170
+ return new RegExp(pattern).test(command);
171
+ }
172
+ catch {
173
+ return false;
174
+ }
175
+ }
176
+ //# sourceMappingURL=patterns.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patterns.js","sourceRoot":"","sources":["../../src/safety/patterns.ts"],"names":[],"mappings":"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"}