Skip to main content
Glama
masx200

Persistent Terminal MCP Server

by masx200
TERMINAL_FIXES.md10.4 kB
# 终端交互问题修复报告 ## 修复概述 本次修复解决了 persistent-terminal MCP 服务器在实际使用中发现的三个关键问题: 1. ✅ **命令执行问题** - 命令输入没有正确发送到终端进程 2. ✅ **交互式输入处理** - 键盘控制字符处理不稳定 3. ✅ **输出读取实时性** - 读取到的输出不是最新的 ## 问题 1: 命令执行修复 ✅ ### 问题描述 - 命令发送后没有回显 - 命令似乎没有执行 - 终端行数增加但看不到内容 ### 根本原因 1. **PTY 配置不正确**:使用了 `xterm-color` 而不是 `xterm-256color` 2. **缺少环境变量**:没有设置 `TERM` 环境变量 3. **写入未确认**:没有检查 `pty.write()` 的返回值 ### 修复方案 #### 1. 改进 PTY 配置 **修改前:** ```typescript const ptyProcess = spawn(shell, [], { name: "xterm-color", // ❌ 错误的终端类型 cols, rows, cwd, env, }); ``` **修改后:** ```typescript // 确保环境变量中包含 TERM const ptyEnv = { ...env, TERM: env.TERM || "xterm-256color", // ✅ 正确的终端类型 LANG: env.LANG || "en_US.UTF-8", // ✅ 确保编码正确 PAGER: env.PAGER || "cat", // ✅ 避免分页器干扰 }; const ptyProcess = spawn(shell, [], { name: "xterm-256color", // ✅ 使用正确的终端类型 cols, rows, cwd, env: ptyEnv, encoding: "utf8", // ✅ 启用 UTF-8 编码 }); ``` #### 2. 改进写入逻辑 **修改前:** ```typescript const inputToWrite = needsNewline ? input + "\n" : input; ptyProcess.write(inputToWrite); // ❌ 没有检查返回值 ``` **修改后:** ```typescript const inputToWrite = needsNewline ? input + "\n" : input; // 写入数据到 PTY const written = ptyProcess.write(inputToWrite); // 如果写入失败(返回 false),等待 drain 事件 if (written === false) { await new Promise<void>((resolve) => { const onDrain = () => { ptyProcess.off("drain", onDrain); resolve(); }; ptyProcess.on("drain", onDrain); // 设置超时,避免永久等待 setTimeout(() => { ptyProcess.off("drain", onDrain); resolve(); }, 5000); }); } // 给 PTY 一点时间处理输入 await new Promise((resolve) => setImmediate(resolve)); ``` ### 测试结果 ```bash ✅ 测试通过:输出包含 "Hello World" ✅ 测试通过:输出包含命令回显 ``` --- ## 问题 2: 交互式输入处理修复 ✅ ### 问题描述 - 发送方向键等控制字符时界面不更新 - 需要多次发送同一个按键 - 按回车确认时没有反应 ### 根本原因 1. **终端类型不正确**:`xterm-color` 不支持完整的 ANSI 转义序列 2. **环境变量缺失**:没有设置 `TERM` 环境变量 3. **编码问题**:没有明确指定 UTF-8 编码 ### 修复方案 通过正确配置 PTY 环境(见问题 1 的修复),交互式应用现在可以: - ✅ 正确处理 ANSI 转义序列 - ✅ 支持方向键、回车等控制字符 - ✅ 实时更新交互式界面 ### 支持的交互式应用 修复后支持以下交互式应用: - `npm create vite` - 项目脚手架 - `vim` / `nano` - 文本编辑器 - `less` / `more` - 分页器 - `htop` - 进程监控 - 任何使用 ANSI 转义序列的应用 --- ## 问题 3: 输出读取实时性修复 ✅ ### 问题描述 - 读取到的输出是旧的 - 需要多次读取才能看到最新输出 - 无法判断命令是否还在执行 ### 根本原因 1. **事件处理延迟**:`onData` 事件中的数据没有立即处理 2. **读取时机问题**:读取时可能数据还在事件队列中 3. **缺少状态检测**:无法判断输出是否稳定 ### 修复方案 #### 1. 改进输出捕获 **修改前:** ```typescript ptyProcess.onData((data: string) => { session.lastActivity = new Date(); outputBuffer.append(data); this.emit("terminalOutput", terminalId, data); }); ``` **修改后:** ```typescript ptyProcess.onData((data: string) => { // 使用 setImmediate 确保数据立即被处理 setImmediate(() => { session.lastActivity = new Date(); outputBuffer.append(data); this.emit("terminalOutput", terminalId, data); }); }); ``` #### 2. 改进读取逻辑 **修改后:** ```typescript async readFromTerminal(options: TerminalReadOptions): Promise<TerminalReadResult> { // ... 参数处理 ... // 给一个很小的延迟,确保 onData 事件中的数据已经被处理 await new Promise(resolve => setImmediate(resolve)); // ... 读取逻辑 ... } ``` #### 3. 新增:等待输出稳定功能 添加了新的方法和 MCP 工具来等待输出稳定: ```typescript /** * 等待终端输出稳定 */ async waitForOutputStable( terminalId: string, timeout: number = 5000, stableTime: number = 500 ): Promise<void> { const session = this.sessions.get(terminalId); if (!session) { throw new Error(`Terminal ${terminalId} not found`); } const startTime = Date.now(); let lastActivityTime = session.lastActivity.getTime(); while (Date.now() - startTime < timeout) { const currentActivityTime = session.lastActivity.getTime(); // 如果输出已经稳定(在 stableTime 内没有新输出) if (Date.now() - currentActivityTime > stableTime) { return; } // 如果有新的活动,更新时间 if (currentActivityTime > lastActivityTime) { lastActivityTime = currentActivityTime; } // 等待一小段时间再检查 await new Promise(resolve => setTimeout(resolve, 100)); } } ``` #### 4. 新增 MCP 工具:`wait_for_output` ```typescript { name: 'wait_for_output', description: 'Wait for terminal output to stabilize', parameters: { terminalId: string, timeout?: number, // 默认 5000ms stableTime?: number // 默认 500ms } } ``` ### 使用示例 ```javascript // 1. 发送命令 await writeTerminal({ terminalId: "xxx", input: "npm install", }); // 2. 等待输出稳定 await waitForOutput({ terminalId: "xxx", timeout: 10000, // 最多等待 10 秒 stableTime: 1000, // 1 秒内没有新输出就认为稳定 }); // 3. 读取输出 const output = await readTerminal({ terminalId: "xxx", }); // 现在可以确保读取到完整的输出 ``` ### 测试结果 ```bash ✅ 测试通过:输出实时更新 ✅ 测试通过:等待输出稳定功能正常 ``` --- ## 新增功能 ### 1. `wait_for_output` MCP 工具 **用途:** 等待终端输出稳定后再继续操作 **参数:** - `terminalId` (必需) - 终端 ID - `timeout` (可选) - 最大等待时间(毫秒),默认 5000 - `stableTime` (可选) - 稳定时间(毫秒),默认 500 **使用场景:** - 执行长时间运行的命令后 - 需要确保获取完整输出时 - 交互式应用响应后 ### 2. 改进的终端环境 **新增环境变量:** - `TERM=xterm-256color` - 支持完整的 ANSI 转义序列 - `LANG=en_US.UTF-8` - 确保 UTF-8 编码 - `PAGER=cat` - 避免分页器干扰 --- ## 测试验证 ### 运行测试 ```bash # 运行修复测试 node test-terminal-fixes.mjs ``` ### 测试覆盖 1. ✅ **基本命令执行** - 验证命令能正确执行并获取输出 2. ✅ **多个命令执行** - 验证连续执行多个命令 3. ✅ **原始输入** - 验证 `appendNewline: false` 功能 4. ✅ **输出实时读取** - 验证输出能实时更新 5. ✅ **终端环境配置** - 验证 TERM 等环境变量正确设置 ### 测试结果 ``` ================================================================================ 测试结果汇总 ================================================================================ 通过: 6 失败: 0 ✅ 所有测试通过! ``` --- ## 最佳实践 ### 1. 执行命令的推荐流程 ```javascript // 1. 发送命令 await writeTerminal({ terminalId, input: "npm install", }); // 2. 等待输出稳定(推荐) await waitForOutput({ terminalId, timeout: 30000, // npm install 可能需要较长时间 stableTime: 1000, // 1 秒内没有新输出 }); // 3. 读取输出 const output = await readTerminal({ terminalId }); ``` ### 2. 交互式应用的使用 ```javascript // 1. 启动交互式应用 await writeTerminal({ terminalId, input: "npm create vite@latest my-app", }); // 2. 等待提示出现 await waitForOutput({ terminalId, stableTime: 500 }); // 3. 读取当前状态 const output1 = await readTerminal({ terminalId }); // 4. 发送控制字符(不添加换行) await writeTerminal({ terminalId, input: "j", // 向下移动 appendNewline: false, }); // 5. 等待界面更新 await waitForOutput({ terminalId, stableTime: 200 }); // 6. 确认选择 await writeTerminal({ terminalId, input: "\n", appendNewline: false, }); ``` ### 3. 处理长时间运行的命令 ```javascript // 对于可能运行很长时间的命令 await writeTerminal({ terminalId, input: "npm run build", }); // 可以定期检查输出 for (let i = 0; i < 10; i++) { await sleep(2000); const output = await readTerminal({ terminalId }); console.log(`进度检查 ${i + 1}:`, output.totalLines, "行"); } // 最后等待稳定 await waitForOutput({ terminalId, timeout: 60000, stableTime: 2000, }); ``` --- ## 修改的文件 1. **src/terminal-manager.ts** - 改进 PTY 配置和环境变量 - 改进 `writeToTerminal` 方法 - 改进 `readFromTerminal` 方法 - 新增 `waitForOutputStable` 方法 - 新增 `isTerminalBusy` 方法 2. **src/mcp-server.ts** - 新增 `wait_for_output` MCP 工具 3. **测试文件** - `test-terminal-fixes.mjs` - 验证修复的测试脚本 --- ## 向后兼容性 ✅ **完全向后兼容** - 所有现有 API 保持不变 - 只是改进了内部实现 - 新增的功能是可选的 --- ## 总结 本次修复解决了终端交互的三个核心问题: 1. ✅ **命令执行可靠** - 通过正确配置 PTY 和改进写入逻辑 2. ✅ **交互式应用支持** - 通过设置正确的终端类型和环境变量 3. ✅ **输出实时准确** - 通过改进事件处理和新增等待机制 现在 persistent-terminal MCP 服务器可以: - 可靠地执行任何 shell 命令 - 支持交互式应用(vim、npm create 等) - 实时准确地捕获输出 - 提供输出稳定性检测 **所有测试通过,可以投入生产使用!** 🎉

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/masx200/persistent-terminal-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server