使用说明
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,或sigkillpty_wait:等待PTY会话退出
输出模型
PTY读取和捕获的启动输出现在使用一个规范的页面形状:
offsetreturnedhas_moretotal_linestext
默认行为是代理优先和紧凑:
- 使用
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:返回纯文本textembedded:将每个返回的行前缀为<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
- 当您希望将结果附加到PTY会话以供以后使用时,请使用此选项
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:通过在本地挂载远程路径sshfsssh_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://sessionspty://sessions/{id}pty://sessions/{id}/bufferpty://sessions/{id}/tailssh://connectionsssh://connections/{id}ssh://mountsssh://mounts/{id}ssh://docs/mount-setupssh://docs/mount-setup/{platform}
当客户端需要快照而不调用工具时,这些功能非常有用。
SSH挂载设置指南适用于代理:当 sshfs/FUSE支持缺失
代理可以读取这些资源,然后引导用户完成正确的本地安装流程
对于当前的平台,而不是猜测。
运行时间要求
PTY 通常翻译为“伪终端”。
内置本地PTY支持。命令需接受以下策略检查:
- 允许的工作目录根
- 允许/拒绝命令
- 允许/拒绝的环境变量
安全外壳协议
SSH功能取决于主机二进制文件:
sshSSH连接和远程执行需要sshfs是必需的ssh_mountumount用于卸载diskutil在macOS上额外探测
在macOS上,服务器也会进行探测 macFUSE / osxfuse 可用性作为SSH挂载能力检测的一部分。
配置
所有配置都是在启动时从环境变量中读取的。
核心设置
PTY_MCP_SESSION_LIMIT:默认情况下跟踪的PTY会话的最大数量32PTY_MCP_DEFAULT_READ_LIMIT:默认读取行数,默认200PTY_MCP_MAX_BUFFER_LINES:每个会话缓冲区保留行数,默认值50000PTY_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_PRELOADLD_LIBRARY_PATHDYLD_INSERT_LIBRARIESDYLD_LIBRARY_PATH
SSH设置
PTY_MCP_SSH_BIN_PATH:显式路径sshPTY_MCP_SSHFS_BIN_PATH:显式路径sshfsPTY_MCP_UMOUNT_BIN_PATH:显式路径umountPTY_MCP_DISKUTIL_BIN_PATH:显式路径diskutilPTY_MCP_SSH_MANAGED_MOUNT_ROOT:SSH挂载的托管本地根目录PTY_MCP_SSH_ALLOWED_HOSTS:逗号分隔的主机列表,支持*和*.example.comPTY_MCP_SSH_DENIED_HOSTS:逗号分隔的主机denylistPTY_MCP_SSH_ALLOWED_USERS:逗号分隔的SSH用户列表PTY_MCP_SSH_ALLOWED_AUTH_KINDS:逗号分隔的身份验证列表,值:config_alias,ssh_agent,identity_filePTY_MCP_SSH_ALLOW_EXPLICIT_MOUNT_PATHS:是否允许任意本地装载路径,默认值truePTY_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端口1PTY_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.
许可证
麻省理工学院