|
|
vor 3 Tagen | |
|---|---|---|
| docs | vor 6 Tagen | |
| services | vor 3 Tagen | |
| tests | vor 3 Tagen | |
| tools | vor 3 Tagen | |
| utils | vor 6 Tagen | |
| .coveragerc | vor 2 Wochen | |
| .env.example | vor 4 Tagen | |
| .gitignore | vor 1 Woche | |
| AGENTS.md | vor 4 Tagen | |
| README.md | vor 3 Tagen | |
| app.py | vor 3 Tagen | |
| config.py | vor 4 Tagen | |
| constants.py | vor 2 Wochen | |
| mcp_protocol.py | vor 4 Tagen | |
| public_gateway.py | vor 3 Tagen | |
| public_server.py | vor 4 Tagen | |
| requirements.txt | vor 2 Wochen | |
| run_coverage.py | vor 2 Wochen |
这是物流系统给 Workbuddy 使用的轻量 MCP Gateway。它负责接收 MCP 请求、解析并转发设备会话,并把工具调用转发到现有 ThinkPHP 项目的 MCP 接口;员工认证与设备会话签发仍由 base 负责。
Gateway 保持“薄网关”边界:
mcp_token、员工状态、公司、工具白名单和业务数据权限的最终校验。.env 优先的配置加载。serve-stdio。serve-public。GWS_xxx 设备配置。initialize、tools/list、tools/call。gateway_session_id 请求级隔离、Redis Gateway session、审计日志和基础限流。GatewayApp 与 PublicGatewayApp 当前注册以下 13 个候选工具:
| MCP 工具 | 用途 | ThinkPHP 路由 | 最终展示 |
|---|---|---|---|
query_order |
普通订单列表查询 | /mcp/tools/queryOrder |
旧协议兼容 |
query_track |
按订单或物流号码查询轨迹 | /mcp/tools/queryTrack |
安全表格 |
query_order_exact |
按明确号码类型精准查询订单 | /mcp/tools/queryOrderExact |
安全表格 |
query_order_detail |
按明确订单号查询订单详情,支持“全部”聚合 | /mcp/tools/queryOrderDetail |
安全中文详情 |
query_customs_declaration_files |
按订单号或排舱单号查询报关资料 | /mcp/tools/queryCustomsDeclarationFiles |
安全表格 |
query_outbound_list |
按业务阶段和筛选条件查询排舱列表 | /mcp/tools/queryOutboundList |
安全表格 |
query_outbound_detail |
按排舱单号查询排舱汇总与订单明细 | /mcp/tools/queryOutboundDetail |
安全详情 |
list_outbound_filter_options |
查询七类排舱筛选项 | /mcp/tools/listOutboundFilterOptions |
安全筛选项 |
list_order_filter_options |
查询精准订单筛选项 | /mcp/tools/listOrderFilterOptions |
安全筛选项 |
export_pending_outbound_orders |
提交未排舱订单异步导出 | /mcp/tools/exportPendingOutboundOrders |
签名任务引用 |
export_out_of_province_port_data |
提交省外进港资料异步导出 | /mcp/tools/exportOutOfProvincePortData |
签名任务引用 |
query_export_task |
查询异步导出状态或文件 | /mcp/tools/queryExportTask |
安全任务状态/文件链接 |
list_pending_outbound_export_filter_options |
查询未排舱导出筛选项 | /mcp/tools/listPendingOutboundExportFilterOptions |
安全筛选项 |
异步导出流程:
export_pending_outbound_orders 或 export_out_of_province_port_data 提交任务,返回 task_refretry_after_seconds)后,使用 query_export_task 和 task_ref 查询状态query_export_task 响应获取下载链接(files[].url)这 13 个名称只是 Gateway 的本地候选集合。员工在 tools/list 中实际看到、在 tools/call 中实际可调用的工具,始终是”Gateway 本地注册集合”与 fmsoperate 当前动态启用列表的交集;动态列表缺失、格式错误或查询失败时关闭访问,不回退为全量开放。
MCP 能力声明为 tools.listChanged=false。工具名称、Schema、说明或注册集合变化后,必须重启对应 Gateway 进程并让客户端重新连接,客户端才会重新获取工具列表。
Gateway 在 tools/call 最终边界处理展示字段,不改变 ThinkPHP 内部接口和工具入参:
query_order 保持原有 columns + records 结果和文本展示,不参与本次转换。services/output_presenter.py 对其余 12 个安全工具执行显式白名单展示。query_order_exact、query_track、query_customs_declaration_files、query_outbound_list 对外使用中文 headers + rows + pagination,不返回内部字段键。query_outbound_detail 使用中文 summary + details + pagination 两层结构。query_order_detail 按中文详情模块返回固定分组或明细;传入“全部”时一次返回概览和十个明细模块,各明细模块独立分页。附件保留文件名、预览与下载链接;后端机器字段和内部 ID 不进入最终展示。task_ref + queued + retry_after_seconds。客户端稍后在新的调用中使用 query_export_task;完成后才返回 files[].label + files[].url。Gateway 不在一次调用内等待或循环轮询。request_id 位于 MCP 结果 _meta;参数错误使用业务名称,未知异常不透传后端细节。tools/call 缺少有效工具名时返回 JSON-RPC -32602 Invalid params,不包装为业务 isError。services/output_presenter.py;未知工具或畸形响应关闭失败。详细设计见集中文档仓的 技术规格 Presenter 分类。
省外进港资料导出使用 outbound_numbers、container_codes、bl_numbers、so_numbers 四个号码数组之一,并同时提供 file_type=NB/SH/MS。用户未明确号码类型时,AI 必须先让用户从排舱单号、柜号、提单号、SO 号中选择,确认前不得调用;提单号指后台排舱单列表的普通提单号,SO 号对应 fms_booking_detail.so_number。
排舱列表查询的 outbound_status 必填,用户未说明排舱阶段时必须先询问;shipping_method 可选,未提供时查询空运、海运和陆运全部,只有用户明确指定后才按运输方式筛选。
AGENTS.md:项目所有权、红线、协作规则和验证命令。README.md:当前工具目录、接入配置、运行方式、运维与排障。Y:/all_project_docs/mcp/guides/mcp-team-sharing.md:面向团队的从 0 到 1 架构、实践与问题复盘分享稿。Y:/all_project_docs/mcp/overview.md:当前系统概况与跨仓职责。requirements.md:现行协议、安全、工具选择和输出合同。tech-specs.md:组件、会话、展示、限流、追踪与日志设计。user-structure.md:员工、调试、工具选择和跨仓开发流程。timeline.md:短里程碑和当前交接状态。临时规划与已完成的执行计划不作为现行合同;交付后应把稳定事实并回上述入口,并从活动工作区清理过程文件。
适合员工本机 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。
用户已确认 Y:\fmsoperate\sql 原有 10 个 MCP SQL 和 Y:\base\sql 中 5 个 MCP SQL 均已执行。订单详情与异步导出任务查询的注册 SQL 仍待目标环境审核与执行,首次注册默认关闭;实际可见工具始终以 fmsoperate 动态注册表当前启用值为准。
仓库静态内容无法证明运行中的 Gateway 是否已加载当前代码,也无法证明 Workbuddy 是否已重新连接。发布工具或 Schema 变更后,运维交接必须分别确认:
initialize 和 tools/list。tools/list 返回的动态工具集合符合当前员工权限与预期启用状态。查看工具列表:
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 app.py call --tool query_customs_declaration_files --order-numbers ORD001,ORD002 --page 1 --limit 20
按排舱单号批量查询报关资料:
python app.py call --tool query_customs_declaration_files --outbound-numbers PC001,PC002 --page 1 --limit 20
order_numbers 只接收后台订单列表及排舱详情“订单号”列展示的订单号,不接收系统单号、内部 order_id/id、客户参考号、快递单号或排舱单号。只有用户明确说明“订单号”或“排舱单号”后才能调用;如果未明确号码类型,AI 必须先提问让用户二选一,确认前不得调用。两种号码数组不能同时传入。
运行全部测试:
python -m unittest discover -s tests -p "test_*.py"
严格覆盖率验收:
python -m coverage run -m unittest discover -s tests -p 'test_*.py'
python -m coverage report -m --fail-under=100
基础配置:
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:
Gateway 诊断事件上报默认关闭。Support 的 internal collector、Mongo 索引和
HMAC 密钥部署完成后,在 Gateway .env 增加:
四项目统一发布时按集中文档仓的 MCP 排障中心全版本部署清单 操作;Gateway 必须放在 Collector、Mongo 索引和两套 PHP Worker 之后灰度启用。
MCP_DIAGNOSIS_ENABLED=true
MCP_DIAGNOSIS_URL=https://support.example.com/internal/mcp-diagnostics/events
MCP_DIAGNOSIS_KEY_ID=gateway-current
MCP_DIAGNOSIS_SECRET=replace-with-at-least-32-random-characters
MCP_DIAGNOSIS_ALLOW_INSECURE_HTTP=false
MCP_DIAGNOSIS_QUEUE_SIZE=1000
MCP_DIAGNOSIS_BATCH_SIZE=100
MCP_DIAGNOSIS_TIMEOUT_SECONDS=0.5
MCP_DIAGNOSIS_INITIAL_BACKOFF_SECONDS=0.25
MCP_DIAGNOSIS_MAX_BACKOFF_SECONDS=5.0
生产环境 MCP_DIAGNOSIS_URL 必须使用 HTTPS。只有隔离测试环境可显式设置 MCP_DIAGNOSIS_ALLOW_INSECURE_HTTP=true 使用 HTTP,默认值 false 会使 HTTP 配置回退为 NullDiagnosticReporter。
MCP_DIAGNOSIS_KEY_ID 和 MCP_DIAGNOSIS_SECRET 必须与 Support
mcp_diagnosis.php 的 Gateway current/previous key 对应。Reporter 使用内存有界队列、
批量 HMAC 和短超时异步发送;Support 超时、拒绝、队列满或线程启动失败均不改变 MCP
响应。进程异常退出时尚未发送的内存事件可能丢失,因此该链路用于排障观测,不作为业务
审计唯一依据。
兼容旧配置键:
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/refresh、/mcp/auth/revoke;正式公网设备配置由 base 的登录态设备接口创建,不调用已退役的 /mcp/auth/exchange。/mcp/tools/listEnabledTools。/mcp/tools/queryOrder、/mcp/tools/queryTrack、/mcp/tools/queryOrderExact、/mcp/tools/queryOrderDetail、/mcp/tools/queryCustomsDeclarationFiles、/mcp/tools/queryOutboundList、/mcp/tools/queryOutboundDetail。/mcp/tools/listOutboundFilterOptions、/mcp/tools/listOrderFilterOptions、/mcp/tools/listPendingOutboundExportFilterOptions。/mcp/tools/exportPendingOutboundOrders、/mcp/tools/exportOutOfProvincePortData、/mcp/tools/queryExportTask。FMS_AUTH_BASE 和 FMS_TOOLS_BASE 只配置域名或基础地址,不要包含 /admin/mcp。
跨 Gateway、PHP 和数据库访问日志的排查顺序见集中文档仓的 mcp-team-sharing.md“排错时怎么看日志”章节。
若 Support 页面只能看到 fmsoperate 事件、看不到 Gateway 前置阶段,依次确认:
.env 的 MCP_DIAGNOSIS_ENABLED=true,URL 指向已部署的 internal collector。不要在日志、命令历史、工单或群聊中打印 MCP_DIAGNOSIS_SECRET、请求体、GWS_*、
MT_*、Authorization 或 Cookie。Gateway 事件只保存 GWS_* 的 SHA-256 前 12 位。
如果 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。