Skip to main content
Glama
README.md
# MCP SSH Server

一个基于 **expect + Python MCP** 的 SSH 持久连接方案,专为 JumpServer 堡垒机设计。

解决传统 SSH MCP 工具无法通过交互式堡垒机保持长连接的问题。

## 特性

- **多堡垒机支持** - 一个配置文件管理多台 JumpServer 和多台目标服务器
- **持久长连接** - 每个连接对应独立 expect 子进程,长期存活无需反复鉴权
- **并发执行** - 同时连接多台服务器,各自独立互不干扰
- **热加载** - 修改配置或模板后无需重启,下次调用自动生效
- **双认证模式** - 支持 SSH 密钥和密码两种认证方式
- **快速响应** - 使用 marker 分割技术,命令执行后立即返回输出

## 架构

```
┌─────────────────────────────────────────────────────────────┐
│                    Claude Code / MCP Client                  │
└──────────────────────────┬──────────────────────────────────┘
                           │ stdio JSON-RPC
                           ▼
┌─────────────────────────────────────────────────────────────┐
│                   MCP SSH Server (Python)                    │
│  ┌──────────────────┐    ┌──────────────────────────────┐  │
│  │ SessionManager   │    │   config.json (热加载)        │  │
│  │ ├─ connect()     │◄──►│   bastions[] → servers[]     │  │
│  │ ├─ execute()     │    └──────────────────────────────┘  │
│  │ └─ close()       │                                       │
│  └────────┬─────────┘                                       │
│           │ spawn + pipe                                     │
│           ▼                                                  │
│  ┌──────────────────────────────────────────────────────┐   │
│  │         expect_template.py (热加载)                    │   │
│  │         生成 expect 脚本处理交互式认证                  │   │
│  └────────┬─────────────────────────────────────────────┘   │
└───────────┼──────────────────────────────────────────────────┘
            │ spawn expect
            ▼
┌─────────────────────────────────────────────────────────────┐
│                    expect 进程 (per session)                  │
│  ┌─────────────────┐                                        │
│  │ spawn ssh       │──→ JumpServer → 目标服务器              │
│  │ stdin pipe      │◄── 接收 Python 发来的命令               │
│  │ stdout pipe     │── 输出到 Python reader 线程             │
│  └─────────────────┘                                        │
└─────────────────────────────────────────────────────────────┘
```

## 安装

### 前置要求

- Python 3.10+
- expect (系统命令)
- [uv](https://github.com/astral-sh/uv) (推荐的 Python 包管理器)

### 安装 expect

**macOS:**
```bash
brew install expect
```

**Ubuntu/Debian:**
```bash
sudo apt-get install expect
```

**CentOS/RHEL:**
```bash
sudo yum install expect
```

### 安装项目

```bash
cd a-mcp/mcp-ssh
uv sync
```

## 配置

复制配置示例并修改:

```bash
cp config.example.json config.json
```

### 配置示例

```json
{
  "bastions": [
    {
      "id": "bastion-01",
      "host": "bastion.example.com",
      "port": 22,
      "user": "your_username",
      "auth_type": "key",
      "key_path": "~/.ssh/your_key.pem",
      "password": "",
      "default": true,
      "servers": [
        {
          "id": "server-01",
          "name": "应用服务器 1",
          "search": "/192.168.1.100",
          "asset_id": "1",
          "target_dir": "/var/www/app1"
        },
        {
          "id": "server-02",
          "name": "应用服务器 2",
          "search": "/192.168.1.101",
          "asset_id": "2",
          "target_dir": "/var/www/app2"
        }
      ]
    },
    {
      "id": "direct-server",
      "host": "direct.example.com",
      "port": 22,
      "user": "your_username",
      "auth_type": "password",
      "key_path": "",
      "password": "your_password",
      "default": false,
      "servers": []
    }
  ]
}
```

### 配置说明

| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | string | 唯一标识符 |
| `host` | string | 服务器地址 |
| `port` | int | SSH 端口(默认 22) |
| `user` | string | 用户名 |
| `auth_type` | string | 认证方式:`key` 或 `password` |
| `key_path` | string | SSH 私钥路径(`auth_type=key` 时必填) |
| `password` | string | 密码(`auth_type=password` 时必填) |
| `default` | bool | 是否为默认堡垒机 |
| `servers` | array | 该堡垒机下的目标服务器列表 |

**服务器配置 (`servers[]`):**

| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | string | 服务器唯一 ID(用于 `ssh_connect`) |
| `name` | string | 服务器名称(描述用) |
| `search` | string | JumpServer 搜索关键词(如 `/192.168.1.100`) |
| `asset_id` | string | JumpServer 资产 ID |
| `target_dir` | string | 登录后切换的工作目录 |

## 使用方法

### 作为 MCP 服务

在 Claude Code 或其他 MCP 客户端中配置:

```json
{
  "mcpServers": {
    "ssh": {
      "command": "uv",
      "args": ["run", "--directory", "/path/to/mcp-ssh", "python", "mcp_ssh_server.py"]
    }
  }
}
```

### MCP 工具列表

| 工具 | 说明 |
|------|------|
| `ssh_connect` | 通过配置连接服务器(推荐) |
| `ssh_connect_raw` | 直接指定参数连接(临时使用) |
| `ssh_execute` | 在会话中执行命令 |
| `ssh_read_output` | 读取会话缓冲区输出 |
| `ssh_close` | 关闭会话 |
| `ssh_list_sessions` | 列出所有活跃会话 |
| `ssh_list_servers` | 列出配置中的所有服务器 |

### 使用示例

#### 1. 连接服务器

```python
# 使用配置中的服务器 ID
ssh_connect(server_id="server-01")

# 或直接指定参数
ssh_connect_raw(
    host="example.com",
    port=22,
    user="admin",
    password="secret",
    search="",  # 直连模式
    target_dir="/home/admin"
)
```

#### 2. 执行命令

```python
ssh_execute(session_id="session_1", command="ls -la")
ssh_execute(session_id="session_1", command="df -h")
```

#### 3. 管理会话

```python
# 查看所有活跃会话
ssh_list_sessions()

# 关闭指定会话
ssh_close(session_id="session_1")
```

## 项目结构

```
mcp-ssh/
├── mcp_ssh_server.py        # MCP 服务主入口 + SessionManager
├── expect_template.py       # expect 脚本模板(支持热加载)
├── config.json              # 多堡垒机 + 多资产配置(需自行创建)
├── config.example.json      # 配置示例
├── pyproject.toml           # Python 项目配置
├── uv.lock                  # 依赖锁定
├── ARCHITECTURE.md          # 详细架构文档
└── README.md                # 本文件
```

## 开发指南

### 本地测试

```bash
cd a-mcp/mcp-ssh
uv run python mcp_ssh_server.py
```

### 热加载机制

- **config.json** - 每次调用 `ssh_connect` 或 `ssh_list_servers` 时检查修改时间,变更则重新加载
- **expect_template.py** - 每次生成 expect 脚本时检查修改时间,变更则重新加载模块

修改后无需重启服务,下次调用自动生效。

### 添加新服务器

在 `config.json` 的 `servers` 数组中添加条目:

```json
{
  "id": "new-server",
  "name": "新服务器",
  "search": "/10.0.0.100",
  "asset_id": "1",
  "target_dir": "/opt/app"
}
```

### 修改 expect 行为

编辑 `expect_template.py` 的 `build_expect_script()` 函数,修改后即时生效。

## 常见问题

### Q: 为什么用 expect 而不是 ssh2 库?

JumpServer 堡垒机是**交互式菜单程序**,不是标准 SSH 跳板机。它禁止 `ProxyJump` 和端口转发,只能通过模拟键盘输入来操作。`expect` 是处理这种场景的最可靠方式。

### Q: 连接断了怎么办?

使用 `ssh_list_sessions` 查看会话状态。如果状态是 `closed` 或 `error`,重新调用 `ssh_connect` 即可。

### Q: 输出有 ANSI 乱码?

SSH 通过 PTY 传输,会带终端控制字符。这是正常的,命令输出本身不受影响。

### Q: 如何调试 expect 脚本?

在 `expect_template.py` 的关键步骤前添加:

```python
'send_user "DEBUG: 当前步骤\\n"',
"flush stdout",
```

热加载会自动生效。

## 许可证

MIT

## 贡献

欢迎提交 Issue 和 Pull Request!

TDQS

A3.8/5.0

Scored across 7 tools

Disambiguation5/5

Each tool has a clearly distinct purpose: connecting (two variants), closing, executing, listing servers, listing sessions, and reading output. No overlaps.

Naming Consistency5/5

All tools follow the pattern 'ssh_<action>' or 'ssh_<verb>_<noun>'. The naming is consistent and predictable.

Tool Count5/5

Seven tools cover the core operations for SSH session management without being excessive or insufficient.

Completeness5/5

The set includes connection, execution, session listing, and buffer reading. For the intended domain (interactive SSH via JumpServer), no obvious gaps.

Maintenance

ActivityMaintained
ResponsivenessNo issues