|
@@ -1,29 +1,38 @@
|
|
|
-# Python MCP Gateway
|
|
|
|
|
|
|
+# Python MCP Gateway
|
|
|
|
|
|
|
|
-这是一个轻量的 Python MCP Gateway,用来把 Workbuddy 的 MCP 工具调用转发到现有 ThinkPHP 授权接口和工具接口。
|
|
|
|
|
|
|
+这是物流系统给 Workbuddy 使用的轻量 MCP Gateway。它负责接收 MCP 请求、管理员工授权会话,并把工具调用转发到现有 ThinkPHP 项目的 MCP 接口。
|
|
|
|
|
|
|
|
-## 当前状态
|
|
|
|
|
|
|
+Gateway 保持“薄网关”边界:
|
|
|
|
|
|
|
|
-当前仓库已经具备一个可运行的 stdio MCP 入口,以及一套经过测试的核心模块,覆盖:
|
|
|
|
|
-- `.env` 优先的配置加载
|
|
|
|
|
-- Redis token 存储,按员工本机 `session_key` 隔离
|
|
|
|
|
-- 文件型 token 存储兜底
|
|
|
|
|
-- `auth_code` 换票、续期、失效的认证客户端
|
|
|
|
|
-- `bind_auth_code` 授权绑定工具
|
|
|
|
|
-- 工具 HTTP 转发客户端
|
|
|
|
|
-- `query_order` 工具注册
|
|
|
|
|
-- Gateway 运行时会话管理
|
|
|
|
|
-- MCP `initialize` / `tools/list` / `tools/call` 请求处理
|
|
|
|
|
-- 本地命令模式下的 stdio MCP 启动入口
|
|
|
|
|
|
|
+- 不直接连接业务数据库。
|
|
|
|
|
+- 不复制 ThinkPHP 的业务权限逻辑。
|
|
|
|
|
+- 不在 Python 内判断订单、费用、轨迹等业务权限。
|
|
|
|
|
+- ThinkPHP 继续负责 `mcp_token`、员工状态、公司、工具白名单和业务数据权限的最终校验。
|
|
|
|
|
|
|
|
-当前实现仍然保持“薄网关”原则:
|
|
|
|
|
-- 业务认证和权限判断仍然留在 ThinkPHP 项目中
|
|
|
|
|
-- Gateway 只负责协议适配、会话管理和 HTTP 转发
|
|
|
|
|
-- 第一阶段只开放查询型工具,当前从 `query_order` 开始
|
|
|
|
|
|
|
+## 当前能力
|
|
|
|
|
|
|
|
-## Workbuddy 配置
|
|
|
|
|
|
|
+- 支持 `.env` 优先的配置加载。
|
|
|
|
|
+- 支持本地 stdio MCP 模式:`serve-stdio`。
|
|
|
|
|
+- 支持公网 HTTP JSON-RPC 模式:`serve-public`。
|
|
|
|
|
+- 支持 Redis token/session 存储。
|
|
|
|
|
+- 支持文件 token store 作为开发排障兜底。
|
|
|
|
|
+- 支持 `bind_auth_code` 授权绑定工具。
|
|
|
|
|
+- 支持 `query_order` 订单查询工具。
|
|
|
|
|
+- 支持 `query_track` 轨迹查询工具。
|
|
|
|
|
+- 支持 MCP `initialize`、`tools/list`、`tools/call`。
|
|
|
|
|
+- 公网模式支持 `gateway_session_id` 请求级隔离、Redis Gateway session、审计日志和基础限流。
|
|
|
|
|
|
|
|
-员工侧 Workbuddy 使用网络 UNC 路径启动 Gateway:
|
|
|
|
|
|
|
+## 两种运行模式
|
|
|
|
|
+
|
|
|
|
|
+### 本地 stdio 模式
|
|
|
|
|
+
|
|
|
|
|
+适合员工本机 Workbuddy 通过命令方式拉起 Gateway。
|
|
|
|
|
+
|
|
|
|
|
+```powershell
|
|
|
|
|
+python \\192.168.1.241\chenjiacheng\mcp\app.py serve-stdio
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+Workbuddy 配置示例:
|
|
|
|
|
|
|
|
```json
|
|
```json
|
|
|
{
|
|
{
|
|
@@ -31,7 +40,7 @@
|
|
|
"fms": {
|
|
"fms": {
|
|
|
"command": "python",
|
|
"command": "python",
|
|
|
"args": [
|
|
"args": [
|
|
|
- "\\\\192.168.1.241\\mcp\\app.py",
|
|
|
|
|
|
|
+ "\\\\192.168.1.241\\chenjiacheng\\mcp\\app.py",
|
|
|
"serve-stdio"
|
|
"serve-stdio"
|
|
|
]
|
|
]
|
|
|
}
|
|
}
|
|
@@ -39,87 +48,154 @@
|
|
|
}
|
|
}
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
-不要把 `Y:\mcp\app.py` 写进 Workbuddy 配置;那只是开发机映射盘视角。
|
|
|
|
|
|
|
+不要把 `Y:\mcp\app.py` 写进员工 Workbuddy 配置;`Y:` 只是开发机映射盘视角,员工机器未必存在。
|
|
|
|
|
|
|
|
-## 常用命令
|
|
|
|
|
|
|
+本地模式下,员工流程为:
|
|
|
|
|
|
|
|
-以下命令在 `mcp/` 目录下执行。
|
|
|
|
|
|
|
+1. 员工在后台获取 Workbuddy 授权码。
|
|
|
|
|
+2. Workbuddy 自动拉起 `serve-stdio`。
|
|
|
|
|
+3. 员工在 Workbuddy 中调用 `bind_auth_code`,输入授权码完成绑定。
|
|
|
|
|
+4. Gateway 调用 `/mcp/auth/exchange`,并带上本机生成的 `session_key`。
|
|
|
|
|
+5. token 存入 Redis 当前员工对应 key。
|
|
|
|
|
+6. 后续直接使用 `query_order`、`query_track` 等工具。
|
|
|
|
|
|
|
|
-### 查看工具列表
|
|
|
|
|
|
|
+### 公网 HTTP 模式
|
|
|
|
|
+
|
|
|
|
|
+适合把 Gateway 部署成公网共享服务,由多个员工的 Workbuddy 远程访问同一个 Gateway。
|
|
|
|
|
+
|
|
|
|
|
+启动命令:
|
|
|
|
|
|
|
|
```powershell
|
|
```powershell
|
|
|
-python app.py list-tools
|
|
|
|
|
|
|
+python app.py serve-public --host 0.0.0.0 --port 8765
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
-### 绑定授权码(开发/排障)
|
|
|
|
|
|
|
+生产部署必须满足:
|
|
|
|
|
|
|
|
-```powershell
|
|
|
|
|
-python app.py bind --auth-code AUTH123
|
|
|
|
|
|
|
+- 必须放在 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:
|
|
|
|
|
+
|
|
|
|
|
+```python
|
|
|
|
|
+import secrets
|
|
|
|
|
+
|
|
|
|
|
+gateway_session_id = 'GWS_' + secrets.token_urlsafe(32)
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+客户端需要保存这个 ID,并在后续每次请求中复用。
|
|
|
|
|
+
|
|
|
|
|
+### 2. 绑定授权码
|
|
|
|
|
+
|
|
|
|
|
+员工从后台复制授权码后,Workbuddy 调用 `bind_auth_code`:
|
|
|
|
|
+
|
|
|
|
|
+```http
|
|
|
|
|
+POST /mcp
|
|
|
|
|
+X-Gateway-Session: GWS_xxx
|
|
|
|
|
+Content-Type: application/json
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
-员工正式使用时不需要执行这条命令。Workbuddy 拉起 `serve-stdio` 后,员工应在 Workbuddy 中调用 `bind_auth_code` 工具完成绑定。
|
|
|
|
|
|
|
+```json
|
|
|
|
|
+{
|
|
|
|
|
+ "jsonrpc": "2.0",
|
|
|
|
|
+ "id": 1,
|
|
|
|
|
+ "method": "tools/call",
|
|
|
|
|
+ "params": {
|
|
|
|
|
+ "name": "bind_auth_code",
|
|
|
|
|
+ "arguments": {
|
|
|
|
|
+ "auth_code": "AC_xxx"
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
|
|
|
-### 直接调用 query_order(开发/排障)
|
|
|
|
|
|
|
+Gateway 会调用 base 项目的 `/mcp/auth/exchange`,并把返回的 `mcp_token` 存入 Redis:
|
|
|
|
|
+
|
|
|
|
|
+```text
|
|
|
|
|
+fms:mcp:gateway:session:{sha256(gateway_session_id)}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+Redis value 中保存 `mcp_token`、`admin_id`、`company_id`、`client_type`、`expire_time` 等信息。Redis key 不保存明文 `gateway_session_id`。
|
|
|
|
|
+
|
|
|
|
|
+### 3. 调用工具
|
|
|
|
|
+
|
|
|
|
|
+后续工具调用必须继续带同一个 `gateway_session_id`:
|
|
|
|
|
+
|
|
|
|
|
+```json
|
|
|
|
|
+{
|
|
|
|
|
+ "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 拒绝。
|
|
|
|
|
+
|
|
|
|
|
+## 常用命令
|
|
|
|
|
+
|
|
|
|
|
+查看工具列表:
|
|
|
|
|
|
|
|
```powershell
|
|
```powershell
|
|
|
-python app.py call --tool query_order --keyword SO20260706001 --page 1 --limit 20
|
|
|
|
|
|
|
+python app.py list-tools
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
-### 启动 MCP stdio 服务
|
|
|
|
|
|
|
+开发/排障时手动绑定授权码:
|
|
|
|
|
|
|
|
```powershell
|
|
```powershell
|
|
|
-python \\192.168.1.241\chenjiacheng\mcp\app.py serve-stdio
|
|
|
|
|
|
|
+python app.py bind --auth-code AUTH123
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
-这个命令可作为 Workbuddy 本地命令模式下的 MCP 服务入口,由 Workbuddy 自动拉起,不要求员工手动执行。
|
|
|
|
|
|
|
+直接调用订单查询:
|
|
|
|
|
|
|
|
-## 环境变量
|
|
|
|
|
|
|
+```powershell
|
|
|
|
|
+python app.py call --tool query_order --keyword USC26070371955 --page 1 --limit 20
|
|
|
|
|
+```
|
|
|
|
|
|
|
|
-当前代码直接支持以下文档口径的配置键:
|
|
|
|
|
-- `FMS_API_BASE`
|
|
|
|
|
-- `FMS_AUTH_BASE`
|
|
|
|
|
-- `FMS_TOOLS_BASE`
|
|
|
|
|
-- `FMS_CLIENT_TYPE`
|
|
|
|
|
-- `FMS_TIMEOUT_MS`
|
|
|
|
|
-- `FMS_TIMEOUT_SECONDS`
|
|
|
|
|
-- `FMS_REFRESH_SKEW_SECONDS`
|
|
|
|
|
-- `FMS_LOG_LEVEL`
|
|
|
|
|
-- `FMS_TOKEN_STORE`
|
|
|
|
|
-- `FMS_TOKEN_STORE_PATH`
|
|
|
|
|
-- `FMS_REDIS_HOST`
|
|
|
|
|
-- `FMS_REDIS_PORT`
|
|
|
|
|
-- `FMS_REDIS_DB`
|
|
|
|
|
-- `FMS_REDIS_PASSWORD`
|
|
|
|
|
-- `FMS_REDIS_PREFIX`
|
|
|
|
|
-- `FMS_SESSION_KEY`
|
|
|
|
|
-
|
|
|
|
|
-同时兼容早期实现中的这些键名:
|
|
|
|
|
-- `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` 优先,系统环境变量只在 `.env` 缺少该配置时兜底。
|
|
|
|
|
-- 如果只提供 `FMS_API_BASE`,当前实现会同时把它作为 auth 和 tools 的基础地址。
|
|
|
|
|
-- 如果 `base` 与 `fmsoperate` 部署在不同域名,应分别提供 `FMS_AUTH_BASE` 与 `FMS_TOOLS_BASE`。
|
|
|
|
|
|
|
+```powershell
|
|
|
|
|
+python app.py call --tool query_track --order-number USC26070371955
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+运行全部测试:
|
|
|
|
|
|
|
|
-## .env 文件
|
|
|
|
|
|
|
+```powershell
|
|
|
|
|
+python -m unittest discover -s tests -p "test_*.py"
|
|
|
|
|
+```
|
|
|
|
|
|
|
|
-当前 `mcp/` 目录建议使用 `.env` 保存共享 Gateway 配置。
|
|
|
|
|
|
|
+## 环境变量
|
|
|
|
|
|
|
|
-推荐配置如下:
|
|
|
|
|
|
|
+基础配置:
|
|
|
|
|
|
|
|
```dotenv
|
|
```dotenv
|
|
|
FMS_AUTH_BASE=http://chenjiacheng.base.dahuo.fudingri.com
|
|
FMS_AUTH_BASE=http://chenjiacheng.base.dahuo.fudingri.com
|
|
@@ -128,7 +204,11 @@ FMS_CLIENT_TYPE=workbuddy
|
|
|
FMS_TIMEOUT_SECONDS=10
|
|
FMS_TIMEOUT_SECONDS=10
|
|
|
FMS_REFRESH_SKEW_SECONDS=120
|
|
FMS_REFRESH_SKEW_SECONDS=120
|
|
|
FMS_LOG_LEVEL=info
|
|
FMS_LOG_LEVEL=info
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+本地 stdio 推荐 Redis token store:
|
|
|
|
|
|
|
|
|
|
+```dotenv
|
|
|
FMS_TOKEN_STORE=redis
|
|
FMS_TOKEN_STORE=redis
|
|
|
FMS_REDIS_HOST=192.168.1.241
|
|
FMS_REDIS_HOST=192.168.1.241
|
|
|
FMS_REDIS_PORT=6379
|
|
FMS_REDIS_PORT=6379
|
|
@@ -137,101 +217,115 @@ FMS_REDIS_PASSWORD=
|
|
|
FMS_REDIS_PREFIX=fms:mcp:workbuddy:
|
|
FMS_REDIS_PREFIX=fms:mcp:workbuddy:
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
-不要在共享 `.env` 中写死 `FMS_SESSION_KEY`。默认情况下 Gateway 会用员工本机的 `COMPUTERNAME` / `USERNAME` 自动生成稳定 `session_key`,Redis key 形如:
|
|
|
|
|
|
|
+公网模式推荐配置:
|
|
|
|
|
|
|
|
-```text
|
|
|
|
|
-fms:mcp:workbuddy:{session_key}
|
|
|
|
|
|
|
+```dotenv
|
|
|
|
|
+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:
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
-这样多个员工通过同一个 `\\192.168.1.241\chenjiacheng\mcp\app.py` 启动时,也不会共用同一个 token。
|
|
|
|
|
|
|
+兼容旧配置键:
|
|
|
|
|
+
|
|
|
|
|
+- `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`
|
|
|
|
|
+
|
|
|
|
|
+说明:
|
|
|
|
|
|
|
|
-## Token 存储
|
|
|
|
|
|
|
+- `config.py` 会自动读取 `mcp/.env`。
|
|
|
|
|
+- 同名配置以 `.env` 优先,系统环境变量只作为兜底。
|
|
|
|
|
+- 如果只配置 `FMS_API_BASE`,会同时作为 auth 和 tools 的基础地址。
|
|
|
|
|
+- 如果 base 与 fmsoperate 是不同域名,应分别配置 `FMS_AUTH_BASE` 和 `FMS_TOOLS_BASE`。
|
|
|
|
|
|
|
|
-当前推荐使用 Redis:
|
|
|
|
|
|
|
+## Token 与 Session 存储
|
|
|
|
|
|
|
|
-```dotenv
|
|
|
|
|
-FMS_TOKEN_STORE=redis
|
|
|
|
|
-```
|
|
|
|
|
|
|
+本地 stdio 模式:
|
|
|
|
|
+
|
|
|
|
|
+- Redis key 默认为 `fms:mcp:workbuddy:{session_key}`。
|
|
|
|
|
+- `session_key` 默认由员工本机 `COMPUTERNAME` / `USERNAME` 生成。
|
|
|
|
|
+- 共享 `.env` 中不要写死 `FMS_SESSION_KEY`。
|
|
|
|
|
+
|
|
|
|
|
+公网 HTTP 模式:
|
|
|
|
|
|
|
|
-Redis 存储行为:
|
|
|
|
|
-- `bind_auth_code` 成功后把 `mcp_token` 写入 Redis
|
|
|
|
|
-- Redis key 使用 `FMS_REDIS_PREFIX + session_key`
|
|
|
|
|
-- `session_key` 优先读取 `FMS_SESSION_KEY`,未配置时自动从员工本机信息生成
|
|
|
|
|
-- token 过期时间会同步设置为 Redis TTL
|
|
|
|
|
-- `revoke` 成功后会删除当前 session 的 Redis key
|
|
|
|
|
|
|
+- Redis key 默认为 `fms:mcp:gateway:session:{sha256(gateway_session_id)}`。
|
|
|
|
|
+- 每个公网客户端必须有独立 `gateway_session_id`。
|
|
|
|
|
+- Gateway 不把 `mcp_token` 返回给 Workbuddy 展示层。
|
|
|
|
|
+- Gateway 不在日志中记录明文 `gateway_session_id`、`mcp_token` 或授权码。
|
|
|
|
|
|
|
|
-文件存储仍保留为兜底方式:
|
|
|
|
|
|
|
+文件存储仅用于开发排障:
|
|
|
|
|
|
|
|
```dotenv
|
|
```dotenv
|
|
|
FMS_TOKEN_STORE=file
|
|
FMS_TOKEN_STORE=file
|
|
|
FMS_TOKEN_STORE_PATH=C:/fms-mcp/.mcp_token.json
|
|
FMS_TOKEN_STORE_PATH=C:/fms-mcp/.mcp_token.json
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
-如果使用网络共享 `app.py`,不要把 `FMS_TOKEN_STORE_PATH` 指向 `\\192.168.1.241\chenjiacheng\mcp\` 共享目录。
|
|
|
|
|
|
|
+如果使用网络共享 `app.py`,不要把文件 token store 指向 `\\192.168.1.241\chenjiacheng\mcp\` 共享目录。
|
|
|
|
|
|
|
|
-## 本地接入建议
|
|
|
|
|
|
|
+## 公网安全注意事项
|
|
|
|
|
|
|
|
-当前员工正式使用路径是:
|
|
|
|
|
-1. 员工在后台获取一次性 `auth_code`
|
|
|
|
|
-2. Workbuddy 自动通过 `python \\192.168.1.241\chenjiacheng\mcp\app.py serve-stdio` 拉起 Gateway
|
|
|
|
|
-3. 员工在 Workbuddy 中调用 `bind_auth_code`,输入授权码完成绑定
|
|
|
|
|
-4. Gateway 调用 `exchange` 时带上 `session_key`
|
|
|
|
|
-5. token 保存到 Redis 当前员工对应的 key
|
|
|
|
|
-6. 后续直接使用 `query_order` 等工具
|
|
|
|
|
|
|
+- 公网入口必须使用 HTTPS。
|
|
|
|
|
+- Redis 只允许内网访问。
|
|
|
|
|
+- Redis 密码不能提交到仓库。
|
|
|
|
|
+- 反向代理必须覆盖而不是透传用户伪造的转发头。
|
|
|
|
|
+- 当前 Python 内置限流默认使用真实 TCP 连接 IP;公网生产建议同时在 Nginx、负载均衡或 API 网关层配置限流。
|
|
|
|
|
+- 审计日志只记录 session hash 的短前缀、员工 ID、公司 ID、工具名和 request_id。
|
|
|
|
|
+- 第一版公网只开放查询型工具,不开放审批、费用修改、状态流转、批量写入。
|
|
|
|
|
|
|
|
-`python app.py bind --auth-code ...` 仅保留给开发、联调和排障使用。
|
|
|
|
|
|
|
+## ThinkPHP 路由口径
|
|
|
|
|
|
|
|
-## 测试命令
|
|
|
|
|
|
|
+ThinkPHP MCP 路由位于各后端项目的 `route/mcp/mcp_route.php`。
|
|
|
|
|
|
|
|
-```powershell
|
|
|
|
|
-python -m unittest discover -s tests -p "test_*.py"
|
|
|
|
|
-```
|
|
|
|
|
|
|
+Gateway 会追加以下路径:
|
|
|
|
|
|
|
|
-## 当前边界
|
|
|
|
|
|
|
+- Auth:`/mcp/auth/exchange`、`/mcp/auth/refresh`、`/mcp/auth/revoke`
|
|
|
|
|
+- Tools:`/mcp/tools/queryOrder`、`/mcp/tools/queryTrack`
|
|
|
|
|
|
|
|
-当前 Gateway 已可作为本地 stdio MCP 进程使用,但仍保持最小边界:
|
|
|
|
|
-- 不在 Python 中沉淀额外业务规则
|
|
|
|
|
-- 不直接连接业务数据库
|
|
|
|
|
-- 不做动态插件加载
|
|
|
|
|
-- 第一阶段不开放写入型工具
|
|
|
|
|
-## query_order BOM 排障
|
|
|
|
|
|
|
+`FMS_AUTH_BASE` 和 `FMS_TOOLS_BASE` 只配置域名或基础地址,不要包含 `/admin/mcp`。
|
|
|
|
|
|
|
|
-如果 Workbuddy 调用 `query_order` 时提示:
|
|
|
|
|
|
|
+## 排障
|
|
|
|
|
+
|
|
|
|
|
+### query_order 返回 UTF-8 BOM 错误
|
|
|
|
|
+
|
|
|
|
|
+如果 Workbuddy 提示:
|
|
|
|
|
|
|
|
```text
|
|
```text
|
|
|
Unexpected UTF-8 BOM (decode using utf-8-sig)
|
|
Unexpected UTF-8 BOM (decode using utf-8-sig)
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
-说明后端工具接口返回的 JSON 前面带了 UTF-8 BOM。当前 Gateway 已在 HTTP JSON 解码层使用 `utf-8-sig` 做兼容。排查时先在 `mcp/` 目录运行:
|
|
|
|
|
|
|
+说明后端工具接口返回的 JSON 前面带了 UTF-8 BOM。当前 Gateway 已在 HTTP JSON 解码层使用 `utf-8-sig` 兼容。先运行全量测试确认当前文件是否已包含修复:
|
|
|
|
|
|
|
|
```powershell
|
|
```powershell
|
|
|
python -m unittest discover -s tests -p "test_*.py"
|
|
python -m unittest discover -s tests -p "test_*.py"
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
-若全量测试通过但 Workbuddy 仍报同样错误,优先确认 Workbuddy 启动的 `app.py` 是否为 `\\192.168.1.241\chenjiacheng\mcp\app.py` 的最新文件。
|
|
|
|
|
-
|
|
|
|
|
-## 2026-07-07 ThinkPHP MCP route path
|
|
|
|
|
-
|
|
|
|
|
-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`:
|
|
|
|
|
|
|
+### stdio 中文乱码
|
|
|
|
|
|
|
|
-- Auth: `/mcp/auth/exchange`, `/mcp/auth/refresh`, `/mcp/auth/revoke`
|
|
|
|
|
-- Tools: `/mcp/tools/queryOrder`
|
|
|
|
|
|
|
+Windows 下 Python 标准输出可能使用本机控制台编码。当前 `mcp_protocol.py` 已将 stdio JSON-RPC 响应改为 `ensure_ascii=True`,传输行只包含 ASCII JSON 转义,客户端解析后仍是正常中文。
|
|
|
|
|
|
|
|
-Keep `FMS_AUTH_BASE` and `FMS_TOOLS_BASE` as host/base-domain values. Do not include `/admin/mcp` in these environment variables.
|
|
|
|
|
-## query_track / stdio 中文乱码排障
|
|
|
|
|
|
|
+如果仍然乱码,优先确认 Workbuddy 启动的是最新的 `\\192.168.1.241\chenjiacheng\mcp\app.py`。
|
|
|
|
|
|
|
|
-如果后端 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 后仍是正常中文。
|
|
|
|
|
-
|
|
|
|
|
-排查顺序:
|
|
|
|
|
-- 先确认 HTTP 层 `ApiClient` 能用 `utf-8-sig` 正常解析后端 JSON。
|
|
|
|
|
-- 再确认 `serve-stdio` 输出不是直接吐本机编码中文。
|
|
|
|
|
-- 修改后需要重启 Workbuddy 拉起的 MCP Gateway 进程。
|
|
|
|
|
-
|
|
|
|
|
-验证命令:
|
|
|
|
|
|
|
+## 当前边界
|
|
|
|
|
|
|
|
-```powershell
|
|
|
|
|
-python -m unittest discover -s tests -p "test_*.py"
|
|
|
|
|
-```
|
|
|
|
|
|
|
+- Gateway 不是业务系统,不直接查 MySQL。
|
|
|
|
|
+- Gateway 不替代 ThinkPHP 权限体系。
|
|
|
|
|
+- 公网 Gateway 是否能生产放量,取决于 Workbuddy 远程 MCP 是否能稳定传递 `gateway_session_id`。
|
|
|
|
|
+- 写入型 MCP 工具需要单独安全评审后再开放。
|