mcp服务

jackson fa6ae6ccc4 mcp 2 hete
services fa6ae6ccc4 mcp 2 hete
tests fa6ae6ccc4 mcp 2 hete
tools 0fbd342225 mcp 2 hete
utils fa6ae6ccc4 mcp 2 hete
.coveragerc fa6ae6ccc4 mcp 2 hete
.env.example fa6ae6ccc4 mcp 2 hete
.gitignore fa6ae6ccc4 mcp 2 hete
COVERAGE_GUIDE.md fa6ae6ccc4 mcp 2 hete
README.md fa6ae6ccc4 mcp 2 hete
app.py fa6ae6ccc4 mcp 2 hete
config.py fa6ae6ccc4 mcp 2 hete
constants.py fa6ae6ccc4 mcp 2 hete
mcp_protocol.py 0fbd342225 mcp 2 hete
public_gateway.py fa6ae6ccc4 mcp 2 hete
public_server.py fa6ae6ccc4 mcp 2 hete
requirements.txt f0ff40881c mcp服务 2 hete
run_coverage.py fa6ae6ccc4 mcp 2 hete

README.md

# Python MCP Gateway

这是物流系统给 Workbuddy 使用的轻量 MCP Gateway。它负责接收 MCP 请求、管理员工授权会话,并把工具调用转发到现有 ThinkPHP 项目的 MCP 接口。

Gateway 保持“薄网关”边界:

  • 不直接连接业务数据库。
  • 不复制 ThinkPHP 的业务权限逻辑。
  • 不在 Python 内判断订单、费用、轨迹等业务权限。
  • ThinkPHP 继续负责 mcp_token、员工状态、公司、工具白名单和业务数据权限的最终校验。

当前能力

  • 支持 .env 优先的配置加载。
  • 支持本地 stdio MCP 模式:serve-stdio
  • 支持公网 HTTP JSON-RPC 模式:serve-public
  • 支持 Redis token/session 存储。
  • 支持文件 token store 作为开发排障兜底。
  • 支持 bind_auth_code 授权绑定工具。
  • 支持 query_order 订单查询工具。
  • 支持 query_track 轨迹查询工具。
  • 支持 MCP initializetools/listtools/call
  • 公网模式支持 gateway_session_id 请求级隔离、Redis Gateway session、审计日志和基础限流。

两种运行模式

本地 stdio 模式

适合员工本机 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: 只是开发机映射盘视角,员工机器未必存在。

本地模式下,员工流程为:

  1. 员工在后台获取 Workbuddy 授权码。
  2. Workbuddy 自动拉起 serve-stdio
  3. 员工在 Workbuddy 中调用 bind_auth_code,输入授权码完成绑定。
  4. Gateway 调用 /mcp/auth/exchange,并带上本机生成的 session_key
  5. token 存入 Redis 当前员工对应 key。
  6. 后续直接使用 query_orderquery_track 等工具。

公网 HTTP 模式

适合把 Gateway 部署成公网共享服务,由多个员工的 Workbuddy 远程访问同一个 Gateway。

启动命令:

python app.py serve-public --host 0.0.0.0 --port 8765

生产部署必须满足:

  • 必须放在 HTTPS 反向代理后面。
  • Redis 必须在内网并开启密码。
  • 不使用 .mcp_token.json 保存公网用户 token。
  • 不使用固定 FMS_SESSION_KEY 表示公网用户。
  • 不使用进程级全局 token 表示当前员工。
  • Workbuddy 每次请求必须稳定传递 gateway_session_id

公网模式当前支持三种 session 传递方式,优先级如下:

  1. Header:X-Gateway-Session: GWS_xxx
  2. Bearer:Authorization: Bearer GWS_xxx
  3. Cookie:gateway_session_id=GWS_xxx

如果 Workbuddy 远程 MCP 无法稳定传递 header、Bearer 或 cookie 中任意一种会话身份,公网共享 Gateway 不能生产放量。

公网 Gateway 会话生命周期

1. 生成 gateway_session_id

客户端首次使用时生成高熵会话 ID:

import secrets

gateway_session_id = 'GWS_' + secrets.token_urlsafe(32)

客户端需要保存这个 ID,并在后续每次请求中复用。

2. 绑定授权码

员工从后台复制授权码后,Workbuddy 调用 bind_auth_code

POST /mcp
X-Gateway-Session: GWS_xxx
Content-Type: application/json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "bind_auth_code",
    "arguments": {
      "auth_code": "AC_xxx"
    }
  }
}

Gateway 会调用 base 项目的 /mcp/auth/exchange,并把返回的 mcp_token 存入 Redis:

fms:mcp:gateway:session:{sha256(gateway_session_id)}

Redis value 中保存 mcp_tokenadmin_idcompany_idclient_typeexpire_time 等信息。Redis key 不保存明文 gateway_session_id

3. 调用工具

后续工具调用必须继续带同一个 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

4. 撤销与过期

  • 员工在后台取消 Workbuddy 授权后,ThinkPHP 会让对应 mcp_token 失效。
  • 公网 Gateway Redis session 会在 TTL 到期后自动过期。
  • 当前公网模式默认 TTL 为 30 天,可通过 FMS_GATEWAY_SESSION_TTL_SECONDS 调整。
  • 如果 token 已失效但 Redis session 仍存在,下一次工具调用会被 ThinkPHP 拒绝。

常用命令

查看工具列表:

python app.py list-tools

开发/排障时手动绑定授权码:

python app.py bind --auth-code AUTH123

直接调用订单查询:

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_URL
  • MCP_TOOLS_BASE_URL
  • MCP_CLIENT_TYPE
  • MCP_TIMEOUT_SECONDS
  • MCP_REFRESH_SKEW_SECONDS
  • MCP_LOG_LEVEL
  • MCP_TOKEN_STORE
  • MCP_TOKEN_STORE_PATH
  • MCP_REDIS_HOST
  • MCP_REDIS_PORT
  • MCP_REDIS_DB
  • MCP_REDIS_PASSWORD
  • MCP_REDIS_PREFIX
  • MCP_SESSION_KEY

说明:

  • config.py 会自动读取 mcp/.env
  • 同名配置以 .env 优先,系统环境变量只作为兜底。
  • 如果只配置 FMS_API_BASE,会同时作为 auth 和 tools 的基础地址。
  • 如果 base 与 fmsoperate 是不同域名,应分别配置 FMS_AUTH_BASEFMS_TOOLS_BASE

Token 与 Session 存储

本地 stdio 模式:

  • Redis key 默认为 fms:mcp:workbuddy:{session_key}
  • session_key 默认由员工本机 COMPUTERNAME / USERNAME 生成。
  • 共享 .env 中不要写死 FMS_SESSION_KEY

公网 HTTP 模式:

  • Redis key 默认为 fms:mcp:gateway:session:{sha256(gateway_session_id)}
  • 每个公网客户端必须有独立 gateway_session_id
  • Gateway 不把 mcp_token 返回给 Workbuddy 展示层。
  • Gateway 不在日志中记录明文 gateway_session_idmcp_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\ 共享目录。

公网安全注意事项

  • 公网入口必须使用 HTTPS。
  • Redis 只允许内网访问。
  • Redis 密码不能提交到仓库。
  • 反向代理必须覆盖而不是透传用户伪造的转发头。
  • 当前 Python 内置限流默认使用真实 TCP 连接 IP;公网生产建议同时在 Nginx、负载均衡或 API 网关层配置限流。
  • 审计日志只记录 session hash 的短前缀、员工 ID、公司 ID、工具名和 request_id。
  • 第一版公网只开放查询型工具,不开放审批、费用修改、状态流转、批量写入。

ThinkPHP 路由口径

ThinkPHP MCP 路由位于各后端项目的 route/mcp/mcp_route.php

Gateway 会追加以下路径:

  • Auth:/mcp/auth/exchange/mcp/auth/refresh/mcp/auth/revoke
  • Tools:/mcp/tools/queryOrder/mcp/tools/queryTrack

FMS_AUTH_BASEFMS_TOOLS_BASE 只配置域名或基础地址,不要包含 /admin/mcp

排障

query_order 返回 UTF-8 BOM 错误

如果 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"

stdio 中文乱码

Windows 下 Python 标准输出可能使用本机控制台编码。当前 mcp_protocol.py 已将 stdio JSON-RPC 响应改为 ensure_ascii=True,传输行只包含 ASCII JSON 转义,客户端解析后仍是正常中文。

如果仍然乱码,优先确认 Workbuddy 启动的是最新的 \\192.168.1.241\chenjiacheng\mcp\app.py

当前边界

  • Gateway 不是业务系统,不直接查 MySQL。
  • Gateway 不替代 ThinkPHP 权限体系。
  • 公网 Gateway 是否能生产放量,取决于 Workbuddy 远程 MCP 是否能稳定传递 gateway_session_id
  • 写入型 MCP 工具需要单独安全评审后再开放。