|
|
2 hete | |
|---|---|---|
| services | 2 hete | |
| tests | 2 hete | |
| tools | 2 hete | |
| .env.example | 2 hete | |
| .gitignore | 2 hete | |
| README.md | 2 hete | |
| app.py | 2 hete | |
| config.py | 2 hete | |
| mcp_protocol.py | 2 hete | |
| requirements.txt | 2 hete |
这是一个轻量的 Python MCP Gateway,用来把 Workbuddy 的 MCP 工具调用转发到现有 ThinkPHP 授权接口和工具接口。
当前仓库已经具备一个可运行的 stdio MCP 入口,以及一套经过测试的核心模块,覆盖:
.env 优先的配置加载session_key 隔离auth_code 换票、续期、失效的认证客户端bind_auth_code 授权绑定工具query_order 工具注册initialize / tools/list / tools/call 请求处理当前实现仍然保持“薄网关”原则:
query_order 开始员工侧 Workbuddy 使用网络 UNC 路径启动 Gateway:
{
"mcpServers": {
"fms": {
"command": "python",
"args": [
"\\\\192.168.1.241\\mcp\\app.py",
"serve-stdio"
]
}
}
}
不要把 Y:\mcp\app.py 写进 Workbuddy 配置;那只是开发机映射盘视角。
以下命令在 mcp/ 目录下执行。
python app.py list-tools
python app.py bind --auth-code AUTH123
员工正式使用时不需要执行这条命令。Workbuddy 拉起 serve-stdio 后,员工应在 Workbuddy 中调用 bind_auth_code 工具完成绑定。
python app.py call --tool query_order --keyword SO20260706001 --page 1 --limit 20
python \\192.168.1.241\chenjiacheng\mcp\app.py serve-stdio
这个命令可作为 Workbuddy 本地命令模式下的 MCP 服务入口,由 Workbuddy 自动拉起,不要求员工手动执行。
当前代码直接支持以下文档口径的配置键:
FMS_API_BASEFMS_AUTH_BASEFMS_TOOLS_BASEFMS_CLIENT_TYPEFMS_TIMEOUT_MSFMS_TIMEOUT_SECONDSFMS_REFRESH_SKEW_SECONDSFMS_LOG_LEVELFMS_TOKEN_STOREFMS_TOKEN_STORE_PATHFMS_REDIS_HOSTFMS_REDIS_PORTFMS_REDIS_DBFMS_REDIS_PASSWORDFMS_REDIS_PREFIXFMS_SESSION_KEY同时兼容早期实现中的这些键名:
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 优先,系统环境变量只在 .env 缺少该配置时兜底。FMS_API_BASE,当前实现会同时把它作为 auth 和 tools 的基础地址。base 与 fmsoperate 部署在不同域名,应分别提供 FMS_AUTH_BASE 与 FMS_TOOLS_BASE。当前 mcp/ 目录建议使用 .env 保存共享 Gateway 配置。
推荐配置如下:
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
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:
不要在共享 .env 中写死 FMS_SESSION_KEY。默认情况下 Gateway 会用员工本机的 COMPUTERNAME / USERNAME 自动生成稳定 session_key,Redis key 形如:
fms:mcp:workbuddy:{session_key}
这样多个员工通过同一个 \\192.168.1.241\chenjiacheng\mcp\app.py 启动时,也不会共用同一个 token。
当前推荐使用 Redis:
FMS_TOKEN_STORE=redis
Redis 存储行为:
bind_auth_code 成功后把 mcp_token 写入 RedisFMS_REDIS_PREFIX + session_keysession_key 优先读取 FMS_SESSION_KEY,未配置时自动从员工本机信息生成revoke 成功后会删除当前 session 的 Redis key文件存储仍保留为兜底方式:
FMS_TOKEN_STORE=file
FMS_TOKEN_STORE_PATH=C:/fms-mcp/.mcp_token.json
如果使用网络共享 app.py,不要把 FMS_TOKEN_STORE_PATH 指向 \\192.168.1.241\chenjiacheng\mcp\ 共享目录。
当前员工正式使用路径是:
auth_codepython \\192.168.1.241\chenjiacheng\mcp\app.py serve-stdio 拉起 Gatewaybind_auth_code,输入授权码完成绑定exchange 时带上 session_keyquery_order 等工具python app.py bind --auth-code ... 仅保留给开发、联调和排障使用。
python -m unittest discover -s tests -p "test_*.py"
当前 Gateway 已可作为本地 stdio MCP 进程使用,但仍保持最小边界:
第一阶段不开放写入型工具
如果 Workbuddy 调用 query_order 时提示:
Unexpected UTF-8 BOM (decode using utf-8-sig)
说明后端工具接口返回的 JSON 前面带了 UTF-8 BOM。当前 Gateway 已在 HTTP JSON 解码层使用 utf-8-sig 做兼容。排查时先在 mcp/ 目录运行:
python -m unittest discover -s tests -p "test_*.py"
若全量测试通过但 Workbuddy 仍报同样错误,优先确认 Workbuddy 启动的 app.py 是否为 \\192.168.1.241\chenjiacheng\mcp\app.py 的最新文件。
ThinkPHP MCP routes now live in each backend project's route/mcp/mcp_route.php.
The Gateway appends these paths to FMS_AUTH_BASE and FMS_TOOLS_BASE:
/mcp/auth/exchange, /mcp/auth/refresh, /mcp/auth/revoke/mcp/tools/queryOrderKeep FMS_AUTH_BASE and FMS_TOOLS_BASE as host/base-domain values. Do not include /admin/mcp in these environment variables.
如果后端 HTTP 返回、保存到 UTF-8 文件都正常,但 Workbuddy/AI 调 MCP 工具时中文显示成乱码,优先检查 Python Gateway 的 serve-stdio 输出层。
本次踩坑原因:Windows 下 Python 标准输出可能使用本机控制台编码,而 MCP stdio 客户端按 JSON/UTF-8 读取时会出现中文乱码。当前 mcp_protocol.py 已将 JSON-RPC stdio 响应改为 json.dumps(..., ensure_ascii=True),传输行只包含 ASCII JSON 转义;客户端解析 JSON 后仍是正常中文。
排查顺序:
ApiClient 能用 utf-8-sig 正常解析后端 JSON。serve-stdio 输出不是直接吐本机编码中文。验证命令:
python -m unittest discover -s tests -p "test_*.py"
query_track now accepts order_id, order_number, or tracking_number.
order_id, the backend keeps using TrackLogic::fmsOrderQueryTrack($orderId).tracking_number, or with only order_number, the backend calls TrackLogic::queryTrack($number) directly. This matches the existing fmsoperate page admin/Order/trackInfo.html?order_number=xxx./mcp/tools/queryTrack; it does not resolve orders locally or connect to the business database.Debug command:
python app.py call --tool query_track --tracking-number 1471904540000000301 --page 1 --limit 20
Root cause: the backend page has session('timezone') from the normal login session, while MCP tool calls rebuild session data from mcp_token. The fmsoperate MCP middleware was reading timezone from st_user, but the normal backend session uses the company's timezone (st_company.timezone_id -> st_timezone.value). When timezone was empty, CommonHelper::changeDateByTimeZone($time, 0, '') returned the original UTC time.
Fix: fmsoperate McpTokenLogic::checkToken() now loads the company timezone and returns a normalized session_context; CheckMcpToken writes session('timezone') from that context before calling tool logic. This keeps query_track aligned with admin/Order/trackInfo.html?order_number=xxx.