内容
# 交互式进程管理
<p align="center">
<strong>为 AI 代理提供交互式终端能力,现已迁移至 [termcp](https://github.com/open-mcp-ai/termcp)</strong>
</p>
<p align="center">
<img src="https://img.shields.io/badge/Go-1.21+-00ADD8.svg" alt="Go 1.21+">
<img src="https://img.shields.io/badge/Platform-macOS%20%7C%20Linux-lightgrey" alt="macOS / Linux">
<img src="https://img.shields.io/badge/MCP-SSE_Transport-green.svg" alt="MCP SSE">
<img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="MIT License">
</p>
<p align="center">
<a href="https://linux.do/"><img src="https://img.shields.io/badge/🐧-linux.do-ff69b4.svg" alt="linux.do"></a>
<a href="./README.zh.md"><img src="https://img.shields.io/badge/🌏-中文-blue.svg" alt="中文"></a>
</p>
<p align="center">
<a href="./README.zh.md">中文</a> | <strong>English</strong>
</p>
---
## 简介
`interactive-process-mcp` 是一个 MCP(模型上下文协议)服务器,旨在为 AI 代理(如 Claude Code)提供启动、控制和管理**长时间运行的交互式进程**的能力。
### 为什么需要它?
AI 代理通常只能执行一次性的命令——它们运行后立即返回结果。但许多现实场景需要**多轮交互**:
- 登录到远程服务器,首先输入密码,然后运行命令
- 在 Python REPL 中逐行调试代码
- 在交互式安装程序中回答 `[Y/n]` 提示
- 使用终端依赖命令,如 `top`、`htop`
- 运行安全工具(例如 impacket)进行多步骤操作
在这些场景中,进程持续运行,AI 代理需要**重复读取和写入**进程的 I/O 流,跨越**多个对话轮次**。`interactive-process-mcp` 正是为此目的而设计的桥梁。
### 核心功能
| 功能 | 描述 |
|---------|-------------|
| **多代理会话共享** | 多个 AI 代理同时读取同一个会话,每个代理都有独立的光标——不会出现输出抢占 |
| **PTY 和 Pipe 双模式** | PTY 模式模拟真实终端;Pipe 模式适用于简单的 stdin/stdout 交互 |
| **远程部署** | 通过 HTTP 传输的 SSE——代理和服务器可以在不同机器上运行 |
| **多会话管理** | 同时管理多个独立进程,无干扰 |
| **消息持久化** | 会话记录和 I/O 消息持久化到本地 JSON 文件 |
| **ANSI 转义码剥离** | 可选的自动移除终端控制序列,输出干净的文本 |
| **带超时阻塞读取** | 代理等待新输出,直到可配置的超时时间;通过 sync.Cond 及时返回 |
| **原子发送和读取** | `send_and_read` 将发送 + 读取合并为一步 |
| **温和终止** | 先发送 SIGTERM,然后在可配置的宽限期后发送 SIGKILL |
| **PTY 调整大小** | 在运行时动态调整终端行数和列数 |
| **会话清理** | 删除已退出的会话,防止资源累积 |
---
## 架构
```
┌──────┐ SSE/HTTP ┌──────────────┐ Internal SSH ┌──────────┐
│代理 │ ──────────> │ Go 服务器 │ ──────────────> │ PTY/ │
│(MCP)│ │ - MCP API │ (本地) │ 进程 │
└──────┘ │ - SSH 服务器 │ └──────────┘
└──────────────┘
│
▼
┌──────────────┐
│ JSON 存储 │
│ - 会话 │
│ - 消息 │
└──────────────┘
```
### 项目结构
```
.
├── cmd/server/main.go # 入口点
├── internal/
│ ├── config/config.go # 配置文件校验
│ ├── mcp/
│ │ ├── server.go # MCP SSE 服务器 & 工具注册
│ │ └── handlers.go # 13 个工具处理器
│ ├── sshserver/server.go # 内部 SSH 服务器(gliderlabs/ssh)
│ ├── sshclient/client.go # 内部 SSH 客户端(crypto/ssh)
│ ├── session/
│ │ ├── session.go # 会话生命周期(协程安全)
│ │ └── manager.go # 线程安全的会话注册表
│ ├── buffer/buffer.go # 多读取器环形缓冲区(每个读取器 1MB)
│ ├── storage/store.go # 原子 JSON 文件持久化
│ ├── message/message.go # 消息管理(每个会话的互斥锁)
│ └── ansi/strip.go # ANSI 转义码移除
├── pkg/api/types.go # 公共类型(会话、消息、会话模式)
├── go.mod
└── go.sum
```
### 关键设计决策
1. **多读取器环形缓冲区**:每个代理注册为独立的读取器,拥有自己的 `ringbuffer.RingBuffer` 实例。写入广播给所有读取器。慢速读取器会丢失最旧的数据(覆盖模式),而不是阻塞写入者。
2. **内部 SSH 架构**:服务器在本地启动 gliderlabs/SSH 服务器。每个 `start_process` 通过 crypto/ssh 客户端创建 SSH 会话,利用 SSH 成熟的 PTY 分配、窗口调整、信号转发和环境变量传递。
3. **HTTP 传输上的 SSE**:与传统的基于 stdio 的 MCP 服务器不同,该服务器公开了一个支持 MCP SSE 传输的 HTTP 端点。代理可以远程连接,实现跨机器部署。
4. **原子 JSON 持久化**:会话元数据和 I/O 消息通过临时文件 + fsync + 重命名存储,防止崩溃时出现半写入文件:
- `data/sessions.json` — 会话列表
- `data/messages/{session_id}/index.json` — 消息索引
- `data/messages/{session_id}/messages/{msg_id}.json` — 消息内容
5. **会话生命周期安全**:退出协程是 `Status`/`ExitCode` 的唯一权限来源(通过 `sync.Once`)。终止是幂等的。Stdin 写入通过专用互斥锁序列化。
---
## 示例
### 示例 1:SSH 远程操作
```
AI 代理流程 进程输出
───────────────── ────────────────
start_process(
命令="ssh",
参数=["deploy@192.168.1.100"],
模式="pty"
)
← "deploy@192.168.1.100's password: "
send_and_read(
文本="my_secret_pass",
press_enter=true
)
← "Welcome to Ubuntu 22.04 LTS
deploy@web-server:~$ "
send_and_read(
文本="df -h",
press_enter=true
)
← "Filesystem Size Used Avail Use% Mounted on
/dev/sda1 100G 45G 55G 45% /
deploy@web-server:~$ "
terminate_process(session_id="abc123")
```
### 示例 2:Python REPL 调试
```
start_process(command="python3", mode="pty")
← "Python 3.10.12\n>>> "
send_and_read(text="data = [1, 2, 3, 4, 5]", press_enter=true)
← ">>> "
send_and_read(text="sum(data)", press_enter=true)
← "15\n>>> "
```
### 示例 3:多代理协作
```
# 代理 A 启动监控进程
start_process(command="top", mode="pty")
→ session_id: "sess-001"
# 代理 B 加入同一会话,不会抢占输出
register_reader(session_id="sess-001")
→ reader_id: 2
# 代理 A 读取自己的光标
read_output(session_id="sess-001", reader_id=1)
→ "PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND..."
# 代理 B 独立读取从头开始
read_output(session_id="sess-001", reader_id=2)
→ "top - 14:32:10 up 3 days, 2:15, 1 user, load average: 0.52, 0.58, 0.59..."
# 代理 B 完成任务
unregister_reader(session_id="sess-001", reader_id=2)
# 代理 A 终止会话
terminate_process(session_id="sess-001")
delete_session(session_id="sess-001")
```
### 示例 4:多会话并行管理
```
start_process(command="ping", args=["-c", "5", "google.com"], name="ping-test")
→ session_id: "a1b2c3"
start_process(command="python3", args=["-m", "http.server", "8080"], name="web-server")
→ session_id: "d4e5f6"
list_sessions()
→ [{id: "a1b2c3", status: "running"}, {id: "d4e5f6", status: "running"}]
read_output(session_id="a1b2c3") → ping 统计信息
terminate_process(session_id="a1b2c3")
terminate_process(session_id="d4e5f6")
```
---
## 工具参考
### `start_process`
启动交互式进程。
| 参数 | 类型 | 必需 | 默认值 | 描述 |
|-----------|------|----------|---------|-------------|
| `command` | 字符串 | 是 | — | 要执行的命令 |
| `args` | 字符串数组 | 否 | `[]` | 命令参数 |
| `mode` | "pty" \| "pipe" | 否 | `"pty"` | I/O 模式 |
| `name` | 字符串 | 否 | 自动生成 | 会话名称 |
| `rows` | 整数 | 否 | `24` | PTY 行数(1–1000) |
| `cols` | 整数 | 否 | `80` | PTY 列数(1–1000) |
返回: `{ session_id, pid, initial_output }`
### `send_input`
向进程发送文本。
| 参数 | 类型 | 必需 | 默认值 | 描述 |
|-----------|------|----------|---------|-------------|
| `session_id` | 字符串 | 是 | — | 会话 ID |
| `text` | 字符串 | 是 | — | 要发送的文本 |
| `press_enter` | 布尔值 | 否 | `false` | 是否追加换行符 |
### `read_output`
读取给定读取器自上次读取以来新的输出。
| 参数 | 类型 | 必需 | 默认值 | 描述 |
|-----------|------|----------|---------|-------------|
| `session_id` | 字符串 | 是 | — | 会话 ID |
| `reader_id` | 整数 | 否 | `0` | 读取器 ID(0 = 默认) |
| `strip_ansi` | 布尔值 | 否 | `true` | 剥离 ANSI 转义码 |
| `timeout` | 数字 | 否 | `5` | 等待时间(秒)(0.1–60) |
| `max_lines` | 整数 | 否 | `0` | 最大行数(0 = 无限制) |
返回: `{ output, has_more, lines_returned, bytes_returned }`
### `send_and_read`
原子操作:发送输入 + 等待 + 读取输出。参数是 `send_input` 和 `read_output` 的并集。
### `list_sessions`
列出所有会话。返回: `{ sessions: [...] }`
### `get_session_info`
获取会话详细信息。返回: `{ id, name, command, args, mode, status, exit_code, pid, created_at }`
### `terminate_process`
终止进程。
| 参数 | 类型 | 必需 | 默认值 | 描述 |
|-----------|------|----------|---------|-------------|
| `session_id` | 字符串 | 是 | — | 会话 ID |
| `force` | 布尔值 | 否 | `false` | 直接使用 SIGKILL |
| `grace_period` | 数字 | 否 | `5` | SIGTERM 后等待(0–60) |
### `delete_session`
从注册表中移除已退出的会话。
| 参数 | 类型 | 必需 | 默认值 | 描述 |
|-----------|------|----------|---------|-------------|
| `session_id` | 字符串 | 是 | — | 会话 ID |
### `resize_pty`
调整 PTY 尺寸(仅 PTY 模式)。
| 参数 | 类型 | 必需 | 默认值 | 描述 |
|-----------|------|----------|---------|-------------|
| `session_id` | 字符串 | 是 | — | 会话 ID |
| `rows` | 整数 | 否 | `24` | 行数 |
| `cols` | 整数 | 否 | `80` | 列数 |
### `register_reader`
为会话注册新的独立读取器。
| 参数 | 类型 | 必需 | 默认值 | 描述 |
|-----------|------|----------|---------|-------------|
| `session_id` | 字符串 | 是 | — | 会话 ID |
返回: `{ reader_id }`
### `unregister_reader`
注销读取器以释放资源。
| 参数 | 类型 | 必需 | 默认值 | 描述 |
|-----------|------|----------|---------|-------------|
| `session_id` | 字符串 | 是 | — | 会话 ID |
| `reader_id` | 整数 | 是 | — | 读取器 ID |
### `list_messages`
列出会话的消息索引。
| 参数 | 类型 | 必需 | 默认值 | 描述 |
|-----------|------|----------|---------|-------------|
| `session_id` | 字符串 | 是 | — | 会话 ID |
返回: `{ messages: [{id, type, created_at, byte_size}, ...] }`
### `get_message`
获取一个或多个消息的内容。
| 参数 | 类型 | 必需 | 默认值 | 描述 |
|-----------|------|----------|---------|-------------|
| `session_id` | 字符串 | 是 | — | 会话 ID |
| `message_ids` | 字符串数组 | 否 | — | 要检索的消息 ID |
返回: `{ messages: [{id, session_id, type, content, created_at, byte_size}, ...] }`
---
## 安装
### 从源码构建
```bash
go build -o server ./cmd/server
```
**要求:** Go >= 1.21 / macOS 或 Linux
### 运行
```bash
./server --host 127.0.0.1 --port 8080 --data-dir ./data
```
选项:
| 参数 | 默认值 | 描述 |
|------|---------|-------------|
| `--host` | `127.0.0.1` | HTTP 服务器主机 |
| `--port` | `8080` | HTTP 服务器端口 |
| `--data-dir` | `./data` | JSON 存储目录 |
| `--ssh-host` | `127.0.0.1` | 内部 SSH 服务器主机 |
| `--ssh-port` | `0`(随机) | 内部 SSH 服务器端口 |
## 配置
### Claude Code
在 `.claude/settings.json` 或 `.mcp.json` 中:
```json
{
"mcpServers": {
"interactive-process": {
"type": "sse",
"url": "http://your-server:8080/sse"
}
}
}
```
或通过 CLI:
```bash
claude mcp add --transport sse interactive-process http://localhost:8080/sse
```
### 其他 MCP 客户端
任何支持 SSE 传输的 MCP 客户端都可以连接到 `http://<host>:<port>/sse`。
---
## 社区
- [linux.do](https://linux.do/) — 中文科技社区
---
## 许可证
MIT