Skip to main content

MES-MCP Server

把 OTD MES/WMS 的车间执行与仓储物流数据,暴露成 AI Agent 可直接调用的 MCP 工具。

与 ERP-MCP、CRM-MCP 同栈同范式,三者装齐即可跑通完整制造链路: 商机 → 合同 → 销售订单 → 生产工单 → 工序/报工 → 质检 → 入库 → 发货 → 回款。

MES-MCP 提供 ERP/CRM 给不了的三样东西:

  1. 工序级颗粒度 —— ERP 只知道工单"完工 60%",MES 知道卡在哪道工序、哪条线、哪台设备
  2. 工单级用料关联与 SN 包装追溯 —— 可辅助圈定召回候选范围;用料关联不等于单台成品实际装料,须核查分页和其他来源
  3. 实时车间状态 —— 在制分布、良率、设备报警、齐套率,分钟级而非日结级

v0.4.1:47 个工具已注册(27 原子查询 + 8 聚合 + 6 复合 Skill + 4 元数据 + 2 版本管理)。新增标签入口默认拒绝,限同一获准范围的内部运维;v0.4.0为46个工具。 完整设计见 MES-MCP-MVP-设计文档.md。 Read + Explain 交付及客户 B 验收边界见 交付说明。MVP 设计文档的旧分期和工具数量仅作历史背景。


快速开始

uv venv
uv pip install -e ".[dev]"

cp .env.example .env      # 填数据库连接
python scripts/probe_otdmes.py   # 先看这个客户库长什么样
mes-mcp                          # 启动 MCP Server(stdio)

接入 Claude Code:

{ "mcpServers": { "mes": { "command": "mes-mcp", "cwd": "E:/AIDev/MES-MCP" } } }

probe_otdmes.py 会输出「这个客户库长什么样」:模块启用度、枚举值集、缺失表。接新客户第一件事就是跑它。

双库对比(验证表结构基线是否仍成立):

python scripts/probe_otdmes.py \
    --other-port 20000 --other-database OTDMES_CUSTOMER_B \
    --out reports/compare.md

另有一个方向相反的探测脚本 probe_config.py:

python scripts/probe_config.py          # 导出配置项字典(配置驱动能力清单)
python scripts/probe_config.py --also 10000/OTDMES_HMD_AI   # 多库并集
脚本 探什么 回答
probe_otdmes.py 数据:模块有没有跑起来、枚举值是什么 客户现在跑到哪了
probe_config.py 能力开关:全局开关、枚举行为引用、对象级属性 产品能配到什么程度

两者合起来才够做 Fit-Gap("这个需求配置能不能解决、要不要二次开发")。probe_config.py 一条命令同时写两个位置、避免漂移:产品 OTDMES/otdmesv2-otd/docs/12-配置项字典.md 与 skill otd-skills/3-Implementing/otd-blueprint-fitgap/references/config-dictionary.md。 密钥类取值自动脱敏,并在写完自检——明文仍出现在输出里就当场失败,不等推上 git 才发现。

单库导出是那一家的快照,不等于"产品支持什么"——口径写「X 库中存在该配置项」。 多库并集才接近产品级能力面;差集也有用:只在一家出现的键往往是那家二开留下的痕迹。


两条必须先知道的设计约束

这两点是双库实测(OTDMES_CUSTOMER_A + OTDMES_CUSTOMER_B)得出的,直接决定了代码怎么写。

一、表结构是统一产品基线,枚举不是

14 张核心表在两个客户库里逐字段完全一致(PRD_WORK_ORDERS 都是 90 字段,WMS_PICKING_LIST_HEADER 都是 43……),连 CUS_ 前缀的"定制表"都是同样的 18 张——它们是标准产品包的一部分。

所以:

层 做法 位置
表名 / 字段名 硬编码 catalog.py
枚举值 / 字典 启动时读库 probe.py
模块启用度 启动时探测 probe.py

枚举确实按客户扩展:领料单类型客户A有 3 种、客户B有 7 种(多出的全是委外相关);仓库编码客户A是 RM01/FG01/SP1、客户B是 CK002/CK003/SFG。别把任何枚举写死在业务代码里。

二、MODULE_NOT_DEPLOYED 不是错误,是事实

同样的表,两家客户使用情况几乎相反:

模块 客户A 客户B
生产报工 19,750 0
设备与工装 675 0
成品/过程检验 291 0
领料发料 931 15,335
工艺与BOM 42 1,698

若工具对无数据模块返回空列表,Agent 会把"没上报工模块"读成"今天没产出",结论错得离谱。因此:

deployment.require("reporting")   # 无数据则抛 ModuleNotDeployedError

错误信息里带明确的替代路径("请改用工单产出数 ORDER_OUT_QTY 估算"),而不是让 Agent 自己猜。

措辞注意:探测只能看出当前库里没有数据,看不出原因——可能是没买、没上线,也可能只是测试库没铺数据。所有提示一律写「当前库无 X 数据」,不写「该客户未启用」。


目录结构

mes_mcp/
├── server.py      ★ MCP 工具注册 + main()
├── runtime.py     配置/数据库/探测结果的运行时单例(惰性探测)
├── rendering.py   QueryResult → Markdown;模块门禁与错误兜底装饰器
├── query.py       ★ 查询构造器(过滤/绑参/分页/别名),所有工具共用
├── charts.py      chart-data 嵌入(ERP 的信封格式 + MES 自己的 type)
├── skills/        ★ 复合 Skill:给结论,不只给数据
│   ├── base.py         SkillResult(含 degraded / caveats)+ SkillContext
│   ├── genealogy.py    SN 族谱(正反向)
│   ├── diagnosis.py    工单延期归因、良率归因
│   └── alerts.py       缺料预警、每日简报
├── domains/       按业务域的查询构造
│   ├── aggregates.py   ★ 聚合与老板视角(逐块降级)
│   ├── integration.py  ERP 回写台账与下发失败(金蝶云星空)
│   ├── production.py   工单、工序任务、报工、不良
│   ├── warehouse.py    库存、库位、领料、到货、入库、发货、盘点、流水、追溯
│   ├── quality.py      检验批、IQC
│   ├── equipment.py    设备台账、报警
│   └── engineering.py  工艺路线、BOM、物料主数据
├── config.py      pydantic-settings。一份 .env 绑一个客户库,运行时不切库
├── errors.py      MESError + ModuleNotDeployedError + 错误码字典
├── db.py          SQL Server 只读访问层(pymssql)
├── dates.py       ★ 日期风格归一化 —— 见下
├── catalog.py     表白名单、模块定义、枚举源、固定枚举、日期字段声明
├── probe.py       部署探测:模块启用度 + 枚举发现 + Markdown 渲染
├── ontology/      MES/WMS 语义模型 + 跨系统关联键
├── formatting.py  dict → Markdown 表(移植自 erp_mcp)
└── timezone.py    +8 时区助手(移植自 erp_mcp)

scripts/probe_otdmes.py    客户库探测 / 双库对比(数据面:模块启用度 + 枚举)
scripts/probe_config.py    配置项字典导出(能力面:开关 + 行为引用),双写 docs 与 skill
tests/unit                 纯逻辑,不连库
tests/integration          连真库冒烟,未配 .env 自动 skip

工具清单(47)

先调 mes_describe_deployment,它告诉你这个客户启用了哪些模块、有哪些仓库、 单据类型有哪几种。不看就查,很容易把"模块没数据"误读成"业务量为零"。

域 工具
生产 mes_query_production_orders mes_query_process_tasks mes_query_work_reports mes_query_defects
质量 mes_query_quality_inspections mes_query_iqc_orders
复检 mes_query_iqc_reinspections mes_expired_material_alert
设备 mes_query_equipment mes_query_equipment_alarms
工程 mes_query_routes mes_query_bom mes_query_materials
仓储 wms_query_stock wms_query_bins wms_query_picking_lists wms_query_picking_list_lines wms_query_arrivals wms_query_input_orders wms_query_shipping_orders wms_query_inventory_orders wms_query_stock_movements wms_query_traceability
标签(默认禁用) mes_read_label_context:精确库存身份、当前账面量及有限流水;包装展开关闭;实现范围与启用前提
聚合 mes_production_dashboard mes_order_progress mes_yield_ranking mes_top_defects mes_wip_distribution mes_equipment_alarm_ranking wms_stock_summary wms_material_readiness
ERP 集成 mes_erp_sync_health mes_query_erp_sync_records mes_query_erp_inbound_errors
Skill mes_sn_genealogy mes_order_delay_diagnosis mes_yield_attribution mes_shortage_alert mes_daily_brief mes_explain_erp_sync_exception
元数据 mes_describe_deployment mes_list_tables mes_describe_table mes_get_ontology
版本 mes_check_update mes_self_update

几个值得单说的:

  • wms_query_traceability —— SN 正反向族谱。给 lot_no 反查这批料流向了哪些成品, 是质量召回圈定范围的基础。ERP 答不了这个问题。必须给定位条件,否则拒绝执行 (无条件全表扫追溯表会拖垮生产库)。
  • mes_query_work_reports —— group_by 支持 process/line/product/day, 汇总模式直接给 yield_pct 良率。
  • wms_query_picking_list_lines —— 返回需求量、已扫量、缺口, 以及委外占用/损耗三列。委外客户做齐套分析必须计入这几列。

聚合工具的两个特点:

  • 逐块降级:mes_production_dashboard 跨 6 个模块取数,某模块无数据时该板块 被列进「未取到的板块」并说明原因,其余照常输出。所以在只上了 WMS 的客户那里 它也能用。mes_order_progress 同理。
  • 嵌 chart-data:返回 Markdown 末尾带 <!-- chart-data {json} -->, 沿用 ERP-MCP 的信封格式(erp-charts-hook 能直接提取),但 type 是 MES 自己的 (mes_ranking / mes_pareto / mes_wip / mes_dashboard)——ERP 那几个渲染器 是金额导向的,拿来画良率百分比会得出荒谬的图。纯查询工具不嵌。

ERP 集成:两个方向别只看一半

某客户与金蝶云星空的接口很重,而且失败率不低——实测 870 条回写里 108 条失败(12.4%), WMS_FG_INPUT 更是 75%。回写失败是需要核对的接口异常;是否存在 ERP 单据或财务差异,需要到 ERP 侧核验。

MES → ERP(回写/过账)  WMS_ERP_REPORT_MANAGE_HEADER   标准产品表
ERP → MES(下发)       ErpErrorLog                     ⚠️ 客户定制表,仅部分客户有

mes_erp_sync_health 会把金蝶的报错归一化后聚类(抹掉单号、物料号、行号再分组), 否则同一类错误会散成一条一组——实测「反写采购订单超出可退数量」被行号拆成 4 个桶, 归一后才看出它是最大的一类(45 次)。

v0.4.0 新增 mes_explain_erp_sync_exception(report_no=...),按单笔台账把已记录事实、待核验解释、缺失的 ERP 证据和人工排查步骤分开。单号不唯一时改用 rec_no 精确定位;工具不会自动重试或写入。

三个坑:

  • 失败原因在 REPORTED_ERP_RESULT_DETAIL,_MESSAGE 与 _CODE 实测全是空串
  • 失败 / 待过账 / 结果未知是三种状态,别混。失败率的分母只取已判定的(OK+NG), 把「结果未知」算进分母会把失败率稀释掉
  • 区间内 0 条回写不等于一切正常。工具会区分「这段时间没过账」(附最近一条的时间, 是告警)和「当前台账无历史记录」(集成配置和数据保留范围待核实)

Skill 层的一条硬规矩

Skill 给的是结论,不只是数据。所以每个 Skill 的返回都强制带两段:

  • 本次分析跳过的环节 —— 哪些模块无数据、由此带来的结论缺口。 在残缺数据上给一个笃定的归因,比不给归因危险得多。
  • 口径与限制 —— 这个结论算的到底是什么。

最要紧的是 mes_sn_genealogy 的精度声明。实测 OTDMES 的发料记录挂在领料单上 (OPERATION_ORDER_NO 是领料单号,客户A 167/167 命中领料单表),链路是:

成品 SN → PRD_UNIT_MASTER.ORDER_NO → WMS_PICKING_LIST_WORK_ORDER
        → 领料单 → 发料流水 → UNIT_MSN → 批次/供应商/到货单 → 采购订单

给出的是工单级(批次级)关联,不是 SN 级。只能确定"这批料发给了这张工单", 不能确定"这颗料装在这台机器上"。用于召回时只提供已扫码发料的工单级候选范围; 须检查分页是否查全,并核对未走 WMS 扫码发料的用料,才能进一步圈定召回对象。 有一条测试专门断言这句话在每次返回里都出现——说小了会漏召回,是要出事的。

(WMS_UNIT_TRACEABILITY_INFO 表名字像用料追溯,实测存的是 SN↔父SN 的包装层级, MPN/MANUFACTURER_NAME 全空,别拿它当用料族谱用。)

新增工具时记得同步改 tests/integration/test_tools.py 的 CALLS 与工具数断言—— 有一条测试专门检查"每个注册的工具都有冒烟调用覆盖"。


开发时最容易踩的坑

按被坑概率排序。完整清单见设计文档 §7。

1. 日期有三种风格混用,拼错不报错只出错数

SPLIT_VARCHAR   PRD_WORK_ORDERS.CREATION_DATE + .CREATION_TIME   两个 varchar
DATE_VARCHAR    PRD_WORK_ORDERS.PLAN_BEGIN_DATE                  单个 varchar
DATETIME        PRD_LOT_REPORT_MANAGEMENT.CREATE_TIME            标准 datetime

同一张表里都可能并存。永远用 dates.build_date_filter(),不要手拼 WHERE:

from mes_mcp.catalog import date_field
from mes_mcp.dates import build_date_filter

sql_frag, params = build_date_filter(
    date_field("PRD_WORK_ORDERS", "created"), "2026-08-01", "2026-08-31"
)

varchar 日期实测是 YYYY-MM-DD(带横杠),字典序比较即正确;datetime 的 end 用 < 次日零点,否则漏数据。这些差异由该函数吸收。

2. charset 必须是 CP936

各库排序规则是 Chinese_PRC_*,varchar 列存 GBK 字节。用 UTF-8 连接不报错,只是把中文静默读成 ²ð°ü×÷Òµ。集成测试里有专门的回归断言。

3. as_dict=True 下所有 SELECT 表达式必须起别名

SELECT COUNT(*) 会抛 ColumnsWithoutNamesError。写成 SELECT COUNT(*) AS n。

4. SQL Server 18456 同时代表"口令错"和"无权访问该库"

服务器故意不区分(防止探测库名)。db.py 的错误翻译把三种可能都列进建议里——本项目开发期就因为这个把权限问题误判成了口令问题。

5. 报工时间用 CREATE_TIME,不是 OPREAT_DATE

后者有 1900-01-01 哨兵值(实测 76/19750)。DateField(sentinel=True) 会自动排除。

6. WMS_OPERATION_TYPE 字典表不完备

客户B数据里有 33 条 OP_TYPE='Delete',字典表里没这一项。翻译层 fallback 返回原 code,绝不丢流水。

7. 备份表必须排除在白名单外

WMS_MATERIAL_ARRIVAL_BODY20250828、WMS_FG_STOCK_MASTER_DELETE 这类混进来会让统计翻倍。catalog.is_backup_table() 负责识别,单元测试里有守卫。

8. PRD_UNIT_HISTORY 这张表不存在

真名是 PRD_UNIT_PRD_HISTORY。现有 C# 版 Otd.MCP.Server 写错了,集成测试里有回归断言。

9. 只读守卫不能裸匹配关键词

工单状态实测有 'CREATE' 这个值,[ORDER_STATUS] = 'CREATE' 曾被守卫当成建表语句 整块拦掉(日报因此少一个板块)。_assert_readonly 现在会先抹掉字符串字面量与 [方括号标识符] 再查关键词。加新守卫规则时注意同样的陷阱。


测试

所有工具的返回文本末尾附带机器可读的 mes-evidence JSON 注释;字段定义、覆盖语义及限制见 证据契约。coverage=unknown 不能解释为完整覆盖。

pytest tests/unit          # 纯逻辑,随时可跑
pytest tests               # 含连库冒烟,需 .env

验收硬要求:每个工具都必须在客户A与客户B两个库上分别跑通。 这两家模块启用度近乎互补(一个 MES 重、一个 WMS 重),是天然的双向回归测试集。只在单库测的工具,换客户必翻车。


安全边界

MES 是生产库,边界比 ERP 更严:

  1. 数据库账号须在目标库完成有效权限验收;只检查 db_datawriter / db_owner 角色不足以证明只读,现场运行 mes-mcp --selftest
  2. db._assert_readonly 静态拒绝一切非 SELECT 语句与拼接语句
  3. 表名只能来自 catalog 白名单,且经 assert_identifier 校验
  4. 业务值一律参数绑定,不做字符串拼接
  5. 不提供裸 SQL 工具——现有 C# 版的 query_tables(where_clause=...) 让 LLM 自由写 WHERE,本项目不继承这个设计
  6. 强制 TOP 上限与查询超时
  7. 零业务写操作。唯一会改环境的是 mes_self_update——它只动本机的 Python 包, 不碰任何 MES 数据,且强制两步确认(首次调用只预览命令)。 MES_VERSION_PIN 可在客户现场冻结版本,设了之后一律拒绝升级。

CI 与发布

.github/workflows/ci.yml 每次 push / PR 跑:

  • 单元测试(Python 3.10 / 3.11 / 3.12)
  • 集成测试在无 .env 时必须全部 skip —— 否则谁在没配库的机器上跑一次 pytest 就会看到一片红
  • 工具能全部注册且都有 docstring
  • ontology 里点名的工具必须真的存在 —— 否则 Agent 照着它调会扑空
  • 独立分发的图谱页面自带 charset(在前 1024 字节内)

集成测试连的是客户内网 SQL Server,CI 上跑不了,也不该把生产库凭据放进 GitHub Secrets —— 那些应在获准的客户测试库执行;当前仅对客户 B 做过定向只读验收。

.github/workflows/release.yml 在打 v* tag 时发布到 PyPI(Trusted Publishing, 仓库里不放 token),发布前校验 tag 与 __version__ 一致 —— 打错 tag 会把 1.2.0 的代码发成 1.3.0,事后很难查。

Metadata

Release files for mes-mcp 0.4.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for mes-mcp 0.4.1
File Size Uploaded Provenance
mes_mcp-0.4.1.tar.gz 141.5 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for mes-mcp 0.4.1
File Interpreter ABI Platform
mes_mcp-0.4.1-py3-none-any.whl Python 3 none any Details

Total release size: 289.4 kB

Release files / mes_mcp-0.4.1.tar.gz

Download URL mes_mcp-0.4.1.tar.gz
Size 141.5 kB
Tags Source
SHA-256 checksum
How to use checksums
ffa9ed569cc9d5076be0a0f940597b424c15b2d9e2c709d5006c660fc3c09817
BLAKE2b-256 checksum
How to use checksums
4c6a823f3174c2b58ea4f18ac99ccf0ee569e4edb84e7503d59aa2082f631cbd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 3, 2026.

Transparency log

Release files / mes_mcp-0.4.1-py3-none-any.whl

Download URL mes_mcp-0.4.1-py3-none-any.whl
Size 147.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
342ed1a586681ee1bce62477ce0ead4512beb673b11ffefc3ee33e3a0db5f7d1
BLAKE2b-256 checksum
How to use checksums
def2c250f7c6ff6ef6b29ff653a26a6da38355937a65402a890dbd360b29d0a6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 3, 2026.

Transparency log

Security for mes-mcp 0.4.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for mes_mcp-0.4.1.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 3, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page