Loxo MCP Server
by tbensonwest
- src
#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import CommandExecutor from "./CommandExecutor.js";
import TtyOutputReader from "./TtyOutputReader.js";
import SendControlCharacter from "./SendControlCharacter.js";
const server = new Server(
{
name: "iterm-mcp",
version: "0.1.0",
},
{
capabilities: {
tools: {},
},
}
);
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "write_to_terminal",
description: "Writes text to the active iTerm terminal - often used to run a command in the terminal",
inputSchema: {
type: "object",
properties: {
command: {
type: "string",
description: "The command to run or text to write to the terminal"
},
},
required: ["command"]
}
},
{
name: "read_terminal_output",
description: "Reads the output from the active iTerm terminal",
inputSchema: {
type: "object",
properties: {
linesOfOutput: {
type: "number",
description: "The number of lines of output to read."
},
},
required: ["linesOfOutput"]
}
},
{
name: "send_control_character",
description: "Sends a control character to the active iTerm terminal (e.g., Control-C)",
inputSchema: {
type: "object",
properties: {
letter: {
type: "string",
description: "The letter corresponding to the control character (e.g., 'C' for Control-C)"
},
},
required: ["letter"]
}
}
]
};
});
server.setRequestHandler(CallToolRequestSchema, async (request) => {
switch (request.params.name) {
case "write_to_terminal": {
let executor = new CommandExecutor();
const command = String(request.params.arguments?.command);
const beforeCommandBuffer = await TtyOutputReader.retrieveBuffer();
const beforeCommandBufferLines = beforeCommandBuffer.split("\n").length;
await executor.executeCommand(command);
const afterCommandBuffer = await TtyOutputReader.retrieveBuffer();
const afterCommandBufferLines = afterCommandBuffer.split("\n").length;
const outputLines = afterCommandBufferLines - beforeCommandBufferLines
return {
content: [{
type: "text",
text: `${outputLines} lines were output after sending the command to the terminal. Read the last ${outputLines} lines of terminal contents to orient yourself. Never assume that the command was executed or that it was successful.`
}]
};
}
case "read_terminal_output": {
const linesOfOutput = Number(request.params.arguments?.linesOfOutput) || 25
const output = await TtyOutputReader.call(linesOfOutput)
return {
content: [{
type: "text",
text: output
}]
};
}
case "send_control_character": {
const ttyControl = new SendControlCharacter();
const letter = String(request.params.arguments?.letter);
await ttyControl.send(letter);
return {
content: [{
type: "text",
text: `Sent control character: Control-${letter.toUpperCase()}`
}]
};
}
default:
throw new Error("Unknown tool");
}
});
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
}
main().catch((error) => {
console.error("Server error:", error);
process.exit(1);
});