基于 Model Context Protocol (MCP) 的多服务器框架,支持同时运行多个独立的 MCP 服务器,每个服务器提供不同的功能。
本项目是一个 MCP 服务器框架,支持同时运行多个独立的 MCP 服务器实例。当前已实现的服务器包括:
- CMD MCP Server:提供本地命令执行和SSH远程命令执行功能
- 本地命令执行:在服务器本地执行 Shell 命令
- SSH 远程命令执行:通过 SSH 连接远程服务器执行命令
- File MCP Server:提供受控沙箱内的文件读写能力(默认端口 10002)
- 创建目录、写入/读取文本文件
- 列出目录内容、复制文件到指定目录
框架特性:
- 多服务器支持:可以同时运行多个独立的 MCP 服务器,每个服务器使用不同的端口
- 多传输方式支持:每个服务器支持 SSE、StreamableHTTP 和组合模式(combined)
- 健康检查:每个服务器提供独立的
/health端点用于服务健康检查 - 统一管理:通过统一的启动/停止脚本管理所有服务器
ops_mcp_server/
├── mcp_server/ # MCP 服务器代码
│ ├── cmd_mcp_server/ # CMD MCP 服务器(命令执行服务器)
│ │ ├── server.py # 服务器主程序
│ │ ├── service.py # 业务逻辑实现
│ │ └── test_mcp_server.py # 测试文件
│ ├── file_mcp_server/ # File MCP 服务器(文件管理服务器)
│ │ ├── server.py
│ │ ├── service.py
│ │ └── test_mcp_server.py
│ └── [其他服务器]/ # 可以添加更多服务器模块
│ ├── server.py
│ ├── service.py
│ └── test_mcp_server.py
├── mcp_client/ # MCP 客户端代码
│ ├── cmd_mcp_client.py # CMD MCP 客户端实现
│ ├── file_mcp_client.py # File MCP 客户端实现
│ └── [其他客户端]/ # 可以添加更多客户端
├── scripts/ # 启动/停止脚本
│ ├── run_cmd_mcp_server.sh # 启动 CMD 服务器脚本
│ ├── stop_cmd_mcp_server.sh # 停止 CMD 服务器脚本
│ ├── run_file_mcp_server.sh # 启动 File 服务器脚本
│ ├── stop_file_mcp_server.sh # 停止 File 服务器脚本
│ └── [其他服务器脚本]/ # 可以添加更多服务器脚本
├── run_all_mcp_servers.sh # 启动所有服务器(统一入口)
├── stop_all_mcp_servers.sh # 停止所有服务器(统一入口)
├── requirements.txt # Python 依赖包列表
├── INSTALL.md # 详细安装指南
└── workspace/ # 工作目录(自动创建)
└── logs/ # 日志目录(自动创建)
├── batch_run.log # 批量启动脚本日志
├── run_cmd_mcp_server.log # CMD 服务器运行日志
├── run_cmd_mcp_server_pid.log # CMD 服务器 PID
└── [其他服务器日志]/ # 其他服务器的日志
- Python 3.8+
- 虚拟环境:
~/mcp_venv(需要预先创建并安装依赖)
mcp- Model Context Protocol 库uvicorn- ASGI 服务器starlette- Web 框架anyio- 异步 IO 库paramiko- SSH 客户端库(用于远程命令执行)pydantic- 数据验证库
pytest- 测试框架requests- HTTP 请求库
重要提示:项目脚本中硬编码了虚拟环境路径为 ~/mcp_venv,请务必在此路径创建虚拟环境。
详细安装步骤请参考 INSTALL.md。
快速安装:
# 创建虚拟环境(必须在 ~/mcp_venv)
python3 -m venv ~/mcp_venv
# 激活虚拟环境
source ~/mcp_venv/bin/activate
# 安装依赖(推荐使用 requirements.txt)
pip install -r requirements.txt
# 或者仅安装核心依赖(不包含测试依赖)
pip install mcp uvicorn starlette anyio paramiko pydantic# 在项目根目录执行
./run_all_mcp_servers.sh# 激活虚拟环境
source ~/mcp_venv/bin/activate
# 启动 CMD MCP Server(默认端口 10001,combined 模式)
python3 mcp_server/cmd_mcp_server/server.py --port 10001 --transport combined
# 启动 File MCP Server(默认端口 10002,combined 模式)
python3 mcp_server/file_mcp_server/server.py --port 10002 --transport combined# 使用停止脚本
./stop_all_mcp_servers.sh
# 或手动停止
./scripts/stop_cmd_mcp_server.sh--port: 服务器端口- CMD MCP Server 默认
10001 - File MCP Server 默认
10002
- CMD MCP Server 默认
--transport: 传输方式,可选值:sse: 仅 SSE 传输streamable-http: 仅 StreamableHTTP 传输combined: 同时支持 SSE 和 StreamableHTTP(默认)stdio: 标准输入输出传输
所有日志文件统一保存在 workspace/logs/ 目录下,每个服务器有独立的日志文件:
batch_run.log: 批量启动脚本的执行日志(所有服务器共享)run_cmd_mcp_server.log: CMD 服务器运行日志run_cmd_mcp_server_pid.log: CMD 服务器进程 PID 记录run_file_mcp_server.log: File 服务器运行日志run_file_mcp_server_pid.log: File 服务器进程 PID 记录run_[服务器名]_mcp_server.log: 其他服务器的运行日志run_[服务器名]_mcp_server_pid.log: 其他服务器的 PID 记录
在 run_all_mcp_servers.sh 和 stop_all_mcp_servers.sh 中配置要启动/停止的服务器列表:
# scripts list
SCRIPTS=(
"scripts/run_cmd_mcp_server.sh"
"scripts/run_file_mcp_server.sh"
# 添加更多服务器脚本
# "scripts/run_xxx_mcp_server.sh"
)每个服务器使用不同的端口,在对应的启动脚本中配置端口号。
CMD MCP Server(默认端口 10001)提供以下 MCP 工具:
执行本地命令
参数:
command(string, 必需): 要执行的命令newSession(boolean, 可选): 是否在新会话中执行,默认false
示例:
{
"command": "ls -la",
"newSession": false
}File MCP Server(默认端口 10002)提供以下工具。所有路径均限定在 workspace/files/ 沙箱根目录下:
递归创建目录。
参数:
path(string, 必需): 相对路径parents(boolean, 可选): 是否递归创建父目录exist_ok(boolean, 可选): 目录存在时是否视为成功
{
"path": "project/logs",
"parents": true,
"exist_ok": true
}写入 UTF-8 文本文件,可控制覆盖策略。
{
"path": "project/config.yaml",
"content": "key: value",
"overwrite": true
}读取文本文件,可限制最大读取字节数。
{
"path": "project/config.yaml",
"maxBytes": 1048576
}列出目录内容,可选择是否递归及最大深度。
{
"path": "project",
"recursive": false
}复制文件到目标目录,必要时自动创建目录。
{
"source": "project/config.yaml",
"targetDir": "archive/2024",
"overwrite": true
}通过 SSH 执行远程命令
参数:
host(string, 必需): 远程主机地址username(string, 必需): SSH 用户名command(string, 必需): 要执行的命令port(integer, 可选): SSH 端口,默认22password(string, 可选): SSH 密码privateKey(string, 可选): SSH 私钥newSession(boolean, 可选): 是否在新会话中执行,默认false
示例:
{
"host": "192.168.1.100",
"username": "root",
"command": "df -h",
"port": 22,
"password": "your_password"
}每个服务器都提供独立的健康检查端点:
CMD MCP Server(端口 10001):
curl http://localhost:10001/healthFile MCP Server(端口 10002):
curl http://localhost:10002/health其他服务器:
# 根据实际端口访问
curl http://localhost:[端口]/health返回示例:
{
"status": "healthy",
"timestamp": "2024-01-01T12:00:00"
}import asyncio
from mcp_client.cmd_mcp_client import CmdMcpClient
async def main():
# 创建客户端(使用 SSE 传输)
client = CmdMcpClient(
remote_ip="localhost",
port=10001,
transport="sse"
)
# 执行本地命令
success, result = await client.execute_command("ls -la")
print(f"执行结果: {result}")
# 执行 SSH 远程命令
success, result = await client.execute_ssh_command(
host="192.168.1.100",
username="root",
command="df -h",
password="your_password"
)
print(f"SSH 执行结果: {result}")
if __name__ == "__main__":
asyncio.run(main())import asyncio
from mcp_client.file_mcp_client import FileMcpClient
async def main():
client = FileMcpClient(remote_ip="localhost", port=10002, transport="sse")
# 创建目录
success, result = await client.create_directory("demo")
print(success, result)
# 写入与读取文件
await client.write_file("demo/hello.txt", "hello file server")
success, result = await client.read_file("demo/hello.txt")
print(success, result)
# 列出目录内容
success, result = await client.list_directory("demo")
print(success, result)
if __name__ == "__main__":
asyncio.run(main())- 端点:
http://host:port/sse - 特点:单向流式传输,适合服务器主动推送数据
- 端点:
http://host:port/mcp - 特点:双向流式传输,支持更复杂的交互
- 同时支持 SSE 和 StreamableHTTP
- 客户端可以根据需要选择任意一种传输方式
批量启动所有 MCP 服务器的脚本。
功能:
- 自动创建日志目录
- 备份已存在的日志文件
- 按顺序启动所有服务器脚本
- 记录执行日志到
workspace/logs/batch_run.log
批量停止所有 MCP 服务器的脚本。
功能:
- 按顺序停止所有服务器
- 记录执行日志到
workspace/logs/batch_run.log
启动单个 CMD MCP 服务器的脚本。
功能:
- 检查服务器是否已在运行
- 激活虚拟环境
- 后台启动服务器(默认端口 10001)
- 记录 PID 和日志
配置说明:
- 修改
RUN_COMMAND变量可以更改服务器路径和端口 - 修改
LOG_FILE和PID_FILE可以更改日志路径
启动单个 File MCP 服务器的脚本。
- 默认端口:10002
- 激活虚拟环境后后台运行
mcp_server/file_mcp_server/server.py - 记录日志到
workspace/logs/run_file_mcp_server.log,PID 记录到workspace/logs/run_file_mcp_server_pid.log
停止单个 CMD MCP 服务器的脚本。
功能:
- 读取 PID 文件
- 检查进程是否运行
- 安全停止服务器进程
- 清理 PID 文件
停止 File MCP 服务器,逻辑与 CMD 版本一致,PID 文件为 workspace/logs/run_file_mcp_server_pid.log。
当添加新的 MCP 服务器时,需要创建对应的启动和停止脚本:
-
创建启动脚本
scripts/run_[服务器名]_mcp_server.sh:#!/bin/bash LOG_FILE="workspace/logs/run_[服务器名]_mcp_server.log" PID_FILE="workspace/logs/run_[服务器名]_mcp_server_pid.log" mkdir -p "$(dirname "$LOG_FILE")" source ~/mcp_venv/bin/activate RUN_COMMAND="python3 mcp_server/[服务器目录]/server.py --port [端口号]" # ... 其他逻辑参考 run_cmd_mcp_server.sh
-
创建停止脚本
scripts/stop_[服务器名]_mcp_server.sh:#!/bin/bash PID_FILE="workspace/logs/run_[服务器名]_mcp_server_pid.log" # ... 其他逻辑参考 stop_cmd_mcp_server.sh
-
更新批量脚本:在
run_all_mcp_servers.sh和stop_all_mcp_servers.sh的SCRIPTS数组中添加新脚本路径
-
虚拟环境路径:脚本中硬编码了虚拟环境路径
~/mcp_venv,如需修改请编辑对应的启动脚本 -
端口占用:每个服务器必须使用不同的端口,确保端口未被占用
- CMD MCP Server 默认端口:10001
- File MCP Server 默认端口:10002
- 添加新服务器时,请选择未被占用的端口
-
日志目录:所有日志自动保存在
workspace/logs/目录,该目录会被自动创建,每个服务器有独立的日志文件 -
服务器命名规范:建议使用统一的命名规范
- 服务器目录:
mcp_server/[服务器名]_mcp_server/ - 启动脚本:
scripts/run_[服务器名]_mcp_server.sh - 停止脚本:
scripts/stop_[服务器名]_mcp_server.sh - 日志文件:
workspace/logs/run_[服务器名]_mcp_server.log
- 服务器目录:
-
SSH 连接:使用 SSH 功能需要确保:
- 目标服务器可访问
- 提供正确的认证信息(密码或私钥)
- 网络连接正常
-
命令超时:本地命令执行默认超时时间为 5 分钟
-
代理设置:代码中会自动清除 HTTP 代理设置,避免云桌面代理问题
-
文件沙箱:File MCP Server 仅允许访问
workspace/files/,超出该目录的请求会被拒绝
- 检查虚拟环境是否正确激活
- 检查端口是否被占用:
netstat -tuln | grep [端口号] - 查看对应服务器的日志文件:
workspace/logs/run_[服务器名]_mcp_server.log - 检查启动脚本中的路径和命令是否正确
- 确认服务器代码文件存在且可执行
- 检查命令语法是否正确
- 检查执行权限
- 查看服务器日志获取详细错误信息
- 检查网络连通性:
ping <host> - 检查 SSH 端口是否正确
- 验证用户名和密码/私钥是否正确
- 检查目标服务器是否允许 SSH 连接
要添加一个新的 MCP 服务器,请按照以下步骤操作:
mkdir -p mcp_server/[服务器名]_mcp_server参考 mcp_server/cmd_mcp_server/ 的结构创建以下文件:
server.py: 服务器主程序,实现 MCP 服务器逻辑service.py: 业务逻辑实现test_mcp_server.py: 测试文件(可选)
在 server.py 中:
from mcp.server.fastmcp import FastMCP
import mcp.types as types
def build_mcp_server(port: int = [端口号]) -> FastMCP:
mcp = FastMCP(name="[服务器名]_mcp_server", host="0.0.0.0", port=port)
@mcp._mcp_server.list_tools()
async def list_tools() -> list[types.Tool]:
# 定义工具列表
return [...]
@mcp._mcp_server.call_tool()
async def call_tool(name: str, arguments: dict):
# 实现工具调用逻辑
...
return mcp参考 scripts/run_cmd_mcp_server.sh 和 scripts/stop_cmd_mcp_server.sh 创建对应的脚本。
在 run_all_mcp_servers.sh 和 stop_all_mcp_servers.sh 的 SCRIPTS 数组中添加新脚本:
SCRIPTS=(
"scripts/run_cmd_mcp_server.sh"
"scripts/run_[服务器名]_mcp_server.sh" # 添加新服务器
)# 单独测试新服务器
./scripts/run_[服务器名]_mcp_server.sh
# 或批量测试所有服务器
./run_all_mcp_servers.sh- 在对应服务器的
service.py中实现业务逻辑 - 在
server.py的list_tools()中注册工具 - 在
call_tool()中添加工具调用逻辑
运行测试文件:
# 测试 CMD MCP Server
python3 mcp_server/cmd_mcp_server/test_mcp_server.py
# 测试其他服务器
python3 mcp_server/[服务器名]_mcp_server/test_mcp_server.py[根据实际情况填写]
[根据实际情况填写]