Skip to content

Commit 3a756c8

Browse files
committed
Merge branch 'main' of https://github.com/ferrislucas/iterm-mcp into main
2 parents 971d586 + 7429ebc commit 3a756c8

File tree

6 files changed

+64
-27
lines changed

6 files changed

+64
-27
lines changed

.github/images/demo.png

649 KB
Loading

.github/images/robot-hand.jpg

-115 KB
Binary file not shown.

README.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
![Main Image](.github/images/robot-hand.jpg)
1+
![Main Image](.github/images/demo.png)
22

33
# iterm-mcp
44
[![smithery badge](https://smithery.ai/badge/iterm-mcp)](https://smithery.ai/server/iterm-mcp)
@@ -10,7 +10,8 @@ iterm-mcp will execute commands in the currently active tab of iTerm.
1010
<a href="https://glama.ai/mcp/servers/h89lr05ty6"><img width="380" height="200" src="https://glama.ai/mcp/servers/h89lr05ty6/badge" alt="iTerm Server MCP server" /></a>
1111

1212
### Tools
13-
- `execute_shell_command` - Executes a command in the current iTerm session
13+
- `write_to_terminal` - Writes to the active iTerm terminal - often used to run a command
14+
- `read_terminal_output` - Reads the output from the active iTerm terminal
1415

1516
## Installation
1617

src/CommandExecutor.ts

+3-19
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { promisify } from 'node:util';
33
import { openSync, closeSync } from 'node:fs';
44
import { isatty } from 'node:tty';
55
import TTYProcessTracker from './TTYProcessTracker.js';
6+
import TtyOutputReader from './TtyOutputReader.js';
67

78
const execPromise = promisify(exec);
89
const sleep = promisify(setTimeout);
@@ -42,7 +43,7 @@ class CommandExecutor {
4243

4344
try {
4445
// Retrieve the buffer before executing the command
45-
const initialBuffer = await this.retrieveBuffer();
46+
const initialBuffer = await TtyOutputReader.retrieveBuffer();
4647

4748
await execPromise(`osascript -e '${ascript}'`);
4849

@@ -59,7 +60,7 @@ class CommandExecutor {
5960
// Give a small delay for output to settle
6061
await new Promise(resolve => setTimeout(resolve, 200));
6162

62-
const afterCommandBuffer = await this.retrieveBuffer();
63+
const afterCommandBuffer = await TtyOutputReader.retrieveBuffer();
6364

6465
// Extract only the new content by comparing buffers
6566
const commandOutput = this.extractCommandOutput(initialBuffer, afterCommandBuffer, command);
@@ -121,23 +122,6 @@ class CommandExecutor {
121122
}
122123
}
123124

124-
private async retrieveBuffer(): Promise<string> {
125-
const ascript = `
126-
tell application "iTerm2"
127-
tell front window
128-
tell current session of current tab
129-
set numRows to number of rows
130-
set allContent to contents
131-
return allContent
132-
end tell
133-
end tell
134-
end tell
135-
`;
136-
137-
const { stdout: finalContent } = await execPromise(`osascript -e '${ascript}'`);
138-
return finalContent.trim();
139-
}
140-
141125
private async retrieveTtyPath(): Promise<string> {
142126
const ascript = `
143127
tell application "iTerm2"

src/TtyOutputReader.ts

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { exec } from 'node:child_process';
2+
import { promisify } from 'node:util';
3+
4+
const execPromise = promisify(exec);
5+
6+
export default class TtyOutputReader {
7+
static async call(linesOfOutput: number) {
8+
const buffer = await this.retrieveBuffer();
9+
const lines = buffer.split('\n');
10+
return lines.slice(-linesOfOutput).join('\n');
11+
}
12+
13+
static async retrieveBuffer(): Promise<string> {
14+
const ascript = `
15+
tell application "iTerm2"
16+
tell front window
17+
tell current session of current tab
18+
set numRows to number of rows
19+
set allContent to contents
20+
return allContent
21+
end tell
22+
end tell
23+
end tell
24+
`;
25+
26+
const { stdout: finalContent } = await execPromise(`osascript -e '${ascript}'`);
27+
return finalContent.trim();
28+
}
29+
}

src/index.ts

+29-6
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
ListToolsRequestSchema,
1717
} from "@modelcontextprotocol/sdk/types.js";
1818
import CommandExecutor from "./CommandExecutor.js";
19-
19+
import TtyOutputReader from "./TtyOutputReader.js";
2020

2121
/**
2222
* Type alias for a note object.
@@ -50,7 +50,6 @@ const server = new Server(
5050
}
5151
);
5252

53-
5453
/**
5554
* Handler that lists available tools.
5655
* Exposes a single "create_note" tool that lets clients create new notes.
@@ -59,18 +58,32 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
5958
return {
6059
tools: [
6160
{
62-
name: "execute_shell_command",
63-
description: "Execute a shell command in the active iIterm session",
61+
name: "write_to_terminal",
62+
description: "Writes text to the active iTerm terminal - often used to run a command in the terminal",
6463
inputSchema: {
6564
type: "object",
6665
properties: {
6766
command: {
6867
type: "string",
69-
description: "The command to run"
68+
description: "The command to run or text to write to the terminal"
7069
},
7170
},
7271
required: ["command"]
7372
}
73+
},
74+
{
75+
name: "read_terminal_output",
76+
description: "Reads the output from the active iTerm terminal",
77+
inputSchema: {
78+
type: "object",
79+
properties: {
80+
linesOfOutput: {
81+
type: "number",
82+
description: "The number of lines of output to read. Default is the most recent 25 lines of output"
83+
},
84+
},
85+
required: ["linesOfOutput"]
86+
}
7487
}
7588
]
7689
};
@@ -82,7 +95,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
8295
*/
8396
server.setRequestHandler(CallToolRequestSchema, async (request) => {
8497
switch (request.params.name) {
85-
case "execute_shell_command": {
98+
case "write_to_terminal": {
8699
let executor = new CommandExecutor()
87100
const command = String(request.params.arguments?.command)
88101
const output = await executor.executeCommand(command)
@@ -94,7 +107,17 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
94107
}]
95108
};
96109
}
110+
case "read_terminal_output": {
111+
const linesOfOutput = Number(request.params.arguments?.linesOfOutput) || 25
112+
const output = await TtyOutputReader.call(linesOfOutput)
97113

114+
return {
115+
content: [{
116+
type: "text",
117+
text: output
118+
}]
119+
};
120+
}
98121
default:
99122
throw new Error("Unknown tool");
100123
}

0 commit comments

Comments
 (0)