终端会话管理服务器

pty-mcp是一个MCP服务器,用于管理本地PTY会话和通过SSH支持的远程工作流,支持跨多次调用的持久终端和SSH状态管理。

  • by observerw
  • Rust
  • 本地部署
  • 15 星标
  • 更新于
在 GitHub 查看 observerw/pty-mcp

使用说明

PTY MCP

pty-mcp 是一个MCP服务器,用于通过stdio管理本地PTY会话和SSH支持的远程工作流。它适用于需要持久终端和SSH状态而不是一次性执行shell的MCP客户端(Claude Code、Codex、OpenCode等)。

随着 pty-mcp,客户可以:

  • 启动一次终端会话,并在多个呼叫中继续使用它
  • 增量读取缓冲输出,而不是在命令之间丢失进程状态
  • 将后续输入发送到同一本地或远程shell
  • 通过一个MCP界面管理SSH连接、远程会话、远程文件和远程目录
  • 在本地挂载远程项目,并将本地编辑与远程执行相结合
  • 公开装载设置资源,以便代理可以引导用户完成本地FUSE/sshfs 安装支架缺失时的安装

这使得开发服务器、监视任务、远程调试和近本地远程开发等工作流更容易驱动。

安装

使用货物:

cargo install pty-mcp

使用Homebrew:

brew tap observerw/tap
brew install observerw/tap/pty-mcp

来源:

cargo build --release

二进制文件将在 target/release/pty-mcp.

用法

将MCP服务器添加到Codex配置中:

[mcp_servers.pty]
command = "pty-mcp"

如果你想运行本地构建的二进制文件:

[mcp_servers.pty]
command = "/absolute/path/to/pty-mcp/target/release/pty-mcp"

服务器通过stdio进行通信,并从环境变量中读取配置。

典型工作流程

从MCP客户端的角度可以最好地理解这些流程:代理接收任务,决定它是否需要持久的终端状态、远程访问或挂载的工作区,然后继续与同一会话交互,而不是从每一轮开始。

场景:使本地开发进程在各个回合中保持活动状态

当代理需要启动以下内容时使用此选项 pnpm dev, cargo watch、测试监视器或REPL,然后稍后回来检查日志或发送更多输入。

flowchart LR
    A["Agent receives a local task<br/>Run a dev server / watcher / REPL"] --> B{"Does the task need process state<br/>to survive across turns?"}
    B -- "Yes" --> C["pty_spawn<br/>Start one persistent local shell/process"]
    C --> D["pty_read<br/>Inspect startup logs and current state"]
    D --> E{"Need to interact,<br/>retry, or provide more input?"}
    E -- "Yes" --> F["pty_write<br/>Send command, keystrokes, or stdin"]
    F --> D
    E -- "No, just observe" --> G{"Still running?"}
    G -- "Yes" --> D
    G -- "Finished / should stop" --> H["pty_wait or pty_kill"]

场景:交互式地调查或操作远程计算机

当代理需要一个可以返回的真实远程shell时,使用此选项,而不是在没有保留终端状态的情况下一次性执行SSH。

flowchart LR
    A["Agent receives a remote task<br/>Check logs / run deployment / debug service"] --> B["ssh_connect<br/>Establish or reuse SSH connection"]
    B --> C{"Need an interactive remote shell<br/>with persistent terminal state?"}
    C -- "Yes" --> D["ssh_session_spawn<br/>Create remote PTY session"]
    D --> E["pty_read<br/>Read remote output incrementally"]
    E --> F{"Need follow-up commands,<br/>answers, or shell input?"}
    F -- "Yes" --> G["pty_write<br/>Continue in the same remote session"]
    G --> E
    F -- "No" --> H{"Task complete?"}
    H -- "Not yet" --> E
    H -- "Yes" --> I["pty_kill if needed<br/>ssh_disconnect when done"]

场景:本地编辑,远程执行

当代理希望访问本地质量文件进行搜索和编辑,同时仍在远程主机上运行构建、测试或运行时命令时,请使用此选项。

flowchart LR
    A["Agent receives a remote code task"] --> B["ssh_connect"]
    B --> C{"Would local editing/search be easier<br/>if the remote project were mounted?"}
    C -- "Yes" --> D["ssh_mount<br/>Expose remote project as local files"]
    D --> E["Agent reads, searches, and edits mounted files locally"]
    E --> F["ssh_session_spawn or ssh_exec<br/>Run commands on the remote host"]
    F --> G["pty_read<br/>Inspect build/test/runtime output"]
    G --> H{"Need another edit or rerun?"}
    H -- "Yes" --> E
    H -- "No" --> I["ssh_unmount and ssh_disconnect"]

刀具表面

PTY工具

  • pty_spawn:启动本地PTY流程
  • pty_write:向正在运行的PTY会话发送输入
  • pty_read:通过保留的输出进行分页,可选地按正则表达式模式进行过滤
    • 返回单个 page.text 字符串而不是每行对象
  • pty_list:列出已知的PTY会话
  • pty_kill:停止PTY会话 sigint, sigterm,或 sigkill
  • pty_wait:等待PTY会话退出

输出模型

PTY读取和捕获的启动输出现在使用一个规范的页面形状:

  • offset
  • returned
  • has_more
  • total_lines
  • text

默认行为是代理优先和紧凑:

  • 使用 text 默认情况下没有行号
  • 请求 line_number_mode=embedded 只有当你需要确切的参考时
  • 嵌入的编号前缀为每个返回的行 <line_number>\t<text>
  • 不再返回每行对象数组
  • capture_limit 是唯一启用的开关 initial_output

pty_read 初始输出捕获支持三 output_view 值:

  • plain:ANSI剥离文本
  • ansi:ANSI保留文本
  • raw:原始缓冲区视图

line_number_mode 支持:

  • none:返回纯文本 text
  • embedded:将每个返回的行前缀为 <line_number>\t<text>

约束:

  • output_view=raw 不能与 line_number_mode=embedded

SSH工具

  • ssh_connect:创建或重用SSH连接句柄
    • 提供 host 或 host_alias
    • 必需的 auth_kind 值: ssh_agent, identity_file, config_alias
  • ssh_list:列出SSH连接和挂载
  • ssh_session_spawn:通过现有SSH连接启动远程PTY会话
    • 可选的 capture_wait_ms:返回前,请短暂等待初始远程PTY输出 initial_output
    • 可选的 capture_limit:限制捕获和包含多少远程PTY输出 initial_output
    • 可选的 output_view:选择捕获的格式 initial_output (plain, ansi,或 raw)
    • 可选的 line_number_mode:选择 none 或 embedded 为俘虏 initial_output.text
  • ssh_run:运行一次性远程脚本并返回 stdout, stderr,并直接退出状态
    • 可选的 max_output_bytes:cap组合捕获输出,默认值 262144
  • ssh_exec:在现有SSH连接上运行远程脚本
    • 当您希望将结果附加到PTY会话以供以后使用时,请使用此选项 pty_wait / pty_read
    • 可选的 wait_timeout_ms:短暂等待脚本完成,并内联返回完成状态和退出字段
    • 可选的 capture_limit:返回 initial_output 使用相同的紧凑页面模型
    • 如果脚本未在该窗口内完成,请使用 pty_wait 和 pty_read 与返回 session_id
  • ssh_read_file:从远程主机读取UTF-8文本文件
    • path 接受绝对路径, ~,或 ~/...
    • 可选的 max_bytes:允许范围 1..=524288,默认值 131072
  • ssh_write_file:将UTF-8文本文件写入远程主机
    • path 接受绝对路径, ~,或 ~/...
    • content 必须是UTF-8文本,并且上限为 262144 字节
  • ssh_list_dir:列出一个远程目录级别
    • path 接受绝对路径, ~,或 ~/...
  • ssh_mkdir:创建远程目录
    • path 接受绝对路径, ~,或 ~/...
  • ssh_mkdir.create_parents:根据需要创建父目录
  • ssh_mount:通过在本地挂载远程路径 sshfs
  • ssh_mount.target_path:本地装载目标路径
  • ssh_mount.create_target:在需要时创建本地装载目标目录
  • ssh_mount.remote_path:接受绝对路径, ~,或 ~/...;在挂载之前,通过SSH解析家庭相对输入,挂载响应/资源存储解析的绝对路径
  • ssh_unmount:卸载已装载的远程路径
  • ssh_unmount.cleanup_target:仅在允许清理时删除目标路径
  • ssh_disconnect:断开连接并可选择清理相关资源

SSH挂载要求

ssh_mount 取决于本地机器能否挂载远程文件系统。

要在本地使用它,您需要:

  • 已安装并可用的FUSE实现
  • sshfs 已安装并可用 PATH,或通过配置 PTY_MCP_SSHFS_BIN_PATH

在实践中:

  • macOS: macFUSE 和 sshfs
  • Linux: fuse 或 fuse3,加 sshfs

没有当地FUSE支持和 sshfsSSH连接和远程命令执行仍然可以工作,但是 ssh_mount 不会。

在macOS上, ssh_mount 还添加 sshfs 禁止AppleDouble文件的装载选项(._*)默认情况下,苹果扩展了属性。这有助于避免在常见情况下将这些形式的Apple元数据写回远程树中,但不会阻止Finder创建 .DS_Store 文件夹。

如果Finder或 cp 在复制到装载路径时报告元数据或权限错误,使用禁用元数据阻止装载行为 PTY_MCP_SSH_MACOS_BLOCK_APPLE_METADATA=false.

代理可以读取内置的装载设置资源 引导用户使用正确的FUSE/sshfs 安装步骤 对于当前的平台。

MCP资源

服务器还公开结构化资源:

  • pty://sessions
  • pty://sessions/{id}
  • pty://sessions/{id}/buffer
  • pty://sessions/{id}/tail
  • ssh://connections
  • ssh://connections/{id}
  • ssh://mounts
  • ssh://mounts/{id}
  • ssh://docs/mount-setup
  • ssh://docs/mount-setup/{platform}

当客户端需要快照而不调用工具时,这些功能非常有用。

SSH挂载设置指南适用于代理:当 sshfs/FUSE支持缺失 代理可以读取这些资源,然后引导用户完成正确的本地安装流程 对于当前的平台,而不是猜测。

运行时间要求

PTY 通常翻译为“伪终端”。

内置本地PTY支持。命令需接受以下策略检查:

  • 允许的工作目录根
  • 允许/拒绝命令
  • 允许/拒绝的环境变量

安全外壳协议

SSH功能取决于主机二进制文件:

  • ssh SSH连接和远程执行需要
  • sshfs 是必需的 ssh_mount
  • umount 用于卸载
  • diskutil 在macOS上额外探测

在macOS上,服务器也会进行探测 macFUSE / osxfuse 可用性作为SSH挂载能力检测的一部分。

配置

所有配置都是在启动时从环境变量中读取的。

核心设置

  • PTY_MCP_SESSION_LIMIT:默认情况下跟踪的PTY会话的最大数量 32
  • PTY_MCP_DEFAULT_READ_LIMIT:默认读取行数,默认 200
  • PTY_MCP_MAX_BUFFER_LINES:每个会话缓冲区保留行数,默认值 50000
  • PTY_MCP_ALLOWED_CWD_ROOTS:冒号分隔允许的工作目录根,默认当前目录
  • PTY_MCP_ALLOWED_COMMANDS:逗号分隔的命令名列表
  • PTY_MCP_DENIED_COMMANDS:逗号分隔的丹尼命令名列表
  • PTY_MCP_ALLOWED_ENV_VARS:以逗号分隔的环境变量名称列表
  • PTY_MCP_DENIED_ENV_VARS:逗号分隔的env-var名称列表

默认情况下,以下环境变量被拒绝:

  • LD_PRELOAD
  • LD_LIBRARY_PATH
  • DYLD_INSERT_LIBRARIES
  • DYLD_LIBRARY_PATH

SSH设置

  • PTY_MCP_SSH_BIN_PATH:显式路径 ssh
  • PTY_MCP_SSHFS_BIN_PATH:显式路径 sshfs
  • PTY_MCP_UMOUNT_BIN_PATH:显式路径 umount
  • PTY_MCP_DISKUTIL_BIN_PATH:显式路径 diskutil
  • PTY_MCP_SSH_MANAGED_MOUNT_ROOT:SSH挂载的托管本地根目录
  • PTY_MCP_SSH_ALLOWED_HOSTS:逗号分隔的主机列表,支持 * 和 *.example.com
  • PTY_MCP_SSH_DENIED_HOSTS:逗号分隔的主机denylist
  • PTY_MCP_SSH_ALLOWED_USERS:逗号分隔的SSH用户列表
  • PTY_MCP_SSH_ALLOWED_AUTH_KINDS:逗号分隔的身份验证列表,值: config_alias, ssh_agent, identity_file
  • PTY_MCP_SSH_ALLOW_EXPLICIT_MOUNT_PATHS:是否允许任意本地装载路径,默认值 true
  • PTY_MCP_SSH_ALLOWED_MOUNT_ROOTS:冒号分隔允许本地挂载根
  • PTY_MCP_SSH_MACOS_BLOCK_APPLE_METADATA:在macOS上,无论 ssh_mount 添加 noappledouble 和 noapplexattr,默认值 true 在macOS和 false 别处
  • PTY_MCP_SSH_PORT_MIN:默认允许的最小SSH端口 1
  • PTY_MCP_SSH_PORT_MAX:默认允许的最大SSH端口 65535

当 PTY_MCP_SSH_MANAGED_MOUNT_ROOT 设置后,它会自动添加到允许的cwd根目录和挂载根目录中。

示例

更严格政策的示例:

[mcp_servers.pty]
command = "pty-mcp"

[mcp_servers.pty.env]
PTY_MCP_ALLOWED_CWD_ROOTS = "/Users/alice/work:/tmp/pty-mcp"
PTY_MCP_ALLOWED_COMMANDS = "bash,sh,python,node,cargo"
PTY_MCP_SSH_ALLOWED_HOSTS = "*.example.com,github.com"
PTY_MCP_SSH_ALLOWED_USERS = "alice"
PTY_MCP_SSH_MANAGED_MOUNT_ROOT = "/tmp/pty-mcp-mounts"

发展

cargo build

问答

如果这已经是技能的时代,为什么还要使用MCP?

因为PTY管理需要后台进程的生命周期与代理会话保持绑定,所以这些进程不会分离并变成孤立进程。

在这种设置中,MCP是一个自然的选择。它的生命周期已经与会话生命周期保持一致,它可以作为所有托管后台进程的父进程。这使其成为持久PTY工作流程的特别好的基础。

致谢

多亏了 shekohex/opencode pty 分享一个深思熟虑的开源PTY管理实现,并在塑造过程中提供有用的现有技术 pty-mcp.

许可证

麻省理工学院