ssh
Provides SSH/SFTP access to Linux remote hosts for executing shell commands and transferring files, with structured exit codes, separated stdout/stderr, timeouts, output truncation, persistent sessions, host-key verification, and multi-host configuration.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@sshcheck disk usage on the prod web server"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
ssh-mcp —— 给 agent 用的 SSH/SFTP 工具
一个 stdio 上的 MCP server,让 agent 用尽量少的调用在 Linux 远端执行命令、传文件, 并拿到结构化、可判定的结果:退出码、stdout/stderr 分离、是否超时、是否截断、当前 cwd。
对外只有一层「SSH 桥」:ssh_run(跑命令)+ ssh_file(搬文件)。
命令按原生 shell 写(&&、管道、tail -n、sed -n、heredoc、mkdir -p),
桥只补 shell 给不了的东西(认证与 host key、连接复用、退出码分离、输出预算、跨调用的会话状态)。
为什么这样切、哪些不变量不能破,见 docs/domain.md。
技术栈:Bun + TypeScript + 官方 MCP SDK + ssh2 库后端。
不调用系统 ssh/scp/sftp 子进程,不用 plink,不用 ControlMaster,
除本 server 进程外不启动任何常驻后台进程(server 退出即无残留)。
1. 安装
cd C:/Files/Codes/ssh_mcp
bun install依赖 ssh2 的 cpu-features 原生模块在装包时会编译(实测 2026-10-26 约 30s),加载成功后走原生 crypto。
1.1 单文件产物(推荐给其它机器/客户端用)
不要求对方装 bun:把 server 编译成一个可执行文件,客户端的 command 直接指向它。
cd C:/Files/Codes/ssh_mcp
bun run build # bun build --compile → dist/ssh-mcp.exe(约 96 MiB)
cp dist/ssh-mcp.exe ~/tools/ # 装到用户目录,路径与仓库位置解耦
bun run dist-verify # 对 ~/tools/ssh-mcp.exe 跑 11 项端到端验收接入片段(把 command 换成 exe 的绝对路径,其余与第 2 节相同):
{
"mcpServers": {
"ssh": {
"command": "C:/Users/UnaRem/tools/ssh-mcp.exe",
"env": { "SSH_MCP_CONFIG": "C:/Files/Codes/ssh_mcp/config.local.json" }
}
}
}要点:
SSH_MCP_CONFIG必须给绝对路径。产物里不含config.local.json(含明文密码,永不入库), 不显式给就退回「进程 cwd 下的 config.local.json」,而客户端的 cwd 不可靠。更新流程:改完
src/**→bun run build→ 覆盖安装位置的 exe →bun run dist-verify。 exe 不入版本管理(dist/已忽略),可判定的来源始终是源码。 覆盖前先退出 MCP 客户端:客户端正持着这个 exe 时 Windows 不让写(实测cp: … Device or resource busy)。也可以直接用发布产物:GitHub Release 按 tag 提供
ssh-mcp-<版本>-windows-x64.zip(解压即ssh-mcp.exe)。 发布是 tag 驱动的:只改package.json的version→ 打v<版本>并推送 → CI 编译、离线自检、挂 zip; tag 与package.json不一致会被 CI 挡下(版本号只写在package.json一处)。实测(2026-10-27):
--compile后行为与源码版一致——11 项验收连续三次 11/11, 建连 589–615ms(源码版 527–589ms,同一量级;cpu-features若加载失败会退回 JS crypto 而明显变慢,未见退化)。 另记一次观察:产物刚cp落地后的第一次运行有 5 项以 transport 关闭的形式失败,随后三次全通过,未复现。
Related MCP server: lms-ssh-client-mcp
2. 接入 MCP 客户端
用绝对路径启动,别用相对路径(客户端的工作目录不确定):
{
"mcpServers": {
"ssh": {
"command": "bun",
"args": ["run", "C:/Files/Codes/ssh_mcp/src/index.ts"],
"env": {
"SSH_MCP_CONFIG": "C:/Files/Codes/ssh_mcp/config.local.json"
}
}
}
}本机 PI-Desktop 的 MCP 配置在 ~/.pi/agent/mcp.json(同样的 mcpServers 形状),已按上面的片段登记为 ssh。
工具名前缀由服务端自己决定:PI-Desktop 的可见工具名 = mcp_ + server 身份名(-→_)+ _ + 工具原名,
身份名取 src/index.ts 里 new McpServer({ name: "ssh" })(实测 2026-10-26:改 mcp.json 的键不影响前缀)。
旧的 name: "ssh-mcp" 会得到 mcp_ssh_mcp_ssh_exec,前缀与工具名交错重复,模型容易漏写或重复 ssh_ 段而调用失败;
现在是 mcp_ssh_ssh_run / mcp_ssh_ssh_file。如果以前在 mcp.json 里登记过 ssh-mcp,请把那条旧条目删掉,否则同一个 server 注册两份,工具目录翻倍。
command 用 bun 的绝对路径(C:/Users/UnaRem/.bun/bin/bun.exe)以免受 GUI 进程 PATH 差异影响。改完这个文件要完整退出并重启 PI-Desktop 进程才会重新发现工具,只重连 server 无效(原因见 2.1)。
不设 SSH_MCP_CONFIG 时 server 会退回 <进程 cwd>/config.local.json,所以显式写绝对路径更稳。
也可以写 bun start(package.json 的 start 脚本就是 bun run src/index.ts),但 MCP 客户端里推荐上面的直接形式,少一层进程。
另一类客户端不这样命名:OpenCode 用它配置文件(~/.config/opencode/opencode.json)里的 mcp 键作命名空间
(本机登记为 ssh-tool → tools["ssh-tool"].ssh_run),不读服务端的身份名,改键即时热重载、不用重启。
两边的命名来源不同:PI-Desktop 的前缀改 src/index.ts 的 name,OpenCode 只改它的键。
实测(2026-10-27,OpenCode v2.1.4):MCP 连接是按目录建立的——每个打开过的目录各有一条自己的连接。
改配置只重建其中一部分目录的连接;已经建立的那条不会重连,它的工具目录就停在旧版本——
注意是名字和行为都旧(实测旧连接仍在回答重构前的 14 个工具,返回体没有新版的 mode/hint 字段)。
刷新要靠「该目录的实例被重建」,日志里出现 mcp connected server=ssh-tool tools=2 directory=… 就是成功标志。
可用的做法,按可靠性排:
opencode service restart(或service stop+start)——重启常驻服务进程,所有目录都重来。 连接活在opencode … serve --service那个常驻进程里,只关 TUI 客户端无效(实测:重开 TUI 后仍是旧工具面)。把会话移到别的目录再移回来(
session_move),触发该目录实例重建,几秒完成。opencode --standalone起一个私有服务,MCP 全新连接,不动常驻服务,适合临时验证。
opencode mcp 只有 list / add / auth / logout,没有 reload 子命令。
自检:调 ssh_run 传 {},返回里带 mode:"inventory" 和 hint 才是新工具面。
协议层的验收不受这些影响——bun run live / bun run dist-verify 走的就是真实 stdio 握手与 tools/call。
启动后 tools/list 只返回两个工具:ssh_run、ssh_file。
这是刻意的窄面(判据见 docs/domain.md);ssh_run 不传任何参数就是自省模式,
返回可用主机清单、配置来源、活动连接与会话——原本用来「先看有哪些主机」的那次调用被省掉了。
2.1 让客户端能检索到工具(重要)
很多客户端(含 PI-Desktop 的 pi-mcp-adapter)靠词法匹配 name/server/description 来决定
是否把工具加载进上下文。它的分词只保留 ASCII:中文描述实测完全不参与匹配,纯中文 description
会让模型「找不到工具」。两处都已处理:
服务端(可移植,已实现):每个工具 description 末尾追加 ASCII
Keywords: ..., server 还带instructions说明何时用哪个工具。改的是文本,工具名与 schema 不动。客户端(PI-Desktop 专有):
mcp.json的该 server 下可加searchKeywords,按工具名给关键词,"*"为全 server 通用。它只影响排序,不展示给模型。示例:
"ssh": {
"command": "C:/Users/UnaRem/.bun/bin/bun.exe",
"args": ["run", "C:/Files/Codes/ssh_mcp/src/index.ts"],
"env": { "SSH_MCP_CONFIG": "C:/Files/Codes/ssh_mcp/config.local.json" },
"searchKeywords": {
"*": ["ssh", "remote", "server", "linux"],
"ssh_run": ["run command on remote server", "exec", "df disk usage", "uname", "grep log", "persistent shell"],
"ssh_file": ["upload file", "download file", "send file to remote host", "fetch remote file"]
}
}实测(用 adapter 自带的 scoreToolMatch 评分,已提交工具元数据):仅 description 关键词就能让
"check disk usage on the server" 命中 ssh_run;叠加 searchKeywords 分数更高、排序更稳。
工具面从 14 个收成 2 个之后,searchKeywords 也要同步改成 ssh_run / ssh_file 两条——
按旧工具名写的键不会命中任何工具。
改动 mcp.json(含 searchKeywords)或工具 description 后,当前会话的工具目录不会自动刷新:
adapter 日志会写 direct tools frozen after initial sync — reconnects won't rebuild the system prompt。
需要完整退出并重启 PI-Desktop(不是只重连 server),或在适配器里用 mcp({ connect: "server" }) 重新发现。
客户端配置(mcp.json 及其 searchKeywords)由使用侧自己维护,本仓库不代改;
本项目只保证服务端这一侧:description 带 ASCII Keywords:、instructions 说明何时用哪个工具。
另外,adapter 的 toolPrefix(none/short/mcp/server)只作用于 adapter 的 proxy 路径,
PI-Desktop 的直接注册路径不受它影响(推断,未实测)——要缩短名字只能改服务端 name 或工具名。
2.2 客户端会先校验参数(实测 2026-10-27,OpenCode)
OpenCode 在把参数发出去之前,先用发布出去的 JSON Schema 校验一遍,两种情形行为不同:
缺字段:直接报错,调用进不到服务端(例:
entries[0].local_path缺失 →Invalid arguments … Missing key)。多余的键:静默剥掉,不报错(例:多传
user/port也照样执行,服务端收不到它们)。
由此两条要记住:
严格的边界必须在服务端做(
z.object(...).strict(),发布的 schema 带additionalProperties:false)。 协议层会拒绝并点出写错的键名(Unrecognized keys: "user")——这条由 SDK 抛出,没有structuredContent也没有error_code,所以test/contract.test.ts断言的是文本里的键名,别断言 structuredContent。「服务端返回
bad_input」与「客户端先拦下」是两条路径:写错的entries[].op只有在同时给了local_path时才会走到服务端的bad_input + allowed_ops;漏了local_path就被客户端拦在外面。 模型能从 schema 自己看见这个边界(两个路径字段都必填),因此不算缺陷,只是排查时要分清错误来自哪一层。
3. 主机与认证
3.1 主机解析顺序
host 参数按以下顺序解析,任一命中即可;都不是就报错并列出全部可用别名(不会让模型猜 IP):
config.local.json里的 profile(见 3.2);~/.ssh/config的别名(读HostName/User/Port/IdentityFile,ProxyJump等跳转不跟随);直接写
192.168.1.21/user@192.168.1.21/host:2222/user@[v6]:2222。
ssh_run 不传任何参数就能拿到两处的别名与来源(source 字段),以及当前活动连接与会话。
3.2 本地 profile(含密码的文件不入库)
复制 config.example.json 为 config.local.json(已在 .gitignore 中):
{
"defaults": { "host_key_policy": "tofu" },
"hosts": {
"lan": {
"hostname": "192.168.1.21",
"user": "unarem",
"password": "……"
},
"app": {
"hostname": "10.0.0.5",
"user": "deploy",
"identity_files": ["~/.ssh/id_ed25519"],
"host_key_policy": "strict"
}
}
}字段:hostname port user password key_passphrase identity_files host_key_policy(tofu/strict/off)。
defaults 会被具体 profile 覆盖。
3.3 认证顺序:密钥优先,密码兜底
有可用私钥(
identity_files,或~/.ssh/config指定的、或~/.ssh/下的 id_* 文件)时先试 publickey, 失败再试密码——这是显式指定的authHandler: ["publickey", "password"](ssh2默认顺序是密码在前)。密码来源优先级:
config.local.json的password→SSH_MCP_PASSWORD_<ALIAS大写、非字母数字换成_>→SSH_MCP_PASSWORD。不会弹密码提示:拿不到凭据就在
SSH_MCP_READY_TIMEOUT_MS内失败,并给出可读原因 (实测对密钥被拒且无密码的主机 1.3s 返回「认证失败:… 拒绝了提供的凭据」)。返回里的
auth_methods_attempted/auth_succeeded_with是从 SSH 协议报文实测出来的,不是猜的。
3.4 host key
ssh2 本身不校验 host key,这里自己实现(known_hosts 只追加,不改写既有行):
tofu(默认):首次连接记录,之后不匹配就拒绝;strict:没有记录就拒绝;off:不校验。支持裸 host(对所有端口生效)、
[host]:port(仅该端口)、IPv6 方括号形式、通配与!排除、|哈希条目。
SSH_MCP_KNOWN_HOSTS 可指定别的 known_hosts 文件(默认 ~/.ssh/known_hosts)。
4. 工具与返回语义
对外只有两个工具,参数只放 shell 给不了的东西(判据与不变量见 docs/domain.md)。
工具 | 参数 | 返回 |
|
|
|
| — | 自省: |
|
| 按请求顺序返回每个条目: |
用法要点:
多步、取尾部、区间、过滤、写文件、建目录都写在命令里:
mkdir -p a && cd a && pwd、journalctl -u app | tail -n 50、sed -n '120,180p' f、cat > f <<'EOF' … EOF。没有commands[]、output_mode、tail_lines、keep_stdin这类参数。用户名/端口/host key 策略不是参数:写在
~/.ssh/config(User/HostName/Port/StrictHostKeyChecking) 或用user@ip;否则就是config.local.json(见 3.2)。跨调用保持状态才用
session="keep":第一次调用建立并返回session_id,之后可以写 id; 只有一个活动会话时连host都能省。session_op="reset"回到登录目录、session_op="close"结束会话。一次性调用里
cwd/env不会带到下一次——结果会带hint说明怎么改成会话,不需要额外参数。
返回语义与错误码:
成功调用:
ok: true+ JSON(同时给structuredContent)。命令退出码非 0 不是工具错误:isError=false,但顶层ok=false,请看exit_code与stderr。工具错误:
isError=true+error_code+ 可操作的error,并附上补救所需的事实。
error_code | 何时 | 附带事实 |
| 缺 |
|
| 别名解析不出来 |
|
| 握手/凭据/host key |
|
| 会话不存在或已被 TTL 回收;只凭 |
|
| 传输条目的文件系统情形(按数据返回,调用本身不是错误)。 | 条目 |
| 其它未归类 |
|
其他保证:
timeout_ms到期 →timed_out: true,server 不死,连接仍可用;输出超预算 →
truncated: true,并回报stdout_bytes/stdout_lines等真实总量;想少看点就在命令里用head/tail/sed;默认给命令补
< /dev/null(cat之类不会挂死);管道命令不补,以免改变语义;引号未闭合等语法问题会先走远端
bash -n预检,返回「命令未执行」+ bash 的报错(实测 ~20ms),命令不会半执行;后台/常驻进程占住 fd → 哨兵超时后返回
background_suspect: true并自动重开会话;会话空闲超过 TTL → 自动关闭,下次用该 id 会明确说明原因与时长,并提示传
session="keep"重新开始。
5. 环境变量(都有默认值)
变量 | 默认 | 作用 |
|
| 本地 profile 文件 |
|
| 覆盖 ssh_config 路径(测试用) |
|
| known_hosts 路径 |
| — | 密码兜底 |
| 12000 | 握手/认证等待上限 |
| 30000 | 命令默认超时(工具可覆盖,上限 600000) |
| 600000 | 会话空闲 TTL |
| 300000 | 进程内连接池空闲 TTL(新连接实测 ~1.8s,故默认保留) |
6. 自检
bun run lint # = bunx tsc --noEmit,类型检查
bun test # 离线测试,不需要网络(fake ssh/sftp 替身 + 进程内真 SSH 服务器)
# test/contract.test.ts 锁的是对外契约:两个工具、参数集合、错误码、自省模式
bun run live # 对真实主机跑 25 项端到端验收,读 config.local.json
LIVE_HOST=lan bun run live
bun run build # 单文件产物 dist/ssh-mcp.exe(见 1.1)
bun run dist-verify # 对产物跑 11 项验收,默认验 ~/tools/ssh-mcp.exe;可传路径验 dist/
bun run dist-verify dist/ssh-mcp.exe
bun run dist-verify dist/ssh-mcp.exe --offline # 只跑协议层 5 项(CI 里没有验收主机,远端相关项记 SKIP)live-verify 会复制一份 ~/.ssh/known_hosts 到临时目录来跑 TOFU/不匹配用例,不动真实文件,跑完清理远端临时目录。
改完 src/** 的远端行为,收官必须跑一次 bun run live;只改文档不必跑。
bun run build 之后必须跑 bun run dist-verify:--compile 把依赖(含 ssh2 的原生模块)打进单文件,
构建期不报错不代表运行期没问题。
7. 边界
只支持 Linux 远端;不做 Windows 远端语义适配。
不启用 ControlMaster/ControlPersist,不引入系统 ssh 子进程或 plink 后端。
不做后台任务/job 家族(异步任务表、轮询取结果):与「不留常驻进程」冲突,已取消。
不把 SFTP 的 list/stat/read/write/mkdir 做成工具:远端
ls/stat/sed/cat/mkdir -p更贴近模型既有能力。不做 npm 发布 / Docker / Web UI / 遥测。对外分发只走
bun build --compile的单文件产物(见 1.1), 不引入包管理分发链路;产物由 tag 驱动的 GitHub Actions 挂到 Release,目前只发 windows-x64(要加平台就再加一个 job, 前提是先在该平台的 CI 里跑通产物自检)。不读写
~/.ssh/config,不改写私钥;对known_hosts仅追加。
This server cannot be deployed
Maintenance
Related MCP Connectors
Operate Linux, macOS and Windows from your LLM. Every action runs through an auditable allowlist.
Work on your own computers from Claude or ChatGPT: run commands, edit and search files.
Linux VMs for AI agents: create VMs, run commands, edit files and open preview URLs.
Scoped, audited SSH exec, sessions, and SFTP on your saved servers without exposing credentials
Related MCP Servers
- AlicenseAqualityDmaintenanceEnables LLMs to interact with remote servers via SSH, supporting command execution, file upload/download, and directory listing.7MIT
- FlicenseAqualityCmaintenanceEnables LLMs to securely SSH into remote servers, execute commands, and manage files via SFTP including listing, reading, writing, deleting, and renaming files.11-
- FlicenseNot gradedqualityCmaintenanceEnables executing shell commands and transferring files on remote machines over SSH/SFTP, including persistent interactive sessions that can handle prompts such as sudo and package manager confirmations.-
- AlicenseNot gradedqualityCmaintenanceEnables AI agents to run shell commands over SSH on a fixed, pre-registered set of Linux machines, returning structured results with audit logging, enforced timeouts, output limits, and explicit registry management. It also supports safe file upload/download and multi-host command execution.MIT