|
|
před 3 týdny | |
|---|---|---|
| instrument_config_mcp | před 3 týdny | |
| scripts | před 3 týdny | |
| .dockerignore | před 3 týdny | |
| .gitignore | před 3 týdny | |
| Dockerfile | před 3 týdny | |
| README.md | před 3 týdny | |
| mcp-design.md | před 3 týdny | |
| pyproject.toml | před 3 týdny | |
| uv.lock | před 3 týdny |
基于 FastMCP 的只读配置查询 MCP 服务,用于访问以下接口:
服务特点:
project_key 选择项目环境id 为准sys_config 表DATABASE_URL 指向包含 sys_config 的数据库Windows 下建议固定使用 uv 的 Python 3.13 环境,避免 pywin32 在 Python 3.14 下的兼容问题。
默认数据库地址:
sqlite:///llm_proxy.db
默认上游超时:
UPSTREAM_REQUEST_TIMEOUT=60
服务会从 sys_config 中读取 key 为 mcp_project_data_projects 的 JSON 数组。
示例:
[
{
"project_key": "dev-01",
"project_name": "DEV开发环境",
"base_url": "http://192.168.1.109:32080",
"username": "admin",
"password": "123456",
"enabled": true
}
]
在项目根目录执行:
uv sync --python 3.13
默认以 HTTP 模式启动,默认地址:http://127.0.0.1:8500/mcp
方式一:使用模块启动
uv run --python 3.13 python -m instrument_config_mcp
方式二:使用 console script 启动
uv run --python 3.13 instrument-config-mcp
可选环境变量:
MCP_HOST,默认 127.0.0.1MCP_PORT,默认 8500MCP_PATH,默认 /mcp如果需要指定数据库:
set DATABASE_URL=sqlite:///llm_proxy.db
uv run --python 3.13 python -m instrument_config_mcp
如果需要指定 HTTP 地址:
set MCP_HOST=0.0.0.0
set MCP_PORT=8500
set MCP_PATH=/mcp
uv run --python 3.13 python -m instrument_config_mcp
如果需要指定上游超时:
set UPSTREAM_REQUEST_TIMEOUT=60
uv run --python 3.13 python -m instrument_config_mcp
当前提供以下工具:
project.listlist_locationslist_system_treelist_systemslist_device_typeslist_meter_typessearch_devicessearch_meterssearch_pointstopology_group_listtopology_listtopology_get_nodetopology_find_context除 project.list 外,其他工具都必须传 project_key。
当前拓扑能力采用“上游拉取 + 本地缓存索引 + 缓存查询工具”的模式。
目标:
当前实现主要落在以下文件中:
instrument_config_mcp/config_api.pyinstrument_config_mcp/topology_cache.pyinstrument_config_mcp/server.py当前已落地的核心能力:
当前代码实际使用的上游接口只有两个,均定义在 instrument_config_mcp/config_api.py:
| Python function | Upstream API | Purpose |
|---|---|---|
list_topologies_with_group(project_key, group_ids=None) |
POST /api/configapi/topo/list_with_group |
拉取拓扑分组与拓扑列表 |
get_topology(project_key, id) |
POST /api/configapi/topo/get |
拉取单张拓扑详情 |
说明:
topo/get_data 的封装与使用核心入口是 refresh_topology_cache(project_key, topology_ids=None)。
实际行为:
project_keylist_topologies_with_group(project_key, group_ids=[]) 拉取全量拓扑树topology_ids,只刷新指定拓扑;否则刷新该项目全部拓扑get_topology(project_key, topology_id)data.diagram 形态选择解析策略:list 走树形拓扑,dict 走图形拓扑刷新结果会返回以下统计字段:
refreshed_group_countrefreshed_topology_countrefreshed_node_countrefreshed_edge_countrefreshed_entity_index_counttopology_idsrefreshed_at刷新模式:
| Mode | Trigger | Behavior |
|---|---|---|
| Full refresh | topology_ids is None |
删除该项目全部拓扑缓存,再重建全部分组、拓扑、节点、边、实体索引 |
| Partial refresh | topology_ids 非空 |
仅删除指定拓扑对应的节点、边、实体索引、拓扑注册信息,但会重建全量分组表 |
说明:
入口函数:_parse_tree_topology(project_key, topology_id, diagram)
处理逻辑:
TopologyNodeTopologyEdgemeter_list / device_list 中抽取实体引用入口函数:_parse_graph_topology(project_key, topology_id, diagram)
处理逻辑:
diagram.nodes 和 diagram.edges_build_graph_node_context(...) 推导节点层级、父节点关系、路径文本、子节点数量TopologyNodeTopologyEdgemeter_list / device_list 中抽取实体引用当前实现采用统一实体索引表:
entity_type = meter | deviceentity_idtopology_idnode_iddepth其中 depth 表示该实体命中的节点深度。
当前通过 _record_deepest_entities(...) 保证:
node_id当前数据库模型定义在 instrument_config_mcp/topology_cache.py 中。
topology_group保存拓扑分组树。
关键字段:
project_keygroup_idgroup_nameparent_group_idgroup_path_textlevelsort_indexrefreshed_atis_active关键约束和索引:
uq_topology_group_project_groupix_topology_group_project_parenttopology_registry保存拓扑主信息。
关键字段:
project_keytopology_idtopology_nametopology_typeobject_type_codegroup_idroot_shapesource_updated_timerefreshed_atis_active关键约束和索引:
uq_topology_registry_project_topologyix_topology_registry_project_grouptopology_node保存拓扑节点。
关键字段:
project_keytopology_idnode_idnode_nameparent_node_idlevelnode_type_coderefer_idrefer_levelis_virtualpath_textchild_countsort_index关键约束和索引:
uq_topology_node_project_topology_nodeix_topology_node_project_topology_parentix_topology_node_project_topology_refertopology_edge保存节点边关系。
关键字段:
project_keytopology_idsource_node_idtarget_node_idsort_index关键约束和索引:
uq_topology_edge_project_topology_nodesix_topology_edge_project_topology_sourceix_topology_edge_project_topology_targettopology_entity_index保存设备/仪表到节点的反查索引。
关键字段:
project_keyentity_typeentity_idtopology_idnode_iddepth关键约束和索引:
uq_topology_entity_index_project_entity_topology_nodeix_topology_entity_index_project_entityix_topology_entity_index_project_topology_nodeSchema 说明:
topology_entity_index 同时承载 meter 和 device 两类实体关系topology_group_list(project_key)用途:
输出结构:
project_keygroupstotaltopology_list(project_key, group_id=None, object_type_code=None)用途:
输出结构:
project_keytopologiestotaltopologies[] 当前包含:
topology_idtopology_nametopology_typeobject_type_codegroup_idgroup_path_textroot_shaperefreshed_attopology_get_node(project_key, topology_id, node_id, include_siblings=True, include_children=True)用途:
输出结构:
topologynodeparentschildrensiblingstopology_find_context(project_key, entity_type, entity_id, topology_id=None, include_siblings=True, ancestor_depth=5, descendant_depth=2)用途:
输出结构:
querymatchestotal_matches每个 matches[] 当前包含:
topologyselfparentschildrenancestorsdescendantssiblingstopology.group_list 直接从 topology_group 组装树结构返回,不再依赖上游实时调用。
topology.list 的过滤逻辑:
group_id,会把该分组及其全部子分组都纳入可见范围object_type_code,再叠加对象类型过滤topology.get_node 返回的是一个节点的直接邻域:
它不做深层遍历。
topology.find_context 会:
topology_entity_index当前拓扑缓存不是单独写入 JSON 文件,而是落在服务使用的数据库中。
数据库连接来源:
instrument_config_mcp/db.py 中的 database_url()sqlite:///llm_proxy.dbDATABASE_URL,则以环境变量为准以默认配置为例:
llm_proxy.dbsys_config 表都在同一个数据库中当前仓库没有单独提供 .sql 建表脚本,也没有 migration 目录。
建表行为分两类:
scripts/init_local_sys_config.py 会执行 Base.metadata.create_all(engine),适合部署初始化时一次性创建 ORM 已注册的表ensure_topology_cache_tables(),内部同样使用 Base.metadata.create_all(sql_engine())这意味着:
除了 MCP 工具,当前还额外暴露了一个内部路由:
GET /topology/cache/refresh?project_key=...对应函数:refresh_topology_cache_route
能力:
topology_id 做局部刷新说明:
include_in_schema=False从当前代码和设计对照看,还存在这些空缺:
topology.get_structure 这种直接返回整张拓扑缓存结构的工具page_size=100page_nummcp_note示例说明:
{
"state": 0,
"state_info": "OK",
"data": {
"page_size": 100,
"page_num": 1,
"total_page": 8,
"total": 115,
"data": []
},
"mcp_note": "Current result is page 1. If the target was not found, continue with page_num=2."
}
仓库内提供了一个简单的 smoke test 脚本:scripts/smoke_test.py
示例 1:查询位置第一页
uv run --python 3.13 python scripts/smoke_test.py list-locations --project-key dev-01 --keyword F1
示例 2:查询系统树
uv run --python 3.13 python scripts/smoke_test.py list-system-tree --project-key dev-01
示例 3:查询设备类型
uv run --python 3.13 python scripts/smoke_test.py list-device-types --project-key dev-01
示例 4:按位置搜索仪表
uv run --python 3.13 python scripts/smoke_test.py search-meters --project-key dev-01 --location-id 162 --show-below true --page-num 1
示例 5:按系统和设备类型搜索设备
uv run --python 3.13 python scripts/smoke_test.py search-devices --project-key dev-01 --system-ids 21 --device-type-ids 5
示例 6:查询某个仪表下的点位
uv run --python 3.13 python scripts/smoke_test.py search-points --project-key dev-01 --id 1785 --page-num 1
list-device-types 和 list-meter-types,确认认证链路正常list-locations 或 list-system-tree,确认配置接口可访问search-devices、search-meters 或 search-points 做正式搜索如果需要另起一个命令行版 opencode 来测试本地 MCP,建议始终使用“隔离数据库 + 隔离用户目录”的方式启动,避免影响当前桌面版 OpenCode 正在使用的会话数据库。
如果直接在当前桌面版 OpenCode 环境里再启动 opencode run,可能会遇到以下问题:
Session not found因此,命令行测试时不要直接复用桌面版进程环境。
uv run --python 3.13 python -m instrument_config_mcp
默认地址:http://127.0.0.1:8500/mcp
C:\Users\Guangyu\.local\share\opencode\auth.json
如果 openai-gw 需要鉴权,命令行隔离环境里也要提供这份文件。
下面的步骤会创建一套临时 OpenCode 用户目录,并把 auth.json 复制进去。这样可以保证:
openai-gw 鉴权$root = 'C:\Users\Guangyu\AppData\Local\Temp\oc-userprofile-openai-gw'
New-Item -ItemType Directory -Force -Path "$root\.local\share\opencode","$root\.cache","$root\AppData\Roaming","$root\AppData\Local" | Out-Null
Copy-Item 'C:\Users\Guangyu\.local\share\opencode\auth.json' "$root\.local\share\opencode\auth.json" -Force
这一组环境变量如果被继承,opencode run 可能会直接报 Session not found:
Remove-Item Env:OPENCODE_SERVER_PASSWORD -ErrorAction SilentlyContinue
Remove-Item Env:OPENCODE_SERVER_USERNAME -ErrorAction SilentlyContinue
Remove-Item Env:OPENCODE_CLIENT -ErrorAction SilentlyContinue
Remove-Item Env:OPENCODE_PID -ErrorAction SilentlyContinue
Remove-Item Env:OPENAI_API_KEY -ErrorAction SilentlyContinue
$env:USERPROFILE = $root
$env:HOMEDRIVE = 'C:'
$env:HOMEPATH = '\Users\Guangyu\AppData\Local\Temp\oc-userprofile-openai-gw'
$env:HOME = $root
$env:XDG_CONFIG_HOME = 'C:\Users\Guangyu\.config'
$env:XDG_DATA_HOME = "$root\.local\share"
$env:XDG_CACHE_HOME = "$root\.cache"
$env:APPDATA = "$root\AppData\Roaming"
$env:LOCALAPPDATA = "$root\AppData\Local"
opencode db path
输出应类似:
C:\Users\Guangyu\AppData\Local\Temp\oc-userprofile-openai-gw\.local\share\opencode\opencode.db
如果这里仍然指向默认的 C:\Users\Guangyu\.local\share\opencode\opencode.db,说明隔离没有生效,不要继续测试。
openai-gw 跑最小化 MCP 测试例如先验证 project.list:
opencode --pure run --format json --print-logs --log-level INFO --model openai-gw/gpt-5.4 --agent build --dir "C:\Guangyu\Projects\instrument-data-mcp" "调用 instrument-config-local MCP 的 project.list 工具列出项目,然后停止。不要修改任何文件。"
如果要测试拓扑工具,可以改成:
opencode --pure run --format json --print-logs --log-level INFO --model openai-gw/gpt-5.4 --agent build --dir "C:\Guangyu\Projects\instrument-data-mcp" "调用 instrument-config-local MCP 的 topology.group_list 和 topology.list。参数 project_key=topo-test。只用一句话总结分组数量、拓扑数量,以及前 3 个 topology_id。不要修改文件,不要做额外操作。"
opencode db path 指向临时目录project.listtopology.group_list / topology.listtopology.find_context / topology.get_nodeSession not found通常不是 MCP 服务的问题,而是命令行继承了桌面版 OpenCode 注入的环境变量。
优先检查:
OPENCODE_SERVER_PASSWORDOPENCODE_SERVER_USERNAMEOPENCODE_CLIENTOPENCODE_PIDopencode db path 是否真的指向临时目录401 Invalid secret key说明隔离环境里没有可用的 auth.json,或者 openai-gw 凭证没有被复制到隔离数据目录。
优先检查:
C:\Users\Guangyu\.local\share\opencode\auth.json 是否存在$root\.local\share\opencode\auth.json优先检查本地 MCP 是否真的在监听:
Invoke-WebRequest -UseBasicParsing 'http://127.0.0.1:8500/mcp' -Method Post -ContentType 'application/json' -Body '{}'
只要不是“无法连接到远程服务器”,通常说明服务已经起来了。
不要直接在桌面版 OpenCode 已打开的同一个环境里运行:
opencode run ...
尤其不要在未隔离 USERPROFILE/HOME/XDG_DATA_HOME 的情况下直接测试,否则有机会干扰当前 GUI 会话数据库或命中桌面版 session 状态。
project_key not found说明 sys_config 中没有对应的 project_key。
project 'xxx' is disabled说明项目配置存在,但 enabled=false。
login failed重点检查:
base_urlusernamepasswordupstream returned non-JSON response说明上游接口返回的不是 JSON,通常需要检查:
核心文件:
instrument_config_mcp/auth.pyinstrument_config_mcp/db.pyinstrument_config_mcp/config_api.pyinstrument_config_mcp/server.py语法校验:
uv run --python 3.13 python -m compileall .