diff --git a/docs/bexell/mcp-services.md b/docs/bexell/mcp-services.md new file mode 100644 index 0000000..3b1eb6d --- /dev/null +++ b/docs/bexell/mcp-services.md @@ -0,0 +1,433 @@ +# 试产排程评估系统 — MCP 服务说明 + +**服务名称:** `bexell-scheduling` +**协议版本:** MCP (Model Context Protocol),Streamable HTTP 传输 +**挂载路径:** `/bexell/mcp` +**部署方式:** mcp-server-demo 单进程多模组之一(与 ERP/CRM 模组共享进程与端口 8001) +**鉴权方式:** 动态 Bearer Token(mcp-auth 后端校验) +**生成时间:** 2026-09-02 + +--- + +## 一、概述 + +Bexell 模组是 **mcp-server-demo** 统一服务中的一个独立 MCP 模组,基于 **MCPServer** 框架(MCP Python SDK)实现,对外提供 4 个 MCP Tool,服务"试产排程评估及优化"场景。 + +统一服务通过 Starlette `Mount` 将三个模组挂载到不同路径,单进程、单端口(8001): + +| 模组 | MCP 端点 | 服务名称 | +|------|----------|----------| +| ERP | `http://localhost:8001/erp/mcp` | `mcp-for-erp-auto` | +| CRM | `http://localhost:8001/crm/mcp` | `mcp-for-crm-auto` | +| **Bexell** | **`http://localhost:8001/bexell/mcp`** | **`bexell-scheduling`** | + +MCP 服务面向 **IndepthAI Skill 层**(`trial-production-scheduling-evaluator`)提供数据查询与产能计算能力,是智能体完成"试产排程评估"任务的核心数据通道。 + +--- + +## 二、架构定位 + +``` +┌──────────────────────────────────────────┐ +│ IndepthAI Skill 层 │ +│ trial-production-scheduling-evaluator │ +│ (解析输入 → 调用 MCP → 格式化输出) │ +└──────────────────┬───────────────────────┘ + │ MCP 协议(Streamable HTTP + Bearer Token) +┌──────────────────▼───────────────────────┐ +│ mcp-server-demo 统一服务 (:8001) │ +│ │ +│ /erp/mcp ── ERP 模组 │ +│ /crm/mcp ── CRM 模组 │ +│ /bexell/mcp ── Bexell 模组(4 工具)◄──│ +│ │ +│ 鉴权:mcp-auth 后端 verify-token API │ +└──────────────────┬───────────────────────┘ + │ SQLAlchemy ORM(独立连接池) +┌──────────────────▼───────────────────────┐ +│ PostgreSQL (bexell_schedule_ai) │ +│ 5 张表 + 1 个视图,无需新增 │ +└──────────────────────────────────────────┘ +``` + +**模组化优势:** + +| 维度 | 说明 | +|------|------| +| 独立连接池 | Bexell 模组使用 `BEXELL_DB_*` 环境变量配置独立 SQLAlchemy Engine,与 ERP/CRM 数据库完全隔离 | +| 独立鉴权 | 使用 `BEXELL_MCP_AUTH_API_KEY` 调用 mcp-auth 后端,token 校验按 service 隔离 | +| 统一运维 | 单进程、单端口,三个模组共享 uvicorn 服务与生命周期管理 | +| 数据一致性 | Tool 计算复用同一数据库,评估公式在单条聚合 SQL 内完成 | +| 可演进性 | 模组目录结构统一(tools.py / db.py / models.py),可独立拆分部署 | + +--- + +## 三、鉴权 + +MCP 端点已启用 **动态 Bearer Token 鉴权**,未携带或携带无效 token 的请求返回 `401 Unauthorized`。 + +**校验流程:** + +1. 客户端在请求头携带 `Authorization: Bearer ` +2. MCP 服务调用 mcp-auth 后端 `POST {MCP_AUTH_API_URL}/api/auth/verify-token`,以 `X-API-Key: {BEXELL_MCP_AUTH_API_KEY}` 标识调用方服务 +3. 后端统一查询 `mcp_auth.mcp_token` 表,返回 `{"valid": bool, "client_id": str, "service_scope": str}` +4. token 与 `bexell-scheduling` 服务作用域匹配才校验通过 +5. 进程内缓存校验结果 30s(`MCP_AUTH_CACHE_TTL`),减少对后端的重复调用 + +**客户端配置示例:** + +```json +{ + "bexell-scheduling": { + "type": "http", + "url": "http://127.0.0.1:8001/bexell/mcp", + "headers": { + "Content-Type": "application/json", + "Accept": "application/json, text/event-stream", + "Authorization": "Bearer " + } + } +} +``` + +**相关环境变量:** + +| 环境变量 | 说明 | +|----------|------| +| `MCP_PUBLIC_URL` / `BEXELL_MCP_PUBLIC_URL` | 服务对外地址(OAuth 资源元数据),模组独立地址未设置时回退通用值 | +| `MCP_AUTH_API_URL` | mcp-auth 后端地址,如 `https://mcp-auth-admin.digiwincloud.com.cn/mcp-auth-api` | +| `BEXELL_MCP_AUTH_API_KEY` | Bexell 模组独立 API Key(由 mcp-auth 后端签发) | +| `MCP_AUTH_CACHE_TTL` | token 校验结果缓存秒数,默认 30 | + +--- + +## 四、MCP Tool 清单 + +| # | Tool 名称 | 类型 | 用途 | +|---|-----------|------|------| +| 1 | `get_equipment_by_type` | 基础查询 | 按设备类型查询该类型所有设备的基础产能信息 | +| 2 | `get_schedule_occupied` | 基础查询 | 获取指定类型全部设备的已占用产能合计 | +| 3 | `get_product_load` | 基础查询 | 查询指定品号在目标设备类型各设备上的标准产能 | +| 4 | `evaluate_trial_capacity` | **一体化计算** | 服务端一次性完成全部数据聚合和公式计算,返回评估结果与推荐设备 | + +--- + +## 五、Tool 详细说明 + +### 5.1 `get_equipment_by_type` — 按类型查询设备产能 + +**用途:** 根据设备类型获取该类型所有设备的基础产能参数,供 Skill 做数据展示或分步计算。 + +**输入参数:** + +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `equipment_type` | string | 是 | 设备类型,可选值:`冲压`、`表面`、`检测` | + +**输出字段:** + +| 字段 | 类型 | 说明 | +|------|------|------| +| `equipment_code` | string | 设备编号(如 CY001、DN003、VJ002) | +| `equipment_name` | string | 设备名称(如 冲压机1、镀镍机3) | +| `equipment_type` | string | 设备类型 | +| `max_capacity` | number | 极限产能(H) | +| `standard_capacity` | number | 标准产能(H) | +| `min_startup` | number | 最小开机产能(H) | +| `line_change_cost` | number | 换线成本系数 | + +**数据来源:** `equipment_capacity` 表(ORM 模型 `EquipmentCapacity`) +**查询逻辑:** `WHERE equipment_type = :type ORDER BY equipment_code` + +**设备类型与数量:** + +| 设备类型 | 编号范围 | 数量 | +|----------|----------|------| +| 冲压 | CY001 ~ CY010 | 10 台 | +| 表面 | DN001 ~ DN012 | 12 台 | +| 检测 | VJ001 ~ VJ005 | 5 台 | + +--- + +### 5.2 `get_schedule_occupied` — 查询排产占用产能 + +**用途:** 获取指定类型全部设备当前已被排产计划占用的产能合计,反映设备负荷现状。 + +**输入参数:** + +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `equipment_type` | string | 是 | 设备类型,如 `冲压`、`表面`、`检测` | + +**输出字段:** + +| 字段 | 类型 | 说明 | +|------|------|------| +| `equipment_code` | string | 设备编号 | +| `equipment_name` | string | 设备名称 | +| `total_occupied_capacity` | number | 占用产能合计(H),无排产时为 0 | + +**数据来源:** `product_equipment_schedule` 表(ORM 模型 `ProductEquipmentSchedule`) +**查询逻辑:** `GROUP BY equipment_code`,`SUM(occupied_capacity)`,`COALESCE` 无记录返回 0 + +--- + +### 5.3 `get_product_load` — 查询品号设备标准产能 + +**用途:** 获取指定品号在目标设备类型各设备上生产时的标准产能消耗系数,用于计算试产需求对设备产能的占用量。 + +**输入参数:** + +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `product_code` | string | 是 | 品号,如 `A0001`、`A0002`、`A0003` | +| `equipment_type` | string | 是 | 设备类型,如 `冲压`、`表面`、`检测` | + +**输出字段:** + +| 字段 | 类型 | 说明 | +|------|------|------| +| `product_code` | string | 品号 | +| `product_name` | string | 品名 | +| `equipment_code` | string | 设备编号 | +| `equipment_name` | string | 设备名称 | +| `standard_capacity` | number | 该品号在该设备上每生产 1 件所消耗的产能(H/件) | + +**数据来源:** `product_equipment_load` 表 JOIN `equipment_capacity` 表(ORM 模型 `ProductEquipmentLoad`) +**查询逻辑:** 按品号 + 设备类型联合过滤,按设备编号排序 + +**品号与标准产能示例(以 A0003 圆柱电池钢壳为例):** + +| 设备类型 | 设备编号 | 标准产能(H/件) | +|----------|----------|-----------------| +| 冲压 | CY001~CY010 | 0.2 | +| 表面 | DN001~DN012 | 0.1 | +| 检测 | VJ001~VJ005 | 0.01 | + +--- + +### 5.4 `evaluate_trial_capacity` — 一体化试产产能评估 ⭐ + +**用途:** 核心工具。服务端一次性完成全部数据聚合和公式计算,返回每台设备的可用产能明细和最优推荐设备。Skill 层只需调用一次即可完成评估,无需多次查询和自行计算。 + +**评估范围:** 覆盖该品号配置了标准产能的**全部设备类型**(冲压/表面/检测)的所有设备,结果按可用产能降序混合排名。 + +**输入参数:** + +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `product_code` | string | 是 | 品号,如 `A0003` | +| `trial_quantity` | integer | 是 | 试产数量,如 `10` | + +**输出结构:** + +```json +{ + "product_code": "A0003", + "product_name": "圆柱电池钢壳", + "trial_quantity": 10, + "evaluated_at": "2026-08-22T15:30:00", + "results": [ + { + "rank": 1, + "equipment_code": "CY003", + "equipment_name": "冲压机3", + "equipment_type": "冲压", + "max_capacity": 15.00, + "occupied_capacity": 8.00, + "line_change_cost": 1.00, + "min_startup_buffer": 0.40, + "product_standard_capacity": 0.2, + "trial_demand": 2.00, + "available_capacity": 3.60, + "status": "可用" + } + ], + "recommended": { + "equipment_code": "CY003", + "equipment_name": "冲压机3", + "available_capacity": 3.60 + } +} +``` + +**输出字段说明(results 数组内每条记录):** + +| 字段 | 类型 | 说明 | +|------|------|------| +| `rank` | integer | 排名(按可用产能降序,跨设备类型混合排名) | +| `equipment_code` | string | 设备编号 | +| `equipment_name` | string | 设备名称 | +| `equipment_type` | string | 设备类型 | +| `max_capacity` | number | 极限产能(H) | +| `occupied_capacity` | number | 当前占用产能合计(H) | +| `line_change_cost` | number | 换线成本 | +| `min_startup_buffer` | number | 最小开机缓冲(= min_startup × 0.1) | +| `product_standard_capacity` | number | 该品号在该设备的标准产能(H/件) | +| `trial_demand` | number | 试产需求(= 试产数量 × 标准产能) | +| `available_capacity` | number | **可用产能**(核心指标) | +| `status` | string | 状态:`可用`(≥0)或 `已满负荷`(<0) | + +**核心计算公式:** + +``` +可用产能 = 极限产能 + − 占用产能合计 + − 换线成本 + − 最小开机 × 0.1 + − 试产数量 × 品号标准产能 +``` + +**数据来源(一次聚合查询,单条 SQL 完成):** + +| 公式因子 | 来源表 | 字段 | +|----------|--------|------| +| 极限产能 | `equipment_capacity` | `max_capacity` | +| 占用产能合计 | `product_equipment_schedule` | `SUM(occupied_capacity)` | +| 换线成本 | `equipment_capacity` | `line_change_cost` | +| 最小开机 | `equipment_capacity` | `min_startup` | +| 品号标准产能 | `product_equipment_load` | `standard_capacity` | +| 品名 | `product_info` | `product_name` | + +**推荐逻辑:** 取 `results` 中排名第一(可用产能最大)且 `available_capacity >= 0` 的设备;若无设备满足条件,`recommended` 返回 `null`。 + +--- + +## 六、Tool 调用策略 + +| 场景 | 推荐方式 | 说明 | +|------|---------|------| +| **试产评估(默认)** | 直接调用 `evaluate_trial_capacity` | 一次调用完成全部计算,Skill 只需格式化输出 | +| **分步调试 / 中间数据展示** | Tool 1 → Tool 2 → Tool 3 → Skill 内计算 | 适用于需要逐步展示数据来源或教学演示场景 | +| **仅查看设备信息** | 调用 `get_equipment_by_type` | 不涉及排产计算,纯基础数据查询 | +| **仅查看负荷现状** | 调用 `get_schedule_occupied` | 查看当前排产占用情况 | + +--- + +## 七、数据模型依赖 + +Bexell 模组复用 `bexell_schedule_ai` 库中以下现有表和 ORM 模型(定义于 `src/modules/bexell/models.py`),**无需新增任何表结构**: + +| 表 / 视图 | ORM 模型 | 说明 | +|-----------|----------|------| +| `sys_parameter` | `SysParameter` | 系统参数(`sys_s_parameter` = 影响系数 0.1) | +| `equipment_capacity` | `EquipmentCapacity` | 设备基础产能(极限/标准/最小开机/换线成本) | +| `product_info` | `ProductInfo` | 品号主数据 | +| `product_equipment_load` | `ProductEquipmentLoad` | 品号 × 设备标准产能(81 行种子数据) | +| `product_equipment_schedule` | `ProductEquipmentSchedule` | 排产明细(排产数量 & 占用产能) | +| `v_equipment_realtime_capacity` | —(视图) | 设备实时产能视图(评估工具未直接使用,供查询参考) | + +**表关系:** + +``` +┌─────────────────────┐ +│ sys_parameter │ 系统参数(影响系数 0.1) +└─────────────────────┘ + +┌─────────────────────┐ ┌──────────────────────────┐ +│ equipment_capacity │────▶│ product_equipment_load │ +│ • 设备基础产能 │ │ • 品号×设备 标准产能 │ +│ • 极限/标准/开机 │ │ • 关联 product_info │ +│ • 换线成本 │ │ • 关联 equipment_capacity│ +└──────────┬──────────┘ └──────────────────────────┘ + │ + │ LEFT JOIN(占用产能聚合) + ▼ +┌──────────────────────────────┐ +│ product_equipment_schedule │ +│ • 排产明细 │ +│ • 排产数量 & 占用产能 │ +└──────────────────────────────┘ +``` + +--- + +## 八、Skill 集成 + +MCP 服务配套 Skill:**`trial-production-scheduling-evaluator`** + +**Skill 执行流程:** + +``` +用户输入(品号 + 试产数量 + 设备类型) + │ + ▼ +Step 1: 解析参数 & 校验 + │ + ▼ +Step 2: 调用 evaluate_trial_capacity + │ 传入 product_code + trial_quantity + ▼ +Step 3: 结果排序 & 状态标记 + │ 可用产能 ≥ 0 → "可用" + │ 可用产能 < 0 → "已满负荷" + ▼ +Step 4: 格式化输出评估报告 + • 推荐设备(★) + • 全部候选设备明细表 + • 风险提示 +``` + +**触发场景:** +- 评估试产排程 +- 查询设备可用产能 +- 推荐试产设备 +- 计算设备产能负荷 + +--- + +## 九、错误处理 + +| 错误场景 | 返回内容 | +|----------|---------| +| token 缺失 / 无效 / 服务作用域不匹配 | MCP 层返回 `401 Unauthorized`(本地缓存 30s,后端不可达时同样拒绝) | +| 品号不存在 | `{"error": "品号 XXX 不存在"}` | +| 品号在某设备无标准产能配置 | 该设备不参与排名(JOIN 过滤),不报错 | +| 所有设备已满负荷 | `recommended` 返回 `null`,`results` 中所有 `status` 为 "已满负荷" | +| 数据库连接失败 | 由 SQLAlchemy(`pool_pre_ping`)与统一服务异常处理捕获 | + +--- + +## 十、技术实现要点 + +| 项目 | 说明 | +|------|------| +| 框架 | MCPServer(MCP Python SDK),非 FastAPI 挂载 | +| 挂载方式 | `server.py` 中 `Starlette Mount("/bexell", bexell_server.streamable_http_app(streamable_http_path="/mcp"))`,子路径 `/mcp` | +| 鉴权 | `get_auth(service="BEXELL")` 构建 `AuthSettings` + `ApiTokenVerifier`,Bearer Token 动态校验 | +| 数据库访问 | 模组独立 SQLAlchemy Engine(`BEXELL_DB_*` 环境变量),`pool_size=10`、`pool_pre_ping=True` | +| ORM 模型 | `src/modules/bexell/models.py` 中定义 5 张表模型 | +| 核心计算 | `evaluate_trial_capacity` 通过单条聚合 SQL 完成(LEFT JOIN 占用聚合 + JOIN 品号负荷),避免多次往返 | +| 影响系数 | 公式中 `0.1` 对应 `sys_parameter.sys_s_parameter`(当前为 SQL 硬编码) | +| 并发安全 | 每次工具调用独立获取/关闭 DB Session | +| 生命周期 | 父 app lifespan 统一管理 session manager 启动与连接池关闭(`close_bexell_engine`) | + +--- + +## 十一、文件结构 + +``` +mcp-server-demo/ +├── .env.example # 环境变量模板(BEXELL_MCP_AUTH_API_KEY、BEXELL_DB_* 等) +├── sql/bexell_init.sql # bexell_schedule_ai 建表 + 种子数据(5 表 + 1 视图) +├── src/ +│ ├── server.py # 统一服务入口:Mount("/bexell") + lifespan +│ ├── auth.py # 动态 Bearer Token 鉴权(mcp-auth 后端校验) +│ └── modules/ +│ └── bexell/ +│ ├── __init__.py +│ ├── tools.py # MCPServer + 4 个 MCP Tool +│ ├── db.py # 独立 SQLAlchemy Engine / SessionLocal +│ └── models.py # 5 张表 ORM 模型 +└── docs/bexell/ + └── mcp-services.md # 本文档 +``` + +**启动方式:** + +```bash +cd mcp-server-demo +set -a; source .env; set +a +cd src && python server.py +# Bexell MCP 端点: http://localhost:8001/bexell/mcp +```