Files
mcp-server-demo/docs/bexell/mcp-services.md
T
2026-09-02 17:14:12 +08:00

434 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 试产排程评估系统 — 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 <token>`
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 <token>"
}
}
}
```
**相关环境变量:**
| 环境变量 | 说明 |
|----------|------|
| `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
```