|
|
hai 1 semana | |
|---|---|---|
| docs | hai 1 semana | |
| services | hai 1 semana | |
| tests | hai 1 semana | |
| tools | hai 1 semana | |
| utils | hai 2 semanas | |
| .coveragerc | hai 2 semanas | |
| .env.example | hai 2 semanas | |
| .gitignore | hai 2 semanas | |
| COVERAGE_GUIDE.md | hai 2 semanas | |
| README.md | hai 2 semanas | |
| app.py | hai 1 semana | |
| config.py | hai 2 semanas | |
| constants.py | hai 2 semanas | |
| mcp_protocol.py | hai 1 semana | |
| public_gateway.py | hai 1 semana | |
| public_server.py | hai 1 semana | |
| requirements.txt | hai 2 semanas | |
| run_coverage.py | hai 2 semanas |
这是物流系统给 Workbuddy 使用的轻量 MCP Gateway。它负责接收 MCP 请求、管理员工授权会话,并把工具调用转发到现有 ThinkPHP 项目的 MCP 接口。
Gateway 保持“薄网关”边界:
mcp_token、员工状态、公司、工具白名单和业务数据权限的最终校验。.env 优先的配置加载。serve-stdio。serve-public。GWS_xxx 设备配置。query_order 订单查询工具。query_track 轨迹查询工具。initialize、tools/list、tools/call。gateway_session_id 请求级隔离、Redis Gateway session、审计日志和基础限流。适合员工本机 Workbuddy 通过命令方式拉起 Gateway。
python \\192.168.1.241\chenjiacheng\mcp\app.py serve-stdio
Workbuddy 配置示例:
{
"mcpServers": {
"fms": {
"command": "python",
"args": [
"\\\\192.168.1.241\\chenjiacheng\\mcp\\app.py",
"serve-stdio"
]
}
}
}
不要把 Y:\mcp\app.py 写进员工 Workbuddy 配置;Y: 只是开发机映射盘视角,员工机器未必存在。
本地 stdio 模式只保留给开发排障和已有 token 会话的兼容调用;员工正式使用路径统一走公网 HTTP 模式,由后台直接生成 Workbuddy 设备配置,不再通过授权码绑定。
适合把 Gateway 部署成公网共享服务,由多个员工的 Workbuddy 远程访问同一个 Gateway。
启动命令:
python app.py serve-public --host 0.0.0.0 --port 8765
生产部署必须满足:
.mcp_token.json 保存公网用户 token。FMS_SESSION_KEY 表示公网用户。gateway_session_id。公网模式当前支持三种 session 传递方式,优先级如下:
X-Gateway-Session: GWS_xxxAuthorization: Bearer GWS_xxxgateway_session_id=GWS_xxx如果 Workbuddy 远程 MCP 无法稳定传递 header、Bearer 或 cookie 中任意一种会话身份,公网共享 Gateway 不能生产放量。
员工在后台或 home 的“连接 Workbuddy”入口新增设备配置。base 会一次性完成:
GWS_xxx。st_mcp_gateway_session。st_mcp_token_session。GWS_xxx 的 Workbuddy 配置。Workbuddy 配置示例:
{
"mcpServers": {
"fms-public": {
"url": "http://192.168.1.241:8765/mcp",
"headers": {
"X-Gateway-Session": "GWS_xxx"
}
}
}
}
后续工具调用必须继续带同一个 gateway_session_id:
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "query_order",
"arguments": {
"keyword": "USC26070371955",
"page": 1,
"limit": 20
}
}
}
Gateway 根据 gateway_session_id 从 Redis 读取当前员工的 mcp_token,再把请求转发到 fmsoperate 的 /mcp/tools/queryOrder。
mcp_token 失效。FMS_GATEWAY_SESSION_TTL_SECONDS 调整。当前公网 MCP 服务端口是 8765,对外地址类似:
http://192.168.1.241:8765/mcp
注意:当前项目没有单独的 restart 子命令,也没有 Windows 服务管理脚本。这里说的“重启”就是先停掉旧的 serve-public 进程,再用启动命令重新拉起。
如果你只是想停掉旧服务,不马上重新启动,执行这两行就够了:
$conn = Get-NetTCPConnection -LocalPort 8765 -State Listen -ErrorAction SilentlyContinue | Select-Object -First 1
if ($conn) { Stop-Process -Id $conn.OwningProcess -Force }
第一行是查找谁在监听 8765 端口;第二行才是真正停止这个进程。执行后如果 $conn 为空,说明当前没有旧服务在运行。
想先确认要停的是哪个进程,可以在 Stop-Process 前执行:
if ($conn) { Get-Process -Id $conn.OwningProcess }
只停止以后,/health 检查会连不上,这是正常的;要恢复服务再执行下面的启动命令。
$conn = Get-NetTCPConnection -LocalPort 8765 -State Listen -ErrorAction SilentlyContinue | Select-Object -First 1
if ($conn) { Stop-Process -Id $conn.OwningProcess -Force }
Set-Location Y:\mcp
python app.py serve-public --host 0.0.0.0 --port 8765
如果服务就在你当前这个 PowerShell 窗口里运行,也可以先按 Ctrl+C 停掉旧服务,然后再执行启动命令:
Set-Location Y:\mcp
python app.py serve-public --host 0.0.0.0 --port 8765
后续如果接入 NSSM、计划任务、Windows 服务或其他进程管理器,再把这里改成对应的 restart 命令。
重启后用下面命令确认服务已经起来:
Invoke-WebRequest http://127.0.0.1:8765/health -UseBasicParsing
正常情况下会返回包含 ok 的 JSON。## 常用命令
查看工具列表:
python app.py list-tools
直接调用订单查询:
python app.py call --tool query_order --keyword USC26070371955 --page 1 --limit 20
直接调用轨迹查询:
python app.py call --tool query_track --order-number USC26070371955
运行全部测试:
python -m unittest discover -s tests -p "test_*.py"
基础配置:
FMS_AUTH_BASE=http://chenjiacheng.base.dahuo.fudingri.com
FMS_TOOLS_BASE=http://chenjiacheng.fmsoperate.dahuo.fudingri.com
FMS_CLIENT_TYPE=workbuddy
FMS_TIMEOUT_SECONDS=10
FMS_REFRESH_SKEW_SECONDS=120
FMS_LOG_LEVEL=info
本地 stdio 推荐 Redis token store:
FMS_TOKEN_STORE=redis
FMS_REDIS_HOST=192.168.1.241
FMS_REDIS_PORT=6379
FMS_REDIS_DB=20
FMS_REDIS_PASSWORD=
FMS_REDIS_PREFIX=fms:mcp:workbuddy:
公网模式推荐配置:
FMS_GATEWAY_MODE=public
FMS_GATEWAY_PUBLIC_BASE=https://mcp.example.com
FMS_GATEWAY_SESSION_TTL_SECONDS=2592000
FMS_TOKEN_STORE=redis
FMS_REDIS_HOST=127.0.0.1
FMS_REDIS_PORT=6379
FMS_REDIS_DB=20
FMS_REDIS_PASSWORD=change-me
FMS_REDIS_PREFIX=fms:mcp:gateway:
兼容旧配置键:
MCP_AUTH_BASE_URLMCP_TOOLS_BASE_URLMCP_CLIENT_TYPEMCP_TIMEOUT_SECONDSMCP_REFRESH_SKEW_SECONDSMCP_LOG_LEVELMCP_TOKEN_STOREMCP_TOKEN_STORE_PATHMCP_REDIS_HOSTMCP_REDIS_PORTMCP_REDIS_DBMCP_REDIS_PASSWORDMCP_REDIS_PREFIXMCP_SESSION_KEY说明:
config.py 会自动读取 mcp/.env。.env 优先,系统环境变量只作为兜底。FMS_API_BASE,会同时作为 auth 和 tools 的基础地址。FMS_AUTH_BASE 和 FMS_TOOLS_BASE。本地 stdio 模式:
fms:mcp:workbuddy:{session_key}。session_key 默认由员工本机 COMPUTERNAME / USERNAME 生成。.env 中不要写死 FMS_SESSION_KEY。公网 HTTP 模式:
fms:mcp:gateway:session:{sha256(gateway_session_id)}。gateway_session_id。mcp_token 返回给 Workbuddy 展示层。gateway_session_id、mcp_token 或授权码。文件存储仅用于开发排障:
FMS_TOKEN_STORE=file
FMS_TOKEN_STORE_PATH=C:/fms-mcp/.mcp_token.json
如果使用网络共享 app.py,不要把文件 token store 指向 \\192.168.1.241\chenjiacheng\mcp\ 共享目录。
ThinkPHP MCP 路由位于各后端项目的 route/mcp/mcp_route.php。
Gateway 会追加以下路径:
/mcp/auth/exchange、/mcp/auth/refresh、/mcp/auth/revoke/mcp/tools/queryOrder、/mcp/tools/queryTrackFMS_AUTH_BASE 和 FMS_TOOLS_BASE 只配置域名或基础地址,不要包含 /admin/mcp。
如果 Workbuddy 提示:
Unexpected UTF-8 BOM (decode using utf-8-sig)
说明后端工具接口返回的 JSON 前面带了 UTF-8 BOM。当前 Gateway 已在 HTTP JSON 解码层使用 utf-8-sig 兼容。先运行全量测试确认当前文件是否已包含修复:
python -m unittest discover -s tests -p "test_*.py"
Windows 下 Python 标准输出可能使用本机控制台编码。当前 mcp_protocol.py 已将 stdio JSON-RPC 响应改为 ensure_ascii=True,传输行只包含 ASCII JSON 转义,客户端解析后仍是正常中文。
如果仍然乱码,优先确认 Workbuddy 启动的是最新的 \\192.168.1.241\chenjiacheng\mcp\app.py。
gateway_session_id。