# local-mcp-server **Repository Path**: dont-touch-my-code/local-mcp-server ## Basic Information - **Project Name**: local-mcp-server - **Description**: 本地启动的mcp服务器,提供一键启动的脚本,分组注册发现工具,可拔插分组 - **Primary Language**: Python - **License**: AGPL-3.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-08-13 - **Last Updated**: 2026-08-18 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Pluggable MCP Server 基于 [MCP (Model Context Protocol)](https://modelcontextprotocol.io) 的可拔插分组式工具服务器。 - **目录即分组**:`groups/` 下每个子目录是一个工具分组,自动发现注册 - **可拔插**:运行时启用/禁用分组,禁用后其工具即时失效 - **服务器自管理**:服务器自身暴露管理工具,支持通过 MCP 协议动态管理分组 - **一键启动**:单命令启动,支持 stdio / sse / streamable-http 三种传输 > 基于 Python + 官方 `mcp` SDK(v2.0+)实现。 --- ## 目录结构 ``` mcp/ ├── server.py # 主入口:一键启动 + CLI 管理命令 ├── config.py # 全局基础配置(仅服务器级;分组特定常量在各分组内) ├── requirements.txt # 依赖 ├── credentials.example.yaml # SSH 凭据配置示例 ├── hosts.example.yaml # SSH 主机清单示例 ├── group_state.json # 分组启用状态持久化(运行后自动生成) ├── core/ │ ├── __init__.py │ ├── registry.py # 分组/工具的注册、发现、启用/禁用 │ ├── manager.py # MCPServer 管理、工具动态注册、一键启动 │ └── config/ # 通用配置管理抽象(不耦合任何分组) │ ├── __init__.py # 导出 ConfigProvider/ConfigScope/ConfigManager 等 │ ├── base.py # ConfigScope 枚举 + ConfigProvider 抽象基类 │ ├── local.py # LocalFileProvider 默认实现(YAML + ${VAR} 引用) │ ├── merger.py # deep_merge + 环境变量字段级覆盖 │ ├── finder.py # 多级路径查找(SHARED/WORKSPACE/PROJECT/LOCAL) │ ├── manager.py # ConfigManager:路由 + 合并 + 缓存 │ └── bootstrap.py # 启动时初始化 ConfigManager + 扫描 providers 扩展 ├── providers/ # 配置 provider 扩展点(预留:Nacos/Vault 等) │ └── __init__.py ├── groups/ # 分组目录:每个子目录即一个分组 │ ├── __init__.py │ ├── db_client/ # 分组:数据库工具(多数据库支持) │ │ ├── __init__.py # 分组元数据声明 │ │ ├── base.py # DatabaseClient 抽象基类 │ │ ├── connection_manager.py # 连接池:connection_id -> client │ │ ├── sqlite_client.py # SQLite 实现(标准库) │ │ ├── mysql_client.py # MySQL 实现(pymysql,延迟导入) │ │ ├── postgres_client.py # PostgreSQL 实现(psycopg2,延迟导入) │ │ └── tools.py # 统一工具函数入口 │ └── ssh_client/ # 分组:SSH 远程运维工具 │ ├── __init__.py # 分组元数据声明 │ ├── base.py # SSHClient 抽象基类 + HostInfo/CommandResult │ ├── paramiko_client.py # ParamikoSSHClient 实现(paramiko,延迟导入) │ ├── host_probe.py # 主机环境探测 + 平台命令适配 │ ├── connection_manager.py # SSH 连接池(LRU) │ ├── credentials.py # 凭据/主机配置加载(通过 ConfigManager) │ ├── constants.py # 分组专用常量(CONFIG_TYPE_*/AUTH_TYPES 等) │ ├── safety.py # 安全策略:风险分级/黑名单/注入防护 │ ├── approval.py # 审批池:write/danger 风险需用户审批 │ ├── audit.py # 审计日志(JSONL,落 logs/audit.log) │ └── tools.py # 14 个工具函数入口 ├── workspaces/ # 多项目配置隔离目录(MCP_WORKSPACE 指定子目录) │ └── .gitkeep ├── logs/ # 审计与运行日志 │ └── .gitkeep └── README.md ``` --- ## 快速开始 ### 1. 安装依赖 推荐使用虚拟环境: ```bash python -m venv .venv # Windows .venv\Scripts\activate # macOS / Linux source .venv/bin/activate pip install -r requirements.txt ``` ### 2. 一键启动 ```bash python server.py start ``` 默认使用 `stdio` 传输。如需 SSE / HTTP: ```bash python server.py start --transport sse python server.py start --transport streamable-http ``` 传输协议、监听地址也可通过环境变量配置: | 变量 | 默认值 | 说明 | |------|--------|------| | `MCP_TRANSPORT` | `stdio` | 传输协议:`stdio` / `sse` / `streamable-http` | | `MCP_HOST` | `127.0.0.1` | SSE / HTTP 监听地址 | | `MCP_PORT` | `9000` | SSE / HTTP 监听端口 | ### 3. MCP 客户端配置(Trae / Claude Desktop 等) 服务器以 stdio 模式启动时,需由 MCP 客户端(如 Trae、Claude Desktop)拉起子进程。 在客户端的 MCP 配置文件中添加本服务器即可。 **配置文件位置:** | 客户端 | 路径 | |--------|------| | Trae CN | 项目目录 `.trae/mcp.json`,或通过 `设置 → MCP → 添加 MCP Server` 界面配置 | | Claude Desktop | `%APPDATA%\Claude\claude_desktop_config.json`(Windows) | **完整配置示例:** ```json { "mcpServers": { "pluggable-mcp-server": { "command": "D:\\program\\mcp\\.venv\\Scripts\\python.exe", "args": ["D:\\program\\mcp\\server.py", "start"], "cwd": "D:\\program\\mcp", "env": { "MCP_TRANSPORT": "stdio", "PYTHONPATH": "D:\\program\\mcp", "PYTHONIOENCODING": "utf-8" } } } } ``` **字段说明:** | 字段 | 必填 | 类型 | 说明 | |------|:----:|------|------| | `mcpServers` | ✅ | object | 所有 MCP 服务器的根容器,key 为服务器名称(自定义) | | `command` | ✅ | string | 启动命令,**建议直接指向虚拟环境的 `python.exe`**,避免找不到 `mcp` 包 | | `args` | ✅ | string[] | 启动参数。`server.py start` 即一键启动;可追加 `--transport sse` 切换传输 | | `cwd` | ✅ | string | 工作目录,**必须指向项目根**,否则 `from config import ...` 等导入失败 | | `env` | 否 | object | 子进程环境变量 | **`env` 可用变量:** | 变量 | 说明 | |------|------| | `MCP_TRANSPORT` | 覆盖默认传输协议(`stdio` / `sse` / `streamable-http`) | | `MCP_HOST` | SSE / HTTP 模式监听地址(默认 `127.0.0.1`) | | `MCP_PORT` | SSE / HTTP 模式监听端口(默认 `9000`) | | `MCP_WORKSPACE` | 当前 workspace 名称(多项目隔离用,仅允许 `[a-zA-Z0-9_-]`,未设置则单项目模式从 `/.mcp/` 读配置) | | `MCP_CONFIG_DIR` | 项目级配置目录绝对路径(覆盖 `/.mcp/` 默认值) | | `MCP___` | 配置字段级环境变量覆盖(如 `MCP_SSH_CLIENT_CREDENTIALS_DEV_BOX_PASSWORD`) | | `PYTHONPATH` | 让 `server.py` 能 import 项目内的 `config` / `core` / `groups` 模块 | | `PYTHONIOENCODING` | Windows 下强制 UTF-8 输出,避免中文乱码 | **关键注意事项:** 1. **必须用虚拟环境的 python**:路径形如 `D:\program\mcp\.venv\Scripts\python.exe`,否则 `mcp` 包未安装 2. **cwd 必须是项目根**:`D:\program\mcp`,否则模块导入失败 3. **stdio 模式无需端口**:客户端通过子进程 stdin/stdout 通信,`MCP_HOST`/`MCP_PORT` 仅 sse/http 模式生效 4. **路径分隔符**:JSON 中 Windows 路径需用 `\\` 转义,或使用正斜杠 `/` **SSE 模式配置示例(远程访问场景):** ```json { "mcpServers": { "pluggable-mcp-server-sse": { "command": "D:\\program\\mcp\\.venv\\Scripts\\python.exe", "args": ["D:\\program\\mcp\\server.py", "start", "--transport", "sse"], "cwd": "D:\\program\\mcp", "env": { "MCP_HOST": "0.0.0.0", "MCP_PORT": "9000" } } } } ``` **配置后使用方式:** Trae 加载配置后会自动启动 server 子进程并发现全部工具。 在对话中直接描述需求,AI 会自动调用对应工具并填充参数: ``` 用户:连一下 SQLite 内存库,建个 users 表插一条数据 AI 自动调用: 1. connect_database(db_type="sqlite") → "f4719a1a390d" 2. execute_query(connection_id="f4719a1a390d", query="CREATE TABLE users (id INTEGER, name TEXT)") 3. execute_query(connection_id="f4719a1a390d", query="INSERT INTO users VALUES (1, 'alice')") ``` > **密码安全提示**:当前连接信息(host/port/user/password)通过 MCP 调用参数传入, > 会进入对话历史与调用日志。生产环境建议使用 SQLite 或通过环境变量注入凭据, > 避免在对话中明文传递密码。 --- ## 环境一致性注意事项(重要) MCP server 是被客户端(Trae / Claude Desktop)作为**子进程**拉起的, 它用的是配置里 `command` 指向的那个 Python 环境,**不是你终端里激活的环境**。 依赖必须装到 MCP server 实际运行的那个 Python,否则会出现"驱动找不到"错误。 ### 问题示例 ``` 你在终端执行 pip install pymysql → 装到系统/conda python(有 pymysql,无 mcp) Trae 拉起 MCP server 子进程 → 用 .venv\python.exe(有 mcp,无 pymysql)← server 在这里跑 ↓ connect_database(db_type="mysql") 报错:驱动未安装 ``` ### 铁律 **MCP 配置里 `command` 指向哪个 python,依赖就必须装到那个 python。** ### 安装依赖的正确姿势 以本项目的 `.venv` 配置为例(`.trae/mcp.json` 中 `command` 指向 `.venv\Scripts\python.exe`): ```bash # ✅ 正确:用 .venv 的 python 装,MCP server 能看到 .venv\Scripts\python.exe -m pip install pymysql psycopg2-binary # ❌ 错误:装到系统/conda,MCP server 看不到 pip install pymysql ``` 完整依赖安装: ```bash .venv\Scripts\python.exe -m pip install -r requirements.txt ``` ### 如何诊断"环境不一致" 遇到"MCP server 找不到某依赖"时,**用 MCP 配置里那个 python** 检查: ```bash # 把路径换成你 mcp.json 里 command 的值 D:\program\mcp\.venv\Scripts\python.exe -c "import pymysql; print('OK')" ``` | 输出 | 含义 | 处理 | |------|------|------| | `ModuleNotFoundError` | 依赖没装到 MCP 用的环境 | 用同一个 python 的 `-m pip install` 重装 | | `OK` | 依赖已在 | 重启 MCP server(配置改动后需重新拉起子进程) | ### 三种保证环境一致的方式 **方式一:始终用项目 `.venv`(推荐,当前默认)** ```json { "mcpServers": { "pluggable-mcp-server": { "command": "D:\\program\\mcp\\.venv\\Scripts\\python.exe", "args": ["D:\\program\\mcp\\server.py", "start"], "cwd": "D:\\program\\mcp" } } } ``` - 优点:环境隔离,不污染系统 - 注意:装依赖时必须用 `.venv\Scripts\python.exe -m pip install ...` **方式二:用系统 python,依赖全部装到系统** ```bash python -m pip install mcp pymysql psycopg2-binary ``` ```json { "mcpServers": { "pluggable-mcp-server": { "command": "python", "args": ["D:\\program\\mcp\\server.py", "start"], "cwd": "D:\\program\\mcp" } } } ``` - 缺点:污染系统环境,多项目易冲突 **方式三:用 conda 独立环境** ```bash conda create -n mcp-server python=3.13 -y conda activate mcp-server pip install -r D:\program\mcp\requirements.txt ``` ```json { "mcpServers": { "pluggable-mcp-server": { "command": "D:\\software\\miniconda\\envs\\mcp-server\\python.exe", "args": ["D:\\program\\mcp\\server.py", "start"], "cwd": "D:\\program\\mcp" } } } ``` - 优点:隔离性好,适合多项目管理 ### 配置改动后必须重启 修改 `.trae/mcp.json` 或安装新依赖后,**必须重启 MCP server 子进程**才生效: - Trae CN:重新加载 MCP 配置或重启会话 - Claude Desktop:重启应用 --- ## CLI 管理命令 ```bash # 列出所有分组及启用状态 python server.py list # 启用一个分组(持久化) python server.py enable <分组名> # 禁用一个分组(持久化,工具即时失效) python server.py disable <分组名> # 查看服务器与分组概览(JSON) python server.py info ``` 示例: ```bash $ python server.py list 分组 状态 工具数 描述 ---------------------------------------------------------------------- db-client 启用 4 数据库连接相关工具(基于 SQLite 的示例实现) ``` --- ## 内置管理工具 服务器启动后,以下管理工具会自动注册并通过 MCP 协议暴露给客户端: | 工具 | 说明 | |------|------| | `list_groups` | 列出所有分组及其启用状态、描述、工具清单 | | `list_tools` | 列出工具清单(可仅看启用分组) | | `enable_group(name)` | 启用分组并即时注册其工具 | | `disable_group(name)` | 禁用分组并即时失效其工具(可拔插) | | `reload_tools` | 热重载:重新发现分组并重注册工具 | > 管理工具始终注册,不会被 `disable_group` / `reload_tools` 清理。 --- ## 示例分组:db-client 内置一个支持多种数据库的工具分组,统一接口、按 `db_type` 路由实现。 ### 支持的数据库 | db_type | 驱动 | 安装命令 | 说明 | |---------|------|----------|------| | `sqlite` | 标准库 `sqlite3` | 无需安装 | 默认可用,文件或内存数据库 | | `mysql` | `pymysql` | `pip install pymysql` | 连接时延迟导入,未安装仅影响 MySQL | | `postgresql` | `psycopg2-binary` | `pip install psycopg2-binary` | 连接时延迟导入,未安装仅影响 PostgreSQL | > 驱动延迟导入:未安装某驱动时,只有对应数据库的连接调用会失败,不影响其他数据库与服务器启动。 ### 工具清单 | 工具 | 参数 | 说明 | |------|------|------| | `list_supported_databases` | — | 列出支持的数据库类型 | | `connect_database` | `db_type`, `host`, `port`, `user`, `password`, `database`, `db_path` | 连接数据库,返回 `connection_id` | | `list_connections` | — | 列出当前所有活动连接(含 id 与 db_type) | | `disconnect_database` | `connection_id` | 关闭并释放指定连接 | | `list_tables` | `connection_id` | 列出该连接数据库的所有表名 | | `execute_query` | `connection_id`, `query` | 执行单条 SQL,返回结果 | ### connect_database 参数说明 参数按 `db_type` 自动路由,无关参数被忽略: | 参数 | sqlite | mysql | postgresql | 默认值 | |------|:------:|:-----:|:----------:|--------| | `db_path` | ✅ | — | — | `:memory:` | | `host` | — | ✅ | ✅ | `127.0.0.1` | | `port` | — | ✅ | ✅ | `3306` / `5432` | | `user` | — | ✅ | ✅ | — | | `password` | — | ✅ | ✅ | — | | `database` | — | ✅ | ✅ | — | ### 调用示例 ``` # 连接 SQLite 内存数据库 connect_database(db_type="sqlite") => "f4719a1a390d" # 连接 MySQL connect_database(db_type="mysql", host="127.0.0.1", port=3306, user="root", password="***", database="test") # 连接 PostgreSQL connect_database(db_type="postgresql", host="127.0.0.1", port=5432, user="postgres", password="***", database="app") # 多连接并存 list_connections() => [{"connection_id": "f4719a1a390d", "db_type": "sqlite"}, {"connection_id": "a1b2c3d4e5f6", "db_type": "mysql"}] ``` ### 架构 ``` tools.py (统一工具入口) │ ▼ connection_manager.py ──► db_type 路由 │ │ ├──► SqliteClient ◄── base.DatabaseClient ├──► MysqlClient ◄── base.DatabaseClient └──► PostgresClient ◄── base.DatabaseClient ``` 新增数据库实现:继承 `base.DatabaseClient`,实现 4 个抽象方法,在 `connection_manager._CLIENT_REGISTRY` 注册即可。 --- ## 示例分组:ssh-client 内置一个 SSH 远程运维工具分组,支持连接远程主机、查看日志、查看服务状态、 管理服务(启停重启)、执行脚本、发布应用、安装软件、获取主机环境状态、 上传下载文件等能力,适用于智能体安全运维场景。 ### 安装依赖 ```bash .venv\Scripts\python.exe -m pip install paramiko pyyaml ``` > paramiko 延迟导入:在 `ParamikoSSHClient.connect()` 方法内 `import paramiko`, > 未安装不影响 server 启动与其他分组,仅 `connect_host` 调用返回友好错误。 ### 配置流程 ssh-client 采用 **混合凭据方案**:智能体只接触 `alias`(主机别名), 不直接传递密码或密钥路径,凭据由 MCP server 侧的配置文件管理。 **步骤 1:配置主机清单**(`hosts.yaml`,可提交 git,不含敏感信息) ```yaml # /.mcp/ssh-client/hosts.yaml 或 workspaces//ssh-client/hosts.yaml hosts: prod-web-01: address: 10.0.1.10 port: 22 user: deploy tags: [prod, web, nginx] mode: require_approval # 生产强制审批 dev-box: address: 192.168.1.100 port: 22 user: root tags: [dev, ubuntu] mode: auto_approve # 开发放宽(write 自动执行) ``` **步骤 2:配置凭据**(`credentials.yaml`,**不入 git**,用 `${VAR}` 引用环境变量) ```yaml # /.mcp/ssh-client/credentials.local.yaml 或 workspaces//ssh-client/credentials.yaml credentials: prod-web-01: auth_type: key # password / key / ssh_config user: deploy key_path: ~/.ssh/id_ed25519 key_passphrase: ${KEY_PASSPHRASE} # 从环境变量读取 dev-box: auth_type: password user: root password: ${DEV_BOX_PASSWORD} ``` **步骤 3:设置环境变量**(实际敏感值) ```bash export KEY_PASSPHRASE=xxx export DEV_BOX_PASSWORD=xxx ``` 完整示例参考 [credentials.example.yaml](credentials.example.yaml) 与 [hosts.example.yaml](hosts.example.yaml)。 ### 工具清单 | 工具 | 参数 | 风险 | 说明 | |------|------|:----:|------| | `connect_host` | `host`, `mode` | read | 连接远程主机,返回 `host_id` 与主机环境信息 | | `disconnect_host` | `host_id` | read | 关闭连接 | | `list_connections` | — | read | 列出所有活动连接 | | `exec_command` | `host_id`, `command`, `timeout` | read/write/danger | 执行 Shell 命令(含安全检查与审批) | | `run_script` | `host_id`, `script`, `language`, `timeout` | write | 上传并执行 bash/python 脚本 | | `read_logs` | `host_id`, `file`/`service`, `tail`, `grep` | read | 读取日志文件或 systemd journal | | `manage_service` | `host_id`, `action`, `service` | read/write | 启停/重启/重载/启用/禁用/查看服务 | | `list_services` | `host_id`, `state` | read | 列出所有服务状态 | | `install_package` | `host_id`, `packages`, `update_first` | write | 安装软件包(适配 apt/yum/dnf/apk) | | `transfer_file` | `host_id`, `direction`, `local_path`, `remote_path` | read/write | SFTP 上传/下载文件 | | `get_system_stats` | `host_id` | read | 获取 CPU/内存/磁盘/负载/uptime | | `approve_action` | `approval_id` | — | 批准待审批操作并立即执行 | | `reject_action` | `approval_id`, `reason` | — | 拒绝待审批操作 | | `list_pending_actions` | `host_alias` | read | 列出所有待审批请求(含倒计时) | ### 调用示例 ``` # 连接远程主机(凭据由配置文件提供,智能体只传 alias) connect_host(host="prod-web-01") => {host_id: "a1b2c3d4e5f6", host_info: {os: "Linux", distribution: "ubuntu", ...}} # 查看日志(read 风险,自动执行) read_logs(host_id="a1b2...", file="/var/log/nginx/access.log", tail=50, grep="error") # 查看服务状态(read 风险,自动执行) manage_service(host_id="a1b2...", action="status", service="nginx") # 重启服务(write 风险,require_approval 模式下返回 ApprovalRequest) manage_service(host_id="a1b2...", action="restart", service="nginx") => {approval_id: "0f5cc985c80d", status: "pending_approval", will_execute: "systemctl restart nginx", side_effects: "restart 服务 nginx", reversible: true, rollback_cmd: "systemctl start nginx", expires_at: 1786972205, remaining_seconds: 300, next_action: "调用 approve_action(approval_id='0f5cc985c80d') 批准执行..."} # 用户批准 approve_action(approval_id="0f5cc985c80d") => {stdout: "", stderr: "", exit_code: 0, approved_by: "user"} # 用户拒绝 reject_action(approval_id="0f5cc985c80d", reason="不应在生产重启 nginx") => {approval_id: "0f5cc985c80d", status: "rejected", reason: "..."} ``` ### 平台自动适配 `connect_host` 建立连接后会自动探测主机环境(`HostInfo`),后续工具按平台适配命令: | 主机环境 | 服务管理 | 包管理 | 日志查看 | |----------|----------|--------|----------| | systemd(Ubuntu/CentOS 7+/RHEL) | `systemctl` | apt-get / yum / dnf | `journalctl` | | sysvinit(CentOS 6 等) | `service` | yum | `tail` 文件 | | openrc(Alpine) | `rc-service` | apk | `tail` 文件 | ### 架构 ``` tools.py (14 个工具入口) │ ├─► connect_host ─► credentials.load_host_config + load_credentials │ ─► ParamikoSSHClient.connect (延迟导入 paramiko) │ ─► host_probe.probe_host_info │ ─► ConnectionManager.create_connection │ ├─► exec_command ─► safety.check_command_safety │ ─► approval.should_require_approval │ ─► client.exec_command │ ─► audit.log │ ├─► manage_service / install_package ─► host_probe.build_platform_command │ └─► approve_action / reject_action ─► ApprovalPool + audit.log ``` --- ## 配置管理 ### 设计目标 - **抽象**:`ConfigProvider` 抽象接口,支持本地文件、配置中心(Nacos/Vault)扩展 - **分层**:`ConfigScope` 枚举定义 4 个作用域,按优先级合并 - **隔离**:`MCP_WORKSPACE` 环境变量实现多项目配置隔离 - **覆盖**:环境变量字段级覆盖(`MCP___`) ### ConfigScope 作用域 合并优先级从低到高: | Scope | 路径 | 用途 | 入 git | |-------|------|------|:------:| | `SHARED` | `/workspaces/shared//` | 跨项目共享默认 | 是 | | `WORKSPACE` | `/workspaces///` | 当前项目 | 是 | | `PROJECT` | `/.mcp//` | 项目 cwd | 是 | | `LOCAL` | `/.mcp//*.local.yaml` | 本地覆盖 | **否** | | 环境变量 | `MCP___` | 字段级覆盖 | — | 合并规则:`SHARED < WORKSPACE < PROJECT < LOCAL < 环境变量字段级覆盖`。 ### 配置分层约束(重要) 为避免 `config.py` 职责越界,配置分三层: ``` Layer 1: config.py —— MCP 服务器全局基础配置 仅放服务器级基础项(BASE_DIR / GROUPS_DIR / SERVER_NAME / TRANSPORT / WORKSPACE_NAME / WORKSPACES_DIR / LOGS_DIR 等) ❌ 禁止出现 CREDENTIALS_FILE / HOSTS_FILE 等分组特定常量 Layer 2: core/config/ —— 通用配置抽象(不耦合任何分组) 提供 load/merge/cache/find/diagnose 通用能力 group 与 config_type 作为参数传入,不硬编码 ❌ 禁止 import 任何 groups.* ❌ 禁止硬编码 "credentials" / "hosts" / "ssh-client" 等字面量 Layer 3: groups//constants.py —— 分组特定配置 定义本分组的 CONFIG_TYPE_*、文件名、schema、枚举值 只在分组包内可见,不污染全局 ✅ ssh_client/constants.py: CONFIG_TYPE_HOSTS / CONFIG_TYPE_CREDENTIALS ✅ db_client 未来接入: db_client/constants.py: CONFIG_TYPE_DATABASES ``` 依赖方向(不可逆):`groups/* → core/config → config.py` ### ConfigProvider 扩展点 `providers/` 目录预留扩展: ```python # providers/nacos.py(未来实现) from core.config.base import ConfigProvider, ConfigScope class NacosProvider(ConfigProvider): name = "nacos" def load(self, group, config_type, scope, workspace=None): # 调用 nacos-sdk 拉配置 ... def watch(self, callback): # 订阅 nacos 配置变更 ... def health_check(self): # 检查 nacos 连通性 ... ``` 在 `core/config/bootstrap.py` 的 `_discover_extension_providers` 中动态加载即可。 ### 环境变量引用 配置文件支持 `${VAR}` 与 `${VAR:-default}` 引用语法: ```yaml credentials: prod-web-01: password: ${PROD_PASSWORD} # 引用环境变量 key_passphrase: ${KEY_PASSPHRASE:-} # 引用,未设置则为 None ``` ### 配置诊断 server 启动时自动打印配置诊断信息: ``` 配置 providers: ['local-file'] 当前 workspace: projectA 配置诊断: group=ssh-client config_type=credentials found_scopes=['workspace', 'local'] 配置诊断: group=ssh-client config_type=hosts found_scopes=['workspace'] ``` --- ## 多项目部署 ### Workspace 隔离 通过 `MCP_WORKSPACE` 环境变量实现多项目配置隔离: ``` workspaces/ shared/ # 跨项目共享默认 ssh-client/ hosts.yaml projectA/ # MCP_WORKSPACE=projectA ssh-client/ hosts.yaml credentials.yaml projectB/ # MCP_WORKSPACE=projectB ssh-client/ hosts.yaml credentials.yaml ``` 不同项目配置各自的 MCP 客户端: ```json // 项目 A 的 .trae/mcp.json { "mcpServers": { "mcp-server": { "command": "D:\\program\\mcp\\.venv\\Scripts\\python.exe", "args": ["D:\\program\\mcp\\server.py", "start"], "cwd": "D:\\program\\mcp", "env": { "MCP_WORKSPACE": "projectA" } } } } ``` ### 部署模式 | 模式 | 适用场景 | 配置位置 | |------|----------|----------| | **单项目模式** | MCP server 与项目同主机 | `/.mcp/ssh-client/`(未设 `MCP_WORKSPACE`) | | **共享部署模式** | MCP server 独立部署,多项目共用 | `/workspaces//ssh-client/` | | **混合凭据模式** | 项目侧管主机清单,MCP server 侧管凭据 | 项目侧 `/.mcp/hosts.yaml`,MCP 侧 `workspaces//credentials.yaml` | ### 混合凭据方案(推荐) - **项目侧**(`hosts.yaml`):维护主机 alias + address + tags + mode,**不含敏感信息**,可提交 git - **MCP server 侧**(`credentials.yaml`):维护 alias → 凭据映射,**不入 git**,用 `${VAR}` 引用 - 通过 `alias` 关联两侧,智能体只接触 `alias`,不接触密码/密钥 --- ## 安全策略 ### 风险分级 `check_command_safety(command)` 返回三级风险: | 风险 | 说明 | 处理 | |------|------|------| | `read` | 只读命令(ls/cat/ps/grep/systemctl status 等) | 自动执行 | | `write` | 修改系统状态(systemctl restart/install/rm 文件等) | 按 `mode` 决定 | | `danger` | 高危命令(rm -rf /、mkfs、dd of=/dev/ 等) | **强制审批** | ### 审批机制 `mode=require_approval` 时 write 风险命令返回结构化 `ApprovalRequest`: ```json { "approval_id": "0f5cc985c80d", "status": "pending_approval", "tool": "manage_service", "host_alias": "prod-web-01", "will_execute": "systemctl restart nginx", "side_effects": "restart 服务 nginx(systemctl)", "reversible": true, "rollback_cmd": "systemctl start nginx", "risk_level": "write", "expires_at": 1786972205, "remaining_seconds": 300, "next_action": "调用 approve_action(approval_id='0f5cc985c80d') 批准执行,或 reject_action(...) 拒绝" } ``` - **TTL**:默认 300 秒,过期自动清理(惰性清理,无线程) - **闭环**:`approve_action` 执行并审计;`reject_action` 记录拒绝事件 - **auto_approve 模式**:write 自动执行,danger 仍强制审批 ### 命令黑名单 命中以下正则的命令直接抛 `DangerousCommandError`: - `rm -rf /` / `rm -rf /*` - `mkfs.* /dev/` - `dd of=/dev/` - `> /dev/sd[a-z]` - `iptables -F` / `iptables -P ... DROP` - `chmod -R 777 /` / `chown -R ... /` - fork bomb `:(){ :|:& };:` - `shutdown` / `reboot` / `halt` / `poweroff` / `init 0` / `init 6` - `curl ... | sh` / `wget ... | sh` ### 交互命令拦截 以下命令需 PTY,本分组不实现,命中抛 `InteractiveCommandError`: `top` / `htop` / `vim` / `vi` / `nano` / `emacs` / `less` / `more` / `tail -f` / `ssh` / `telnet` / `ftp` / `sftp` / `nc` / `screen` / `tmux` / `mysql` / `psql` / `mongo` / `redis-cli` / `python` / `node` / `ruby` / `perl` / `bash` / `sh`(REPL) 替代方案:`top -bn1` / `cat file` / `tail -n 100 file` / `mysql -e "..."` ### Shell 注入防护 用户输入参数(service 名、package 名等)通过 `shlex.quote` 包装: ```python build_safe_command("systemctl {0} {1}", action, service) # service="nginx; rm -rf /" → "systemctl restart 'nginx; rm -rf /'" ``` ### 审计日志 所有命令执行与审批决策落 `logs/audit.log`(JSONL 格式): ```json { "timestamp": 1786971905.123, "timestamp_iso": "2026-08-18T09:19:05+0800", "actor": "agent", "tool": "exec_command", "host_alias": "prod-web-01", "connection_id": "a1b2c3d4e5f6", "risk_level": "read", "command": "ps -ef | grep nginx", "exit_code": 0, "status": "success", "duration_ms": 120 } ``` `status` 字段:`success` / `failed` / `blocked`(命中黑名单)/ `denied`(被拒绝)/ `pending_approval`。 --- ## 如何新增一个分组 一个目录就是一个分组。在 `groups/` 下新建子目录,包含 `__init__.py` 与工具实现即可。 ### 步骤 1. 新建目录,例如 `groups/file_utils/` 2. 编写 `tools.py`,定义工具函数(需带类型注解与 docstring,MCP 据此生成 schema): ```python # groups/file_utils/tools.py from pathlib import Path def read_text(file_path: str) -> str: """读取文本文件内容。 Args: file_path: 文件路径。 Returns: 文件文本内容。 """ return Path(file_path).read_text(encoding="utf-8") ``` 3. 在 `__init__.py` 中声明分组元数据: ```python # groups/file_utils/__init__.py from .tools import read_text GROUP_NAME = "file-utils" GROUP_DESCRIPTION = "文件操作相关工具" GROUP_ENABLED = True # 默认启用,可被 group_state.json 覆盖 TOOLS = [read_text] ``` 4. 重启服务器或通过 MCP 调用 `reload_tools` 即可热加载,无需改主程序代码。 ### 分组元数据说明 | 变量 | 必填 | 说明 | |------|------|------| | `GROUP_NAME` | 是 | 分组唯一标识 | | `GROUP_DESCRIPTION` | 否 | 分组描述 | | `GROUP_ENABLED` | 否 | 默认是否启用(默认 `True`) | | `TOOLS` | 是 | 工具函数列表(至少一个) | --- ## 可拔插机制 - **持久化**:`group_state.json` 记录每个分组的启用状态,优先于分组默认 `GROUP_ENABLED` - **CLI 操作**:`python server.py disable db-client` 立即持久化,下次启动不注册该分组工具 - **运行时操作**:通过 MCP 调用 `disable_group` / `enable_group` 即时生效 - 禁用 → 该分组所有工具从服务器移除,客户端 `tools/list` 立即不再可见 - 启用 → 该分组工具重新注册,立即可调用 --- ## 架构设计 ``` ┌──────────────────────────────────────────────┐ │ server.py │ │ CLI (start / list / enable / disable / info)│ └───────────────┬──────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────┐ │ core/manager.py │ │ MCPServerManager │ │ - 持有 MCPServer 实例 │ │ - 动态注册管理工具 + 分组工具 │ │ - enable/disable/reload 热插拔 │ │ - start() 一键启动 │ └───────┬──────────────────────┬───────────────┘ │ │ ▼ ▼ ┌──────────────┐ ┌──────────────────────────┐ │ core/registry│ │ groups// │ │ GroupRegistry│ │ __init__.py 元数据 │ │ - discover │ │ tools.py 工具实现 │ │ - enable │ └──────────────────────────┘ │ - disable │ │ - persist │ └──────────────┘ ``` **核心流程:** 1. `GroupRegistry.discover()` 扫描 `groups/` 下所有子包,动态导入读取元数据 2. 合并 `group_state.json` 持久化状态确定每个分组是否启用 3. `MCPServerManager` 将启用分组的工具通过 `MCPServer.add_tool()` 注册 4. `MCPServer.run(transport)` 启动服务,客户端通过 MCP 协议发现并调用工具 --- ## 验证结果 本项目已通过端到端验证(基于 MCP JSON-RPC 协议): - `initialize` 握手成功,返回正确的 `serverInfo` 与 `instructions` - `tools/list` 发现全部 25 个工具(5 个管理工具 + 6 个 db-client 工具 + 14 个 ssh-client 工具) - `list_supported_databases` 返回 `['sqlite', 'mysql', 'postgresql']` - SQLite 全流程:连接 → 建表 → 插入 → 查询,返回正确结果 - `list_connections` 正确显示连接 id 与 db_type,多连接并存 - MySQL 未安装驱动时返回清晰错误(`pip install pymysql`),不影响服务器与其他数据库 - 不支持的 `db_type` 返回清晰错误 - `enable` / `disable` 持久化与即时生效均已验证 - SSH 分组未配置凭据时服务器正常启动,仅打印配置缺失警告 - SSH 分组工具调用命中危险黑名单(如 `rm -rf /`)返回 `DangerousCommandError` - SSH 分组工具调用交互式命令(如 `top`)返回 `InteractiveCommandError` - SSH 分组 write 风险命令在 `require_approval` 模式下返回结构化 `ApprovalRequest` - `approve_action` / `reject_action` / `list_pending_actions` 审批闭环已验证 - 命令执行、连接建立、审批决策均落 `logs/audit.log` 审计日志 --- ## 技术栈 - Python 3.13+ - [mcp](https://pypi.org/project/mcp/) >= 2.0.0(官方 MCP Python SDK) - [pyyaml](https://pypi.org/project/PyYAML/) >= 6.0(配置文件解析) - [paramiko](https://pypi.org/project/paramiko/) >= 3.4.0(SSH 客户端,ssh-client 分组用,延迟导入) - 标准库 `sqlite3`(db-client 分组用)