18 · MCP 服务器

相关:第 15 章 自动化 API 总览 · 第 16 章 自动化 API 方法速查 · docs/MCP服务器-外部AI连接说明.md

这一章是什么

MCP(Model Context Protocol)是一套"让 AI 连上你的工具"的标准协议。 Hmi.Mcp.Host 就是一个 MCP 服务器 —— 跑起来之后,Claude、Cursor 这类 AI 就能直接读写你的工程。

什么时候你会需要它:

  • 你想让 AI 帮你批量改点表、批量建画面,而不是自己一个个点
  • 你想用自然语言改工程:"把所有温度点的报警上限调成 120"
  • 你要给公司内部的 AI 工具开一个组态入口

它是怎么做到的:MCP 工具全部是自动化 API 的薄包装 —— 每个工具就是调一下 IProjectAutomation 上的对应方法,把结果转成 JSON 回给 AI。

所以:第 15、16 章讲的能力,MCP 全都有;第 16 章没讲的能力,MCP 也没有。


一、先搞明白:它能做什么、不能做什么

能做

感知(只读) 看工程里有什么 —— 画面、点、通道、协议、配方、报警配置…
组态(可写) 改工程文件 —— 加删点、建画面、摆元素、配报警、配历史、写脚本…

不能做

为什么
写实时值 / 下发 PLC 有物理后果。这条边界贯穿整个软件
启停设备 同上
改运行态 只碰磁盘上的工程文件,不碰内存里的运行数据

这是设计上刻意不开的,不是"还没做"。MCP 工具清单里一个运行态写值工具都没有。

想理解为什么这么设计:因为 AI 会犯错。组态改了可以撤销、可以重新打开工程; 而给一台运行中的锅炉下发错误设定值,可能直接出事故。

也不提供的东西

能力 状态
prompts(提示词模板) 不暴露 —— 请求会回错误码 -32601(方法不存在)
resources(资源) 不暴露 —— 同上
instructions(服务器说明) 不带(为 None)

提示词从哪来? —— 全部走每个工具自己的 description,包括:

  • 工具级 [Description] —— 说这个工具干什么
  • 参数级 [Description] —— 说每个参数填什么

覆盖率 100%,而且有测试钉死(少一个就红)。为什么这么较真: 这两样东西就是给 AI 的提示词。缺一个,AI 就不知道该填什么,只能瞎猜。


二、exe 在哪

MCP 服务器是个独立 exe,随发行包一起提供,不需要编译:

<安装目录>\
  组态编辑器.exe            ← 主程序,双击进组态开发环境
  mcp\Hmi.Mcp.Host.exe      ← MCP 服务器(就是它)
  project\                  ← 随包自带的示例工程(含 project.json)

它在主程序旁边的 mcp 子目录里,是一个独立进程(与主程序不是同一个进程,也不共用运行时)。 配置时路径别写成主程序那个 exe。


三、命令行参数

Hmi.Mcp.Host.exe <工程目录> [选项]
参数 默认值 意思
<工程目录> — 第一个不以 -- 开头的参数就是工程目录
--http [<host> <port>] 127.0.0.1 8765 用 http 模式(不写就是 stdio 模式)
--token <口令> 无 http 模式的访问口令
--read-only 关 只读模式:只暴露感知类工具
--tools a,b,c 无 只暴露这几个工具(逗号分隔)

也可以用环境变量 HMI_PROJECT_DIR 指定工程目录。

⚠ 参数解析很宽松,几个要注意的

情况 行为
没有 --help 不存在这个参数
不认识的 --xxx 静默忽略(不报错、不提示)
参数缺值 / 端口填了非数字 宽松回落到默认值(不报错)
--stdio ⚠ 不是真参数 —— 传了也被忽略。默认就是 stdio

⚠ 最后一条容易误导:tools/mcp-probe.mjs 里传了 --stdio,看着像是"显式选了 stdio 模式", 其实是被忽略的,生效的是默认值。写配置时别以为必须传它。

启动失败的返回码

情况 返回码
没给工程目录 2(并往 stderr 打用法)
目录不存在 2
打开工程失败 2
工具被过滤成空集 2

四、两种连接方式

4.1 stdio 模式(默认)

服务器不说话的时候不占任何东西,通过标准输入输出和客户端通信。

Hmi.Mcp.Host.exe "D:\我的工程"

适合:Claude Desktop、Cursor 这类自己管子进程的客户端。 客户端拉起这个 exe,往它 stdin 写请求、从 stdout 读回答。

4.2 http 模式(常驻)

监听一个端口,多个客户端可以连同一个实例。

Hmi.Mcp.Host.exe "D:\我的工程" --http 127.0.0.1 8765
地址 http://127.0.0.1:8765/mcp
鉴权 设了 --token 才校验

鉴权方式(任选一种):

Authorization: Bearer <token>
X-Api-Key: <token>

不匹配回 401。没设 token 就不校验 —— 所以本机用可以不管, 但如果要监听 0.0.0.0(别人也能连),一定要设 token。

适合:多个工具连同一个实例、或只支持 http 的客户端。

4.3 在组态器里启停

组态器界面里「工具 → MCP 服务器」面板可以一键启停,不用敲命令行。 配置存在 %APPDATA%\FangLikong\mcp-server.json,字段和命令行参数一一对应。

详见第 17 章第七节。


五、能用的工具:共 134 个

感知 57 个、其余 77 个(组态 + 删除)。

这几个数字和下面两张表的每一行都由测试直读本章核对 (tests/Hmi.Mcp.Host.Tests/McpToolShapeTests.cs 的 Tool_count_in_the_ai_facing_doc_matches_reality + Manual_counts_match_reality + Manual_section_inventories_match_reality)。 改工具清单必须同步改这里,否则测试红。

两张表是生成的:python tools/gen-mcp-manual.py --write 读 src/Hmi.Mcp.Host/ProjectMcpTools.cs 的每个 [Description] 重建(已有行的人工文案保留), 分组按「动词 + 名词」规则推。别手改行 —— 手改的部分下次生成就没了; 真要改文案,改 [Description]。

5.1 感知类(只读)

看工程整体

工具 干什么
get_project 读工程元信息(名/版本/启动画面/说明)
get_project_tree 工程树总览:画面→元素(名/类型)、点表规模、通道→设备、配方、报警/历史条数、报表
get_theme 读取工程级语义色板
validate_project 整份工程校验(点 Id 连续性、重名、引用有效性)

画面与元素

工具 干什么
describe_element_props 取某元素类型的可写属性清单(改属性前必须先调它)
get_element 读单个元素的完整模型(类型/全部属性/绑定/事件)
get_screen 取一个画面的完整模型
list_element_types 列所有可用元件类型
list_elements 列画面里的元素
list_screens 列所有画面
list_symbols 列现成工业符号库
validate_element 干跑校验:建元素前先检查一组属性能否落在某元素类型上(可写属性名/值可转/闭集枚举合法)

点表与通道

工具 干什么
describe_protocol 描述一个协议支持哪些连接方式及参数
get_tag 取一个点的定义
list_channels 列所有通道/设备
list_global_variables 列全局内部变量
list_protocols 列可用协议 + 地址语法提示
list_screen_variables 列某画面的窗口变量
list_tag_types 列可选点类型 + 每种类型哪些字段有意义
list_tags 列所有点位定义
read_tag_once 单次读一个点的当前值(只读一次,不订阅、不写值)
test_channel 连通测试一个通道(只读探测,绝不下发写)

脚本

工具 干什么
build_project 编译校验整份工程的所有脚本
check_script 只编不跑一段脚本(查语法错)
get_element_events 读元素事件脚本
get_module 取一个模块的完整内容
get_project_scripts 读工程级脚本
get_screen_scripts 读画面脚本
list_function_catalog 列内置脚本函数目录(15 库 / 176 函数)
list_modules 列脚本模块
list_project_periodics 列后台定时脚本(含触发方式 At/IntervalMs 与中文描述 Trigger)

配方 / 报警 / 历史 / 报表

工具 干什么
get_alarm 取一个点的报警配置
get_history_rule 取一个点的历史规则
get_recipe 取配方完整内容
get_report 取报表定义
list_alarms 列所有点的报警配置
list_history_rules 列所有点的历史规则
list_recipes 列配方
list_reports 列报表定义
query_report 按已定义的报表出一张真实表格(AI 辅助调试用):Historian 源查历史库聚合

多语言 / 皮肤

工具 干什么
get_i18n 读整份多语言表
get_translation 查某源文本在某语言下的译文
list_button_skins 列自定义按钮皮肤
list_languages 列语言 + 译条数
list_style_presets 列内置外观预设的 Id/中文名/分类

转发 / 通知 / 启动 / 发布

工具 干什么
get_forward 取一条转发规则的完整定义
get_notify_config 读通知配置(SMTP/收件人/触发级别等)
get_publish_profile 读交付发布配置(exe 文件名/产品信息/图标/版本;含留空回退值)
get_startup_config 读启动配置:启动画面 + 启动/退出时要调的全局子程序清单
list_forwards 列所有北向转发规则

数据库

工具 干什么
list_db_connections 列工程级数据库连接 + 可用驱动清单

导出类(只读,但有输出)

工具 干什么
export_project 导出 .hmiproj 归档
export_recipe 导出配方为 JSON 文本
export_tags 导出整张点表为 JSON 文本
export_translations_csv 把多语言表导出成 CSV 文本(第一列原文,其余每语言一列)
pack_project 封成加密运行封包 .hmipkg

类型结构

工具 干什么
describe_type_schema 查一个嵌套/复杂类型的字段结构(名/类型/枚举合法值/默认值/是否必填)

为什么"导出类"和 build_project 也算感知:它们不改工程模型 —— export_* / pack_project 只是把现有内容导出,build_project 只编不跑、回一份诊断。判据的唯一真源是 src/Hmi.Automation/SensingTools.cs:前缀 get_ list_ describe_ query_ read_ validate_ check_, 外加显式名单里的 export_project export_tags export_recipe export_translations_csv pack_project build_project test_channel read_tag_once query_report。 配 readOnly: true 时只有感知类会被暴露出去。

5.2 组态类(可写)

画面

工具 干什么
create_screen 新建画面
delete_screen 删画面
duplicate_screen 复制整张画面(深拷贝控件树+全部属性/绑定/事件)
rename_screen 画面改名
set_screen_props 改画面属性(尺寸/背景/网格)

元素

工具 干什么
add_element 加元素
align_elements 把多个元素对齐到统一基准(以选中元素的外包围盒为参照)
apply_style 套预置样式
distribute_elements 把多个元素沿某方向均匀分布(相邻元素边到边的间隙相等,首尾位置不动)
duplicate_element 复制元素(深拷贝,含全部属性/绑定/事件),按偏移摆放
group_elements 把多个元素成组(扁平分组,同 GroupId 即一组):校验都在(缺则整批不改)
move_element 改几何(位置/大小/旋转)
remove_binding 删绑定
remove_element 删元素
rename_element 元素改名,保留其绑定/事件/属性(优于「删了重建」——重建会丢绑定和事件)
reorder_element 调图层次序
set_binding 设/改绑定
set_element_props 改非几何属性(文本/颜色/字号/动作)
set_elements_props 批量改多个元素的同一组属性
ungroup_elements 解组:传入任一成员即解散它所在的整组(清空该组所有成员的 GroupId)

点 / 变量

工具 干什么
add_internal_variable 加全局内部变量
add_screen_variable 加窗口变量
add_tag 加点
import_tags 从 JSON 文本(单个点或点数组)导入点表并按点名合并(同名覆盖)
remove_tag 删点
update_tag 改点定义

通道 / 设备

工具 干什么
add_channel 加通道
add_device 加设备
remove_channel 删通道
remove_device 删设备
update_channel 改通道
update_device 改设备

脚本

工具 干什么
add_module 新建脚本模块
delete_module 删一个脚本模块(引用它的脚本编译时会报未定义,删前先确认没被引用)
remove_project_periodic 删后台定时脚本
save_module 新建/整体保存一个脚本模块(整个工程共享的函数库)
set_element_events 整体保存一个元素的事件脚本集(Handlers:事件名→脚本,如按钮 Click)
set_project_periodic 加/改后台定时脚本(按间隔 intervalMs,或按时刻 at 如 *:00)
set_project_scripts 整体保存工程级脚本
set_screen_scripts 整体保存一个画面的脚本(OnEnter/OnExit/Periodic)

配方

工具 干什么
create_recipe 建空配方
delete_recipe 删配方
import_recipes 从 JSON 导入配方
remove_recipe_item 删配方项
set_recipe_element_item 设配方里的编辑框目标
set_recipe_item 设配方里的点目标
set_recipe_remark 设备注

报警 / 历史 / 报表

工具 干什么
remove_alarm 删报警配置
remove_history_rule 删历史规则
remove_report 删报表定义
set_alarm 设/改点的报警配置
set_alarms_bulk 批量设/改报警配置(一次多条,省逐条 100 次往返;100 台机组配同类报警就用它)
set_history_rule 设/改点的历史规则
set_history_rules_bulk 批量设/改历史记录规则(一次多条)
set_report 设/改报表定义

多语言

工具 干什么
add_language 加目标语言
extract_translations 从画面抽取可翻译文本
import_translations_csv 从 CSV 文本导入译文(表头首列为原文、其余列为语言代码;按原文匹配填充)
remove_language 删一个目标语言:从目标语言列表移除,并清掉每条译条里这门语言的译文(源文本保留)
remove_translation 删译文(与 set_translation 对称)
set_default_language 设默认(源)语言代码,如 zh-CN / en-US
set_i18n 整份覆盖多语言表
set_translation 设一条译文

皮肤

工具 干什么
add_button_skin 加自定义按钮皮肤
remove_button_skin 删皮肤
update_button_skin 改皮肤

转发 / 通知 / 启动 / 发布 / 工程信息

工具 干什么
remove_forward 删一条转发规则
set_forward 设/改一条北向转发规则(同名整体覆盖)
set_notify_config 写通知配置(整份替换)
set_project_info 写工程元信息:工程名 name / 说明 description
set_publish_profile 写交付发布配置
set_startup_config 写启动配置
set_theme_color 新增或修改工程级语义色角色

数据库

工具 干什么
delete_db_connection 删除一个工程级数据库连接(不动物理数据文件——那是用户数据)
set_db_connection 新增或修改一个工程级数据库连接(按连接名匹配,忽略大小写)

持久化

工具 干什么
import_project 从归档还原工程
save_project 校验 + 落盘

⚠ 组态完必须调 save_project

add_tag / create_screen 这些只改内存,不落盘。 最后一定要调 save_project,否则改了半天磁盘上没变。

这一点对 AI 尤其重要 —— 它可能会觉得"我调了 add_tag 就是加好了"。 工具级 description 里写了这件事,但要提醒用 AI 的人注意。


六、工具过滤

McpToolFilter 决定哪些工具暴露出去:

public static bool IsSensing(string name);                       // 是不是感知类
public static IReadOnlyCollection<string> Build(
    ProjectAutomation automation, bool readOnly, List<string>? whitelist);
参数 效果
readOnly = true 只暴露感知类
whitelist 只暴露清单里的
两个都给了 是"与"关系 —— 既要在白名单里,又要符合 readOnly

readOnly 和 whitelist 是与关系,不是或 —— 别指望"白名单里的写工具能绕过 readOnly"。

只有 http 模式用过滤。stdio 模式全量暴露。 所以 --read-only 只在 http 模式下有意义。


七、怎么在客户端里配上

Claude Desktop

改 claude_desktop_config.json:

{
  "mcpServers": {
    "hmi": {
      "command": "C:\\app\\mcp\\Hmi.Mcp.Host.exe",
      "args": ["D:\\我的工程"]
    }
  }
}

Cursor

改 ~/.cursor/mcp.json,格式同上。

Claude Code

claude mcp add hmi -- "C:\app\mcp\Hmi.Mcp.Host.exe" "D:\我的工程"

⚠ 改完要完全重启客户端

不是关窗口 —— 要真的退出进程。很多客户端只在启动时读一次配置。


八、自检:先确认它真的能用

有个探针脚本:

node tools/mcp-probe.mjs
用法 说明
node tools/mcp-probe.mjs 默认用 samples/DemoProject
node tools/mcp-probe.mjs <工程目录> 指定工程
node tools/mcp-probe.mjs --json 输出 JSON

它做什么:

  1. 自己 spawn 这个 exe(自动在 Debug / Release 里找)
  2. 走一遍协议:initialize → notifications/initialized → tools/list → prompts/list → resources/list
  3. 统计工具数、参数数、缺描述项
  4. 验证 prompts / resources 是不是真的回 -32601

退出码:有工具缺描述、或有参数缺描述 → 非 0。所以可以当 CI 闸门用。

配客户端之前先跑一遍这个 —— 如果探针都跑不通,说明 exe 没编译好或工程目录不对, 那客户端里配了也是白配。


九、一个完整的使用例子

场景:让 AI 帮我把所有温度点的报警上限统一改成 120。

你(在 Claude Desktop 里)说:

帮我把这个工程里所有名字带"温度"的点,报警上限 Hi 改成 120、HiHi 改成 130, 回差 2,延时 3 秒。改完保存。

AI 大概会这么做:

① list_tags                       → 拿到所有点,筛出名字含"温度"的
② 对每个点:
     get_alarm("烤箱.温度")        → 先看现有配置(了解结构)
     set_alarm(tagName:"烤箱.温度", deadband:2, delaySeconds:3,
               hi:   {Limit:120, Level:"Alarm"},
               hiHi: {Limit:130, Level:"Critical"})
③ save_project                    → 落盘
④ 回报:改了 12 个点

⚠ set_alarm 的参数形状:hi / hiHi / lo / loLo 收的是对象 ({Limit, Level, Message}),不是数字;deadband 是回差、delaySeconds 是秒。 这是 MCP 层的入参形态,和 AlarmConfigModel 的字段名一致(hiHi → HiHi)。 详见第 16 章第 8 组。

注意 get_alarm 那一步:如果这个点本来没配报警,get_alarm 会返回失败 —— 这是正常的,直接 set_alarm 就行。

如果 AI 改元素属性改错了,大概率是它没先调 describe_element_props。 提醒它:"先查一下这个元素有哪些可写属性"。


十、这一章的坑

# 坑 怎么避
1 改完忘了 save_project 内存改了磁盘没变。AI 最容易漏这步
2 以为 --stdio 是真参数 它被静默忽略,默认就是 stdio
3 以为有 --help 没有。参数写错了也不报错,静默回落默认值
4 --read-only 只在 http 模式生效 stdio 模式全量暴露
5 readOnly + whitelist 想成"或" 是"与"
6 http 监听 0.0.0.0 又不设 token 谁都能改你的工程
7 改完客户端没重启 要真的退出进程
8 set_element_props 属性名瞎猜 先 describe_element_props
9 指望它写实时值 没有这类工具,设计上不开
10 点 Id 跳号导致 save_project 失败 别自己指定 Id

十一、和自动化 API 的对应关系

MCP 工具就是自动化 API 的包装。对照着看:

MCP 工具名 对应 IProjectAutomation 方法
get_project GetProject()
add_tag AddTag(tag)
set_alarm SetAlarm(config)
save_project Save()
validate_project Validate()
… …

唯一的不同:MCP 工具名用 snake_case(add_tag), 自动化 API 方法名用 PascalCase(AddTag)。

所以自动化 API 加一个方法,想让它能通过 MCP 用,就要在 ProjectMcpTools 里加一个对应的工具方法, 并且工具级和参数级的 [Description] 都要写 —— 不然测试会红。


本章内容以 src/Hmi.Mcp.Host/ 与 docs/MCP服务器-外部AI连接说明.md 为准。 工具计数被测试直读,改工具必须同步改文档。