内容
MCP 运行器
我在这里编写了一个 Model Context Protocol (MCP) 服务器,它封装了长时间运行的命令,允许代理启动、交互和监控进程,如 HTTP 服务器、调试器或复杂的编译任务。
## 功能
- **多个并发会话**:同时运行多个命令,每个命令都有一个唯一的会话 ID
- **持久化日志记录**:所有输出都记录到 `/tmp` 文件中
- **增量输出读取**:仅返回自上次读取以来新的输出
- **灵活的 stderr 处理**:将 stderr 与 stdout 组合或保持它们分开
- **自动清理**:当进程退出并且所有输出都被消费时,会话自动被清理
- **HTTP 监控**:在 localhost:8089 上的 Web 接口,用于查看活动会话及其输出
- **原始字节输入**:通过字节数组发送控制字符(Ctrl-A、Ctrl-X 等)
- **响应等待**:在发送输入后阻塞并收集输出,直到空闲
- **MCP 诱导**:直接向用户提示密码/秘密,而不将它们暴露给 LLM
- **MCP 日志记录**:通过 MCP 日志通知(行缓冲,ANSI 剥离)选择加入进程输出的实时日志流
- **MCP 进度**:在 `await_response_ms` 阻塞期间进行进度通知(当客户端提供进度令牌时)
- **MCP 提示**:picocom、GDB 和 Black Magic Probe 工作流的内置指南
## 安装
### 从 GitHub(推荐)
```bash
cargo install --git https://github.com/murphysean/mcp-runner.git
```
### 从源代码构建
```bash
git clone https://github.com/murphysean/mcp-runner.git
cd mcp-runner
cargo build --release
```
二进制文件位于 `target/release/mcp-runner`
## 用法
### Claude 代码
在 `~/.claude/settings.json` 中添加您的 Claude 代码配置:
```json
{
"mcpServers": {
"runner": {
"command": "/path/to/mcp-runner"
}
}
}
```
或在项目目录中使用项目特定的配置 `.claude/settings.json`。
### Kiro
在 Kiro 中,导航到 **设置 > MCP 服务器** 并添加:
```json
{
"runner": {
"command": "/path/to/mcp-runner"
}
}
```
或者,创建或编辑 `~/.kiro/mcp.json` 中的 MCP 配置文件:
```json
{
"mcpServers": {
"runner": {
"command": "/path/to/mcp-runner"
}
}
}
```
### 其他 MCP 客户端
在您的 MCP 客户端配置中添加:
```json
{
"mcpServers": {
"runner": {
"command": "/path/to/mcp-runner"
}
}
}
```
### 可用工具
#### `start_command`
启动一个新的命令会话。
**参数:**
- `command`(字符串,必填):要执行的命令
- `args`(字符串数组,可选):命令参数
- `split_stderr`(布尔值,可选):如果为 true,则将 stderr 与 stdout 分开(默认值:false)
- `use_pty`(布尔值,可选):在伪终端内启动(对于交互式程序如 picocom、gdb TUI 等是必需的)
- `stream_log`(布尔值,可选):通过 MCP 日志通知将进程输出实时流式传输到客户端日志。stdout 行在 `info` 级别发送,stderr 在 `warning` 级别发送。记录器名称为 `session/{id}/stdout` 或 `session/{id}/stderr`。默认值:false。
**返回:** 会话 ID
**示例:**
```json
{
"command": "python3",
"args": ["-m", "http.server", "8000"],
"split_stderr": false
}
```
#### `send_input`
向正在运行的命令的 stdin 发送输入。支持文本、原始字节和 MCP 诱导密码。
**参数:**
- `session_id`(字符串,必填):会话 ID
- `input`(字符串,可选):要发送的文本。自动追加换行符/回车符,并带有正确的行尾(管道 vs PTY)。修剪尾部空格后追加回车符。
- `bytes`(u8 数组,可选):要发送的原始字节(例如 `[1, 24]` 表示 Ctrl-A Ctrl-X)。不自动追加回车符。
- `no_enter`(布尔值,可选):如果为 true,则不修改输入字符串直接发送。使用部分输入、选项卡补全等。
- `elicit`(布尔值,可选):如果为 true,则通过 MCP 诱导直接提示用户(密码从不触及 LLM)。自动追加回车符。
- `elicit_message`(字符串,可选):诱导的自定义提示消息
- `await_response_ms`(u64,可选):阻塞并收集输出,直到在这么多毫秒内没有新数据到达
必须提供 `input`、`bytes` 或 `elicit: true` 之一。如果存在 `input`,则优先于 `bytes`。`await_response_ms` 与任何输入模式组合使用。
当 `await_response_ms` 活动且客户端在请求中提供 `_meta.progressToken` 时,每秒发送进度通知,附带经过的时间和收集的字节。
**自动回车行为:**
使用 `input` 时,自动追加回车符 — 只需发送命令文本。根据会话类型(`\n` 表示管道,`\r\n` 表示 PTY)选择正确的行尾。任何尾部空格(包括您可能添加的 `\n` 或 `\r\n`)都会被修剪,因此 `"ls"`、`"ls\n"` 和 `"ls\r\n"` 的行为相同。
要抑制自动回车,设置 `no_enter: true`。`bytes` 参数从不自动追加回车符。
**示例:**
```json
{"session_id": "1", "input": "print('hello')", "await_response_ms": 1000}
```
```json
{"session_id": "1", "bytes": [1, 24]}
```
```json
{"session_id": "1", "elicit": true, "elicit_message": "Enter the sudo password"}
```
#### `send_signal`
向正在运行的命令发送 Unix 信号(例如,终止 gdb)。
**参数:**
- `session_id`(字符串,必填):会话 ID
- `signal`(字符串,必填):信号名称(SIGINT、SIGTERM、SIGKILL、SIGSTOP、SIGCONT、SIGHUP、SIGQUIT)
**注意:** 仅在 Unix 系统上支持。
#### `read_output`
读取自上次读取以来新的 stdout 数据。
**参数:**
- `session_id`(字符串,必填):会话 ID
- `strip_ansi`(布尔值,可选):从输出中剥离 ANSI 转义序列(默认值:true)
**返回:** 新的输出文本(默认情况下剥离 ANSI)
#### `read_stderr`
读取自上次读取以来新的 stderr 数据(仅当 `split_stderr` 为 true 时)。
**参数:**
- `session_id`(字符串,必填):会话 ID
- `strip_ansi`(布尔值,可选):从输出中剥离 ANSI 转义序列(默认值:true)
**返回:** 新的 stderr 文本(默认情况下剥离 ANSI)
#### `stop_command`
停止正在运行的命令。
**参数:**
- `session_id`(字符串,必填):会话 ID
#### `delete_session`
删除会话并清理其日志文件。如果进程仍在运行,则首先停止它。
**参数:**
- `session_id`(字符串,必填):会话 ID
#### `get_status`
获取命令会话的状态。
**参数:**
- `session_id`(字符串,必填):会话 ID
**返回:** 运行状态和退出代码(如果已完成)
### HTTP 端点
MCP 运行器在 localhost:8089 上提供 HTTP 接口用于监控会话:
- `GET /` — 列出所有会话及其状态和链接
- `GET /session/{id}/stdout` — 查看当前 stdout 内容
- `GET /session/{id}/stderr` — 查看当前 stderr 内容
- `GET /session/{id}/stdout/stream` — stdout 的 SSE 流(类似 `tail -f`)
- `GET /session/{id}/stderr/stream` — stderr 的 SSE 流(类似 `tail -f`)
- `GET /session/{id}/stdout/follow` — 实时 HTML 页面,带有自动滚动的 SSE 输出
- `GET /session/{id}/stderr/follow` — 实时 HTML 页面,带有自动滚动的 SSE 输出
- `GET /session/{id}/input` — HTML 表单,向进程发送文本输入
- `POST /session/{id}/input` — 向进程提交文本输入
- `GET /session/{id}/password` — HTML 表单,发送密码输入(掩码字段)
- `POST /session/{id}/password` — 向进程提交密码输入
- `DELETE /session/{id}` — 删除会话
#### ANSI 转义码处理
HTTP 端点以三种模式处理 ANSI 转义码(颜色、粗体等):
| 查询参数 | 行为 |
|-------------|----------|
| *(默认)* | 将 ANSI 转换为样式化的 HTML(颜色、粗体等) |
| `?raw=1` | 保持 ANSI 代码原样 |
| `?strip=1` | 剥离 ANSI 代码,纯文本 |
示例:
- `GET /session/1/stdout` — 带有样式输出的 HTML
- `GET /session/1/stdout?raw=1` — 带有 ANSI 代码的原始输出
- `GET /session/1/stdout?strip=1` — 不带 ANSI 的纯文本
对于 SSE 流,默认情况下会将 ANSI 转换为 HTML。使用 `?raw=1` 保持 ANSI 代码或使用 `?strip=1` 获取纯文本:
- `GET /session/1/stdout/stream` — 转换为 HTML 的输出
- `GET /session/1/stdout/stream?raw=1` — 带有 ANSI 代码的原始输出
- `GET /session/1/stdout/stream?strip=1` — 不带 ANSI 的纯文本
#### SSE 流式传输
`/session/{id}/stdout/stream` 和 `/session/{id}/stderr/stream` 端点使用服务器发送事件(SSE)实时流式传输输出:
```
id: 1
data: 你好,世界!
id: 2
data: 另一行
event: done
data: [进程退出]
```
- 每个事件都有一个 `id` 字段,包含 1 为基准的行号(匹配编辑器行号)
- `data` 字段包含一行完整的输出
- 不完整的行(尚未以换行符结束)被保留,直到完整
- 当进程退出时,发送最终的 `event: done` 消息
**从最后一行恢复:** 包含 `Last-Event-ID` 头部,带有接收到的最后一行行号bash
curl -H "Last-Event-ID: 42" http://localhost:8089/session/1/stdout/stream
```
**JavaScript 示例:**
```javascript
const source = new EventSource('/session/1/stdout/stream');
source.onmessage = (e) => console.log(`行 ${e.lastEventId}:${e.data}`);
source.addEventListener('done', () => source.close());
```
### 可用提示
#### `picocom_guide`
使用 picocom 串行终端的指南。涵盖连接到设备、读取输出和使用原始字节控制序列(Ctrl-A Ctrl-X = `[1, 24]`)正常退出。
#### `gdb_guide`
通过命令包装器使用 GDB 的指南。涵盖启动 GDB、发送带有 `await_response_ms` 的命令、中断执行并使用 SIGINT 以及常见的调试命令。
#### `blackmagic_probe_guide`
使用 Black Magic Probe 进行设备调试的指南。涵盖探针发现、通过 `target extended-remote` 连接、SWD 扫描、闪存、调试和在并行 picocom 会话中监控 UART 输出。
## 示例工作流
1. **启动 Web 服务器:**
```
工具:start_command
参数:{"command": "python3", "args": ["-m", "http.server", "8000"]}
返回:session_id: "1"
```
2. **检查输出:**
```
工具:read_output
参数:{"session_id": "1"}
返回:"在 0.0.0.0 端口 8000 上提供 HTTP 服务..."
```
3. **检查状态:**
```
工具:get_status
参数:{"session_id": "1"}
返回:{"running": true, "exit_code": null}
```
4. **停止服务器:**
```
工具:stop_command
参数:{"session_id": "1"}
```
## 实现细节
- 日志文件在 `/tmp/mcp_cmd_<session_id>_stdout.log` 和 `_stderr.log` 中创建
- 读取位置在每个会话中内存跟踪
- 会话在进程退出后持续存在,以便可以查看输出;使用 `delete_session` 清理
- 当 MCP 服务器关闭时,所有会话都会终止
## 待办事项
- [x] **HTTP 流式传输**:用于实时 stdout/stderr 流式传输的 SSE 端点(`/session/{id}/stdout/stream`,`/session/{id}/stderr/stream`)
- [x] **HTTP 跟随页面**:显示 SSE 流的实时 HTML 页面(`/session/{id}/stdout/follow`,`/session/{id}/stderr/follow`)
- [x] **MCP 资源**:将会话 stdout/stderr 暴露为可订阅的 MCP 资源(`session://1/stdout`)。通过 `notify_resource_updated` 推送更新。
- [x] **MCP 日志通知**:通过 `notify_logging_message` 将进程输出实时流式传输到客户端日志,以便代理可以被动监视长时间运行的构建或服务器,而无需调用 `read_output`。
- [x] **进度通知**:在 `await_response_ms` 阻塞期间发送 `notify_progress` 更新,以便客户端可以显示经过的时间或解析的构建进度。
## 许可证
MIT