plctap 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- plctap-0.1.0/.github/workflows/ci.yml +16 -0
- plctap-0.1.0/.github/workflows/publish.yml +23 -0
- plctap-0.1.0/.gitignore +28 -0
- plctap-0.1.0/ARCHITECTURE.md +178 -0
- plctap-0.1.0/BUILD.md +121 -0
- plctap-0.1.0/HANDOFF.md +135 -0
- plctap-0.1.0/LICENSE +21 -0
- plctap-0.1.0/PKG-INFO +122 -0
- plctap-0.1.0/PLAN.md +90 -0
- plctap-0.1.0/README.md +95 -0
- plctap-0.1.0/docs/PUBLISH.md +85 -0
- plctap-0.1.0/docs/demo.gif +0 -0
- plctap-0.1.0/docs/demo.png +0 -0
- plctap-0.1.0/eval/README.md +40 -0
- plctap-0.1.0/eval/baseline.py +143 -0
- plctap-0.1.0/eval/benchmark.py +170 -0
- plctap-0.1.0/eval/corpus/modbus_m2.yaml +167 -0
- plctap-0.1.0/pyproject.toml +70 -0
- plctap-0.1.0/scripts/integration_protoforge.py +48 -0
- plctap-0.1.0/scripts/make_demo_gif.py +216 -0
- plctap-0.1.0/skill/SKILL.md +31 -0
- plctap-0.1.0/src/plctap/__init__.py +6 -0
- plctap-0.1.0/src/plctap/config.py +44 -0
- plctap-0.1.0/src/plctap/conn/__init__.py +1 -0
- plctap-0.1.0/src/plctap/conn/manager.py +165 -0
- plctap-0.1.0/src/plctap/diag/__init__.py +1 -0
- plctap-0.1.0/src/plctap/diag/engine.py +265 -0
- plctap-0.1.0/src/plctap/diag/kb.yaml +593 -0
- plctap-0.1.0/src/plctap/listener.py +279 -0
- plctap-0.1.0/src/plctap/models.py +138 -0
- plctap-0.1.0/src/plctap/pcap.py +127 -0
- plctap-0.1.0/src/plctap/protocols/__init__.py +25 -0
- plctap-0.1.0/src/plctap/protocols/auto.py +76 -0
- plctap-0.1.0/src/plctap/protocols/base.py +154 -0
- plctap-0.1.0/src/plctap/protocols/common.py +53 -0
- plctap-0.1.0/src/plctap/protocols/fins/__init__.py +9 -0
- plctap-0.1.0/src/plctap/protocols/fins/adapter.py +251 -0
- plctap-0.1.0/src/plctap/protocols/fins/codec.py +408 -0
- plctap-0.1.0/src/plctap/protocols/melsec/__init__.py +9 -0
- plctap-0.1.0/src/plctap/protocols/melsec/adapter.py +171 -0
- plctap-0.1.0/src/plctap/protocols/melsec/codec.py +329 -0
- plctap-0.1.0/src/plctap/protocols/modbus/__init__.py +5 -0
- plctap-0.1.0/src/plctap/protocols/modbus/adapter.py +304 -0
- plctap-0.1.0/src/plctap/protocols/modbus/codec.py +601 -0
- plctap-0.1.0/src/plctap/safety.py +48 -0
- plctap-0.1.0/src/plctap/server.py +379 -0
- plctap-0.1.0/src/plctap/streams.py +74 -0
- plctap-0.1.0/tests/test_adapter_fins_melsec.py +365 -0
- plctap-0.1.0/tests/test_adapter_modbus.py +339 -0
- plctap-0.1.0/tests/test_codec_fins.py +253 -0
- plctap-0.1.0/tests/test_codec_melsec.py +176 -0
- plctap-0.1.0/tests/test_codec_modbus.py +374 -0
- plctap-0.1.0/tests/test_diag.py +218 -0
- plctap-0.1.0/tests/test_eval.py +44 -0
- plctap-0.1.0/tests/test_listener.py +226 -0
- plctap-0.1.0/tests/test_pcap.py +88 -0
- plctap-0.1.0/tests/test_smoke.py +175 -0
- plctap-0.1.0/tests/test_streams.py +89 -0
- plctap-0.1.0/uv.lock +1664 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
pull_request:
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
test:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
steps:
|
|
11
|
+
- uses: actions/checkout@v4
|
|
12
|
+
- uses: astral-sh/setup-uv@v5
|
|
13
|
+
with:
|
|
14
|
+
python-version: "3.11"
|
|
15
|
+
- run: uv sync --extra eval
|
|
16
|
+
- run: uv run pytest -q
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
# 打 v* 标签即触发: git tag v0.1.0 && git push origin v0.1.0
|
|
4
|
+
# 用 PyPI Trusted Publishing (OIDC), GitHub Actions 内不需要任何 token/密码;
|
|
5
|
+
# 前置一次性设置: PyPI 项目设置里启用 trusted publishing (见 docs/PUBLISH.md)
|
|
6
|
+
|
|
7
|
+
on:
|
|
8
|
+
push:
|
|
9
|
+
tags: ["v*"]
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
id-token: write # OIDC 签发发布凭证
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
publish:
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
- uses: astral-sh/setup-uv@v5
|
|
20
|
+
with:
|
|
21
|
+
python-version: "3.11"
|
|
22
|
+
- run: uv build
|
|
23
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
plctap-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
dist/
|
|
6
|
+
build/
|
|
7
|
+
.venv/
|
|
8
|
+
.pytest_cache/
|
|
9
|
+
.coverage
|
|
10
|
+
htmlcov/
|
|
11
|
+
|
|
12
|
+
# 运行时产物
|
|
13
|
+
*.jsonl
|
|
14
|
+
|
|
15
|
+
# IDE / OS
|
|
16
|
+
.idea/
|
|
17
|
+
.vscode/
|
|
18
|
+
.DS_Store
|
|
19
|
+
Thumbs.db
|
|
20
|
+
|
|
21
|
+
# 本地调试杂物
|
|
22
|
+
.tmp/
|
|
23
|
+
hangdiag.log
|
|
24
|
+
issue-evidence/
|
|
25
|
+
|
|
26
|
+
# eval 生成物 (可再生)
|
|
27
|
+
eval/prompts.jsonl
|
|
28
|
+
eval/results_baseline.json
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# plctap — Agent-PLC MCP Server 架构设计(ARCHITECTURE.md)
|
|
2
|
+
|
|
3
|
+
> 配合 PLAN.md v2 与 BUILD.md 使用|2026-09-03
|
|
4
|
+
|
|
5
|
+
## 1. 分层架构总览
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
MCP 客户端 (Claude Desktop / Codex / Cursor)
|
|
9
|
+
│ MCP stdio
|
|
10
|
+
MCP Server (FastMCP)
|
|
11
|
+
├─ 工具注册表(按配置条件注册,写工具默认缺席)
|
|
12
|
+
├─ 安全闸门 + 审计日志(JSONL)
|
|
13
|
+
├─ 连接管理器(连接池/空闲回收/目标级锁)
|
|
14
|
+
├─ 协议适配器(插件式: Modbus / FINS / MELSEC)
|
|
15
|
+
├─ Codec 纯函数层 (parse / validate / build)
|
|
16
|
+
└─ 诊断引擎(规则引擎 + YAML 知识库, 纯确定性输出)
|
|
17
|
+
│ TCP
|
|
18
|
+
真实 PLC / ProtoForge 模拟器
|
|
19
|
+
评测 harness(离线独立进程, 直连 codec, 不走 MCP)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## 2. 六个关键架构决策(ADR 摘要,2026-09-03 已全部确认)
|
|
23
|
+
|
|
24
|
+
### D1 会话模型:无状态参数 + 内部透明连接池
|
|
25
|
+
- 工具每次携带 host/port,不暴露 connect()/session_id
|
|
26
|
+
- 连接池 key = (protocol, host, port, unit);空闲 30s 回收;池上限可配
|
|
27
|
+
- FINS/TCP 节点号握手等细节封装在适配器内部
|
|
28
|
+
- 每目标地址互斥锁,防并发写竞争
|
|
29
|
+
- 理由:LLM 管理 session id 极易出错(遗忘/编造/过期)
|
|
30
|
+
|
|
31
|
+
### D2 Codec 纯函数化(可测试性根基)
|
|
32
|
+
- parse(hex)->ParseResult、validate(frame)->CheckList、build(params)->hex 均为纯函数
|
|
33
|
+
- 适配器只做网络 I/O;评测/单测/故障注入只测纯函数,CI 毫秒级
|
|
34
|
+
- ParseResult 每字段带 byte offset 与原始 hex 片段,供 diagnose 引用证据
|
|
35
|
+
|
|
36
|
+
### D3 异步 I/O:纯 asyncio
|
|
37
|
+
- 适配器用 asyncio.open_connection + asyncio.wait_for 超时
|
|
38
|
+
- 不依赖同步协议库(pymodbus 等仅作行为对齐参考)
|
|
39
|
+
- 与 MCP 工具超时语义天然对齐;支持并发探测多设备
|
|
40
|
+
|
|
41
|
+
### D4 诊断引擎纯确定性
|
|
42
|
+
- 规则引擎 + kb.yaml,输出结构化候选: {symptom, evidence[], confidence, suggested_action}
|
|
43
|
+
- 不生成自然语言;叙述由 Agent + Skill 层完成
|
|
44
|
+
- 输出可被 Agent 用于继续调用 probe_device 验证 → 诊断→探测→确认工具链
|
|
45
|
+
|
|
46
|
+
### D5 安全闸门:注册层控制 + 全量审计(方案C,已确认)
|
|
47
|
+
- allow_write=false 时写类工具根本不注册(Agent 不可见)
|
|
48
|
+
- 开启后每次写/发送写 JSONL 审计: {ts, tool, target, frame_hex, caller}
|
|
49
|
+
- 配套 MCP 审批弹窗(config.toml: default_tools_approval_mode / per-tool approval_mode)
|
|
50
|
+
|
|
51
|
+
## 3. 目录结构
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
plctap/
|
|
55
|
+
├── pyproject.toml # 入口: plctap (console script)
|
|
56
|
+
├── src/plctap/
|
|
57
|
+
│ ├── server.py # FastMCP app + 条件注册
|
|
58
|
+
│ ├── config.py # allow_write / 池大小 / 超时
|
|
59
|
+
│ ├── safety.py # 闸门 + 审计日志
|
|
60
|
+
│ ├── conn/
|
|
61
|
+
│ │ └── manager.py # 连接池 + 目标级锁
|
|
62
|
+
│ ├── protocols/
|
|
63
|
+
│ │ ├── base.py # ProtocolAdapter ABC + 注册表
|
|
64
|
+
│ │ ├── modbus/{adapter.py, codec.py}
|
|
65
|
+
│ │ ├── fins/{adapter.py, codec.py}
|
|
66
|
+
│ │ └── melsec/{adapter.py, codec.py}
|
|
67
|
+
│ ├── diag/
|
|
68
|
+
│ │ ├── rules.py # 确定性规则引擎
|
|
69
|
+
│ │ ├── kb.yaml # 50+ 故障知识库
|
|
70
|
+
│ │ └── report.py # 结构化诊断报告模型
|
|
71
|
+
│ └── models.py # ParseResult / ReadResult / DiagnosticReport
|
|
72
|
+
├── skill/SKILL.md # 方法论指令层
|
|
73
|
+
├── eval/
|
|
74
|
+
│ ├── corpus/ # 分层评测集 (yaml)
|
|
75
|
+
│ └── benchmark.py # vs 裸模型对比
|
|
76
|
+
├── tests/ # 纯函数单测 + MCP 冒烟测试
|
|
77
|
+
└── README.md
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## 4. 核心数据模型(Pydantic)
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
class ParseResult(BaseModel):
|
|
84
|
+
protocol: str
|
|
85
|
+
direction: "req" | "resp"
|
|
86
|
+
fields: list[Field] # name, value, raw_hex, byte_offset, note
|
|
87
|
+
valid: bool
|
|
88
|
+
|
|
89
|
+
class ReadResult(BaseModel):
|
|
90
|
+
target: Target # protocol/host/port/unit
|
|
91
|
+
request_frame: str # hex, 便于调试
|
|
92
|
+
raw_registers: list[int]
|
|
93
|
+
interpreted: Any # 按 datatype/byteorder 解释
|
|
94
|
+
elapsed_ms: int
|
|
95
|
+
|
|
96
|
+
class ProbeResult(BaseModel):
|
|
97
|
+
reachable: bool
|
|
98
|
+
failure_class: "connection_refused" | "timeout" | \
|
|
99
|
+
"connected_but_no_reply" | "exception_response" | None
|
|
100
|
+
exception_code: int | None
|
|
101
|
+
layer_hint: "connectivity" | "protocol" | "application"
|
|
102
|
+
|
|
103
|
+
class DiagnosticReport(BaseModel):
|
|
104
|
+
candidates: list[Candidate] # symptom/evidence/confidence/suggested_action
|
|
105
|
+
next_tools: list[str] # 建议Agent接下来调用的工具
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## 5. 数据流走查
|
|
109
|
+
|
|
110
|
+
### 场景 A: "读 40001 开始 10 个寄存器"
|
|
111
|
+
plc_read(modbus, host, 502, addr=0, count=10, datatype=float32)
|
|
112
|
+
→ 连接池取/建 TCP → codec.build 请求帧 → 发送 → codec.parse 响应
|
|
113
|
+
→ 字节序解释 → ReadResult。一次工具调用完成。
|
|
114
|
+
|
|
115
|
+
### 场景 B: "192.168.1.10 读不到数据"
|
|
116
|
+
probe_device → timeout → (Skill 指引) 查网络层
|
|
117
|
+
端口通 → plc_read → 异常码 0x02 → diagnose 匹配 KB
|
|
118
|
+
→ "地址越界, 建议先确认寄存器区范围" → Agent 转述。
|
|
119
|
+
三层分类(连接/协议/应用)对应三条工具路径。
|
|
120
|
+
|
|
121
|
+
## 6. 协议适配器接口
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
class ProtocolAdapter(ABC):
|
|
125
|
+
async def probe(self, target) -> ProbeResult
|
|
126
|
+
async def read(self, target, address, count, datatype) -> ReadResult
|
|
127
|
+
async def write(self, target, address, values) -> WriteResult # 闸门后注册
|
|
128
|
+
async def send_raw(self, target, frame_hex) -> RawExchange
|
|
129
|
+
# codec 纯函数: adapter.codec.parse / validate / build
|
|
130
|
+
```
|
|
131
|
+
新协议 = 新目录 + 注册表登记,server.py 不改(插件式扩展点)。
|
|
132
|
+
|
|
133
|
+
## 7. 配置与部署
|
|
134
|
+
|
|
135
|
+
```toml
|
|
136
|
+
# ~/.codex/config.toml 或 claude_desktop_config.json
|
|
137
|
+
[plctap]
|
|
138
|
+
allow_write = false # 默认
|
|
139
|
+
pool_max_per_target = 2
|
|
140
|
+
idle_timeout_sec = 30
|
|
141
|
+
default_timeout_ms = 2000
|
|
142
|
+
audit_log = "~/.plctap/audit.jsonl"
|
|
143
|
+
```
|
|
144
|
+
分发: pipx/uvx 安装;Claude Desktop / Codex 各给一段配置样例;后续可加 streamable_http 远程模式。
|
|
145
|
+
|
|
146
|
+
### D6 评测设计:分层难度 + 同模型双跑(已确认)
|
|
147
|
+
- 五档难度: 单帧ModbusTCP / RTU CRC / FINS / 批量日志 / 主动探测
|
|
148
|
+
- 同一批帧: 裸模型问答 vs 挂MCP工具, 同一模型消融, 分层报准确率
|
|
149
|
+
- 不做竞品准确率对比; README 放功能覆盖对比表即可
|
|
150
|
+
|
|
151
|
+
### D7 I/O 模型: 纯 asyncio(已确认, 随技术栈D3=Python+FastMCP)
|
|
152
|
+
|
|
153
|
+
## 8. 演进路线
|
|
154
|
+
- v0.1 (M1): Modbus TCP probe+read+parse/validate, ProtoForge 实测
|
|
155
|
+
- v0.2 (M2): +FINS/MELSEC + 诊断引擎 + 分层评测对比表
|
|
156
|
+
- v0.3 (M3): +写闸门 + parse_pcap + Skill → 发布 v1.0
|
|
157
|
+
- v2.0: S7(snap7) / 数据类型自动推断 / HTTP 远程部署
|
|
158
|
+
## 9. 工业协议层角色定位(2026-09-03 补充)
|
|
159
|
+
|
|
160
|
+
两个"server"概念区分:
|
|
161
|
+
- MCP 协议层: 永远是 Server(向 Claude/Codex 暴露工具)
|
|
162
|
+
- 工业协议层: v1 只做 Client/主站(主动连 PLC)
|
|
163
|
+
|
|
164
|
+
"被连接"能力的演进(不做通用模拟器, 那是 ProtoForge 的地盘, 用集成代替重写):
|
|
165
|
+
- v1 : 仅 Client/主站
|
|
166
|
+
- v1.1 : 服务端诊断套件(共享监听基础设施, codec.parse/build 反向复用):
|
|
167
|
+
start_proxy 诊断代理: 上位机→代理→真实PLC 透明转发+录制
|
|
168
|
+
→ 排查"上位机说没回 / PLC 说没发"扯皮场景
|
|
169
|
+
start_listener 钓鱼模式(实习真实痛点背书):
|
|
170
|
+
待测设备只能当client时, 立可编程假server钓出其帧行为
|
|
171
|
+
mode: record_only / respond_normal / respond_scripted / inject_errors
|
|
172
|
+
→ 捕获畸形握手/错误字节序/重发风暴; 可按请求写入数控(CNC)
|
|
173
|
+
→ 知识库新增 client 侧故障模式类; 面试零硬件双向演示
|
|
174
|
+
- 永不做: 独立完整从站模拟器;ProtoForge 作为开发/测试依赖引入
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
|
plctap-0.1.0/BUILD.md
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
> **注意(2026-09-03)**:项目 plctap 已重新定位为「Agent-PLC 连接器」(见 PLAN.md v2)。工具优先级调整为:M1 先做 `probe_device` + `plc_read`(Modbus TCP 读写闭环),诊断层工具顺延到 M2;BUILD.md 第 2 节的解析/诊断工具设计仍有效,连接层工具设计以 PLAN.md 第 3 节为准。
|
|
2
|
+
# 工业协议诊断 MCP 实施指南(BUILD.md)
|
|
3
|
+
|
|
4
|
+
> 配合 PLAN.md 使用|形态已定:MCP Server 为核心 + 薄 Skill 指令层|测试台架:ProtoForge
|
|
5
|
+
|
|
6
|
+
## 0. 形态决策(为什么)
|
|
7
|
+
|
|
8
|
+
- `parse_frame` / `validate_frame` 是确定性字节级操作 → 必须是代码工具 → **MCP Server**
|
|
9
|
+
- 诊断方法论(抓包→比对→排除)是流程知识 → **Skill(纯指令,SKILL.md)**
|
|
10
|
+
- 评测卖点 = 工具化解析 vs 模型口算的准确率差 → 没有工具层就没有这个对比
|
|
11
|
+
|
|
12
|
+
## 1. 仓库结构(建议)
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
proto-diag-mcp/
|
|
16
|
+
├── pyproject.toml # 依赖: fastmcp, pymodbus(参考), pytest
|
|
17
|
+
├── src/proto_diag/
|
|
18
|
+
│ ├── server.py # FastMCP 入口, 注册 tools/resources/prompts
|
|
19
|
+
│ ├── protocols/
|
|
20
|
+
│ │ ├── modbus/ # parser.py, rules.py, spec.md
|
|
21
|
+
│ │ ├── fins/ # parser.py, rules.py, spec.md
|
|
22
|
+
│ │ └── melsec/ # parser.py, rules.py, spec.md
|
|
23
|
+
│ ├── diagnose/
|
|
24
|
+
│ │ ├── rules.py # 规则引擎(确定性检查)
|
|
25
|
+
│ │ └── kb.yaml # 50+ 故障知识库(症状→根因→修复)
|
|
26
|
+
│ └── models.py # ParseResult / DiagnosticReport Pydantic 模型
|
|
27
|
+
├── skill/ # 薄 Skill 层
|
|
28
|
+
│ └── SKILL.md # 诊断方法论, 指令 only
|
|
29
|
+
├── eval/
|
|
30
|
+
│ ├── corpus/ # 评测集: hex + 期望标签 (yaml)
|
|
31
|
+
│ └── benchmark.py # 工具诊断 vs 裸模型问答 对比
|
|
32
|
+
├── tests/
|
|
33
|
+
└── README.md
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## 2. MCP 工具设计
|
|
37
|
+
|
|
38
|
+
### Tools(都返回结构化 JSON)
|
|
39
|
+
- `parse_frame(protocol: "modbus"|"fins"|"melsec", hex: str)` → 逐字段拆解(事务号/单元号/功能码/地址/数量/数据/CRC 等),每个字段带 byte offset 和原始 hex 片段
|
|
40
|
+
- `validate_frame(protocol, hex)` → 校验清单逐项 pass/fail:CRC/LRC、MBAP 长度一致性、功能码合法、寄存器地址边界、FINS 目标节点、MC 帧尾代码
|
|
41
|
+
- `diagnose(protocol, hex | log_snippet, context?)` → parse+validate 结果 → 规则引擎匹配知识库 → 输出结构化报告:`{severity, symptom, root_cause, evidence, fix_suggestion, next_steps}`
|
|
42
|
+
- `list_protocols()` → 能力自述(Agent 自发现用)
|
|
43
|
+
|
|
44
|
+
### Resources
|
|
45
|
+
- `spec://modbus`、`spec://fins`、`spec://melsec` 脱敏协议速查(提炼,不整抄手册)
|
|
46
|
+
|
|
47
|
+
### Prompts
|
|
48
|
+
- `diagnostic_playbook`:抓包→比对→排除法完整流程
|
|
49
|
+
|
|
50
|
+
## 3. 解析器实现要点(M1)
|
|
51
|
+
|
|
52
|
+
- **纯手写解析,不依赖协议库做解析**(库只做行为对齐参考)。原因:库是客户端视角,你要的是无状态帧解析,且求职需要展示"从协议规范手写解析器"
|
|
53
|
+
- Modbus TCP:MBAP 头 7B + PDU;注意异常响应帧(功能码 |0x80 + 异常码)单独处理
|
|
54
|
+
- FINS TCP:FINS/TCP 握手帧(0x46494E53) 与 FINS 帧(TC/RS 头)区分;命令码 0101/0102/0103…
|
|
55
|
+
- MELSEC 3E:副头部 + 网络号/PC 号 + 监视定时器 + 结束代码;注意二进制 vs ASCII 模式先只做二进制
|
|
56
|
+
- 字节序:Modbus 大端,FINS 大端,MC 二进制小端——这些差异本身就是诊断知识库素材
|
|
57
|
+
- 每个协议:先写固定样例的单元测试(pytest + 参数化),再接 FastMCP
|
|
58
|
+
|
|
59
|
+
## 4. 用 ProtoForge 生产评测集(M2 核心技巧)
|
|
60
|
+
|
|
61
|
+
1. `git clone` ProtoForge,跑起 modbus/fins/mc 模拟器(MIT 协议,实现可学习参考)
|
|
62
|
+
2. 正常流量:客户端轮询读写 → 录制基线报文
|
|
63
|
+
3. **故障注入清单**(评测集来源,每类 ≥5 条):
|
|
64
|
+
- CRC 篡改 1 bit
|
|
65
|
+
- MBAP/副头部长度字段与实际不符
|
|
66
|
+
- 事务号乱序 / 复用
|
|
67
|
+
- 寄存器地址越界(读 4x 段用 0x 前缀地址)
|
|
68
|
+
- 字节序调换(大端↔小端)
|
|
69
|
+
- 异常响应帧(Illegal Address / Illegal Function / Gateway Target Failed)
|
|
70
|
+
- 连接竞态:半包、粘包、TCP RST 中断
|
|
71
|
+
- FINS:节点号重复、TC 帧超时
|
|
72
|
+
- MC:结束代码非 0(映射到具体含义)
|
|
73
|
+
3.5 **client 侧故障类(钓鱼模式产出,源自实习场景)**:设备连假 server 后钓出的——畸形握手帧、错误字节序请求、超时重发风暴、非法功能码请求、半包粘包的 client 拆帧错误
|
|
74
|
+
4. 每条样本标注:`{hex, protocol, expected_symptom, expected_root_cause}` → eval/corpus/*.yaml
|
|
75
|
+
5. benchmark.py 双跑:本 MCP 工具诊断 vs 同一批帧直接喂裸模型问答 → 出对比表(README 核心卖点)
|
|
76
|
+
|
|
77
|
+
## 5. Skill 写法(M3,薄)
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
skill/SKILL.md
|
|
81
|
+
---
|
|
82
|
+
name: proto-diag
|
|
83
|
+
description: 工业协议(Modbus/FINS/MELSEC)报文诊断。当用户给出 hex 报文、
|
|
84
|
+
抓包片段、PLC 通信异常日志,或要求排查上位机/网关通信故障时触发。
|
|
85
|
+
---
|
|
86
|
+
诊断流程(严格按序):
|
|
87
|
+
1. 判断协议,无法判断时问用户或用 parse_frame 各试一次看哪个结构成立
|
|
88
|
+
2. parse_frame → 读结构化字段
|
|
89
|
+
3. validate_frame → 逐项看 fail 项
|
|
90
|
+
4. 结合上下文(设备型号/网络拓扑)读 diagnose 报告
|
|
91
|
+
5. 给出修复建议后,提醒用户复测并回报新报文,进入闭环
|
|
92
|
+
|
|
93
|
+
钓鱼模式(设备只能当 client 时):
|
|
94
|
+
- start_listener(protocol, port, mode) 起假 server → 让用户把设备指向该端口
|
|
95
|
+
- 优先 record_only: 收满样本后 parse_frame+diagnose 逐帧分析
|
|
96
|
+
- 需要观察设备容错时才用 respond_normal/respond_scripted, 一次只改一个变量
|
|
97
|
+
- 结束必须提醒用户恢复设备原配置
|
|
98
|
+
注意事项: 字节序陷阱、半包重组成完整帧再解析、不要跳过 validate 直接下结论
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
- 只在 SKILL.md 里写方法论与工具调用顺序,不含脚本
|
|
102
|
+
- description 是触发关键:写清"何时触发/何时不触发"
|
|
103
|
+
|
|
104
|
+
## 6. 接入与发布清单(M3)
|
|
105
|
+
|
|
106
|
+
- [ ] Claude Desktop: `claude_desktop_config.json` 注册 stdio server,截图
|
|
107
|
+
- [ ] Codex: `config.toml` `[mcp_servers.proto-diag]` 注册,截图
|
|
108
|
+
- [ ] README: 架构图 + 评测对比表 + GIF 演示(终端录 asciinema)
|
|
109
|
+
- [ ] 提交 glama.ai / mcp.so / Pulse 收录
|
|
110
|
+
- [ ] 可选薄 Web demo: FastAPI 单页,粘贴 hex → 展示解析+诊断
|
|
111
|
+
- [ ] 加 MIT License、CI(pytest)
|
|
112
|
+
|
|
113
|
+
## 7. 安全红线(坚持 PLAN.md 的要求)
|
|
114
|
+
|
|
115
|
+
- 报文样例全部脱敏/改写;知识库用例来自实习排查经历但改写为通用场景
|
|
116
|
+
- 不含公司设备 IP、型号组合、业务寄存器语义
|
|
117
|
+
- 评测集用 ProtoForge 注入生成 + 手工构造,不依赖真实抓包原文
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
|
plctap-0.1.0/HANDOFF.md
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# plctap 开发交接文档(HANDOFF.md)
|
|
2
|
+
|
|
3
|
+
> 交接对象:负责编码实现的 agent|交接日期:2026-09-03
|
|
4
|
+
> 本文档是唯一入口。按顺序读完第 1 节列出的文档后再动手写代码。
|
|
5
|
+
|
|
6
|
+
## 1. 必读文档(按序)
|
|
7
|
+
|
|
8
|
+
| 顺序 | 文档 | 内容 |
|
|
9
|
+
|---|---|---|
|
|
10
|
+
| 1 | PLAN.md | 定位、用户、能力地图、里程碑(已定案,不要重新讨论定位) |
|
|
11
|
+
| 2 | ARCHITECTURE.md | 七个已确认 ADR、数据模型、适配器接口、服务端套件 |
|
|
12
|
+
| 3 | BUILD.md | 仓库结构、三协议解析要点、ProtoForge 故障注入清单、SKILL.md 模板 |
|
|
13
|
+
|
|
14
|
+
## 2. 已锁定的决策(直接执行,勿重议)
|
|
15
|
+
|
|
16
|
+
1. **名称**: `plctap`(GitHub 仓库 / PyPI 包 / Python 包名统一)
|
|
17
|
+
2. **技术栈**: Python 3.11+ / FastMCP / 纯 asyncio(不用同步协议库做 I/O;pymodbus、pymcprotocol 仅作帧格式对齐参考)
|
|
18
|
+
3. **会话模型**: 工具无状态(每次带 host/port),server 内部连接池 key=(protocol,host,port,unit),空闲30s回收,目标级互斥锁
|
|
19
|
+
4. **Codec 纯函数化**: parse/validate/build 不碰 socket;全部单测覆盖
|
|
20
|
+
5. **诊断引擎**: 纯确定性(规则引擎+kb.yaml),只输出结构化候选,不生成自然语言,不调 LLM
|
|
21
|
+
6. **安全**: `allow_write=false` 时写类工具不注册;所有写/发送写 JSONL 审计日志(~/.plctap/audit.jsonl)
|
|
22
|
+
7. **协议角色**: v1 只做 Client/主站;钓鱼模式 listener 基础两档(record_only/respond_normal)属 M3
|
|
23
|
+
8. **评测**: 五档难度×同模型双跑;corpus 用 YAML;不做竞品准确率对比
|
|
24
|
+
9. **三协议**: Modbus TCP / Omron FINS(TCP) / Mitsubishi MC(3E 二进制)
|
|
25
|
+
|
|
26
|
+
## 3. 实施顺序(M1 任务分解,含验收标准)
|
|
27
|
+
|
|
28
|
+
### T1 项目骨架(半天)
|
|
29
|
+
- `uv init`,pyproject:`[project.scripts] plctap = "plctap.server:main"`
|
|
30
|
+
- 依赖: fastmcp, pydantic, pyyaml, pytest, pytest-asyncio, scapy(评测用, 可选)
|
|
31
|
+
- 建 ARCHITECTURE.md 第 3 节的目录树(包名 plctap)
|
|
32
|
+
- CI: GitHub Actions 跑 pytest
|
|
33
|
+
- **验收**: `uv run plctap` 能启动,MCP 工具列表里出现空实现工具
|
|
34
|
+
|
|
35
|
+
### T2 Modbus codec 纯函数(1 天)
|
|
36
|
+
- `protocols/modbus/codec.py`: build_read_request / parse_response / parse_request / validate_frame
|
|
37
|
+
- 覆盖: FC01-04 请求/响应、FC05-06、异常响应(FC|0x80+异常码)、MBAP 长度校验
|
|
38
|
+
- ParseResult 每字段带 byte_offset 和 raw_hex
|
|
39
|
+
- **验收**: 参数化单测 ≥30 条全绿,含异常帧和畸形帧
|
|
40
|
+
|
|
41
|
+
### T3 连接管理器 + probe(1 天)
|
|
42
|
+
- `conn/manager.py`: 异步连接池 + wait_for 超时 + 目标级锁
|
|
43
|
+
- `probe_device`: 分类 connection_refused / timeout / connected_but_no_reply / exception_response,输出 layer_hint
|
|
44
|
+
- **验收**: 对本地 ProtoForge 模拟器 probe 成功;对不存在端口返回 refused;对静默端口返回 timeout
|
|
45
|
+
|
|
46
|
+
### T4 plc_read(1 天)
|
|
47
|
+
- 读 FC03/FC04,支持 datatype: uint16/int16/float32(大小端选项)
|
|
48
|
+
- 返回 ReadResult(含 request_frame hex、elapsed_ms)
|
|
49
|
+
- **验收**: 对话式完成"探测→读取→解释数值",Claude Desktop + Codex 双端接入截图
|
|
50
|
+
|
|
51
|
+
### T5 parse_frame / validate_frame 工具化(半天)
|
|
52
|
+
- **验收**: 粘贴 hex 得到结构化字段与校验清单
|
|
53
|
+
|
|
54
|
+
### M1 DoD(全绿才算完成)
|
|
55
|
+
- [ ] ProtoForge 起本地 Modbus 从站,全流程演示 GIF 录制
|
|
56
|
+
- [ ] pytest 通过率 100%,CI 绿
|
|
57
|
+
- [ ] Claude Desktop / Codex 接入文档 + 截图
|
|
58
|
+
|
|
59
|
+
### M2/M3 按 PLAN.md 里程碑执行,注意:
|
|
60
|
+
- M2 评测 corpus 先做 Modbus 层(单帧/CRC/批量日志三档),FINS/MELSEC 层后补
|
|
61
|
+
- M2 FINS 注意 TCP 握手(0x46494E53 节点分配)藏在 adapter 内;MC 只做 3E 二进制帧
|
|
62
|
+
- M3 listener 用 asyncio.start_server,respond_normal 直接用 codec.build 回包
|
|
63
|
+
|
|
64
|
+
## 4. 实现要点与坑(务必看 BUILD.md 第 3、4 节)
|
|
65
|
+
|
|
66
|
+
- Modbus TCP 无 CRC(RTU 才有);字节序三协议各不同(Modbus 大端/FINS 大端/MC 小端)——这些差异是知识库素材
|
|
67
|
+
- 解析器对齐参考: pymodbus(Modbus)、pymcprotocol(MC 3E/4E)、sukmatechid/fins-driver(FINS)
|
|
68
|
+
- ProtoForge 用法: clone 后按 protocols/{modbus,fins,mc}/server.py 起模拟器;故障注入=篡改响应字节再发(见 BUILD.md 第 4 节清单)
|
|
69
|
+
- asyncio: 所有 socket 操作 asyncio.wait_for 包裹;连接池回收用后台 task
|
|
70
|
+
- Windows 本地开发: 日志/路径用 pathlib;审计日志目录不存在要自动创建
|
|
71
|
+
|
|
72
|
+
## 5. 红线(违反即返工)
|
|
73
|
+
|
|
74
|
+
1. 报文样例/知识库全部脱敏改写——不含公司设备 IP、型号组合、业务寄存器语义
|
|
75
|
+
2. 写工具默认不注册;审计日志不可关
|
|
76
|
+
3. 不引入 LLM 调用进 server;不做自然语言解析
|
|
77
|
+
4. 不重写通用模拟器(ProtoForge 是依赖不是竞品,MIT 协议可放心参考其 FINS/MC 实现)
|
|
78
|
+
5. 不做超范围的协议(S7/OPC-UA 是 v2 的事)
|
|
79
|
+
|
|
80
|
+
## 5.5 M1 联测记录(2026-09-04 完成)
|
|
81
|
+
|
|
82
|
+
- ProtoForge Modbus TCP 从站 (127.0.0.1:15020) 实测: probe / fc06 写×3 / fc03 读回精确匹配 / float32 解释 3.1415927 / fc05 线圈 0xFF00 / 越界 ILLEGAL_DATA_ADDRESS(0x02) —— 全部通过
|
|
83
|
+
- 本 Windows 关端口表现为 timeout(SYN 被丢弃),refused 分支由 monkeypatch 确定性覆盖
|
|
84
|
+
- 联测脚本: scripts/integration_protoforge.py
|
|
85
|
+
- 发现上游 bug: ProtoForge 设备写回路径在 pymodbus 3.15 下失效 (ModbusServerContext.async_setValues 对 legacy context 恒返回 DEVICE_BUSY) —— M2 做故障注入时需绕过该路径或给上游提 issue
|
|
86
|
+
- **ProtoForge 测试台架必须 pin pymodbus==3.12.1** (`pip install protoforge "pymodbus<3.13"`): pymodbus 3.13+ 下配置的点位值会全部读回 0 (写回路径静默失效), 不 pin 会在"点位值全变 0"上浪费半天排查
|
|
87
|
+
- P2 已修复: plc_write 实装 (fc05/06) + 审计记录真实帧 (on_frame 发送前回调) + list_protocols 与实际注册对齐
|
|
88
|
+
|
|
89
|
+
## 5.6 演示资产(2026-09-04 完成)
|
|
90
|
+
|
|
91
|
+
- docs/demo.gif: 聊天式演示动画, 由 scripts/make_demo_gif.py 用真实工具调用数据渲染 (内嵌状态化从站, 可随时重跑)
|
|
92
|
+
- README 已含 Claude Desktop / Codex 接入样例 (含本地开发路径变体与 env 段)
|
|
93
|
+
- 修复连接池清扫任务并发 bug (sweep 迭代快照化, 含回归测试) —— 演示脚本压测发现
|
|
94
|
+
- 待用户环境: 双端接入截图 (配置样例已备好), 推送 GitHub
|
|
95
|
+
|
|
96
|
+
## 5.7 M2 完成记录(2026-09-04)
|
|
97
|
+
|
|
98
|
+
- **codec + 适配器**: FINS/TCP 两层协议 (TCP 头 16B + FINS 10B 头, 全大端) 与 MELSEC 3E 二进制 (副头部 0x5000, 全小端, 位软元件字数=ceil(count/16)) 的 build/parse/validate/interpret 全套; 两协议适配器接入 probe/read/send_raw, server 工具三协议分发 (plc_read 增 area/device 参数)
|
|
99
|
+
- **方向判别统一**: `protocols/auto.py` 的 parse_auto 供 server 与诊断引擎共用 (Modbus 按 fc|0x80 与 12B 规则、FINS 按 TCP 命令与 ICF bit6、MELSEC 按命令字回显)
|
|
100
|
+
- **诊断引擎 (D4)**: `diagnose` 工具 + 确定性规则引擎 + kb.yaml (60 条 entries + 3 条字节序 references); req→resp 帧配对启用交叉校验 (tid/sid/节点回显, 抓串包/网关错路由); Modbus TCP/RTU 双轨三档判别 (TCP 合法→单轨 TCP / RTU 完整可解释→单轨 RTU / 双败→合并但按"形似 RTU"门控抑制噪声候选)
|
|
101
|
+
- **评测**: eval/benchmark.py (tool 模式打分 + `--export-prompts` 导出裸模型 JSONL) + corpus/modbus_m2.yaml 18 条三档语料 (single_frame_tcp 8 / integrity_crc 5 / batch_log 5), 全部合成帧, **18/18 通过**; tests/test_eval.py 自检防基线回退
|
|
102
|
+
- **测试**: 248 全绿 (新增 codec FINS/MELSEC 48 + 适配器 14 + 诊断 81 + eval 自检 2)
|
|
103
|
+
- 关键坑 (已修, 详见代码注释): ① asyncio fake server 对 EOF (IncompleteReadError) continue 会形成无挂起自旋、饿死整个事件循环 —— EOF 必须 return; ② FINS 请求的 DA1 必须定向到握手确认的 server_node, 否则多节点网络交叉校验误报; ③ 合法 RTU 帧按 TCP 硬解会产出 MBAP 类伪候选 (双轨判别的动机)
|
|
104
|
+
- 红线遵守: 全部帧样本/KB 文案脱敏合成; server 内无 LLM 调用; 未写通用模拟器 (ProtoForge 为依赖)
|
|
105
|
+
|
|
106
|
+
## 5.8 P3 语义修正 + 基线方法学(2026-09-04)
|
|
107
|
+
|
|
108
|
+
- reachable 语义定死: 传输层可达 (TCP 已建立)。exception_response 时 reachable=True
|
|
109
|
+
(设备在线且回规范异常帧) —— models/server docstring、modbus/fins/melsec 适配器、测试同步
|
|
110
|
+
- eval/baseline.py: 裸模型基线 runner (stdlib 调 Responses API, 离线 --answers 模式,
|
|
111
|
+
确定性判分与工具模式同源) + eval/README.md 方法学 (同模型双跑/分层/对比表纪律)
|
|
112
|
+
- 自检: 判分器对关键词命中/空数组/错答三种情况验证正确
|
|
113
|
+
- 真跑基线需 OPENAI_API_KEY (用户环境); README 对比表 = 工具模式 18/18 vs 基线 X/18
|
|
114
|
+
|
|
115
|
+
## 5.9 M3 完成记录(2026-09-04)
|
|
116
|
+
|
|
117
|
+
- **send_frame** (闸门): 发送原始帧 + 等待一帧响应, 与 plc_write 同受 allow_write 控制, 发送前完整帧落审计; list_protocols 的 send_raw 与闸门对齐
|
|
118
|
+
- **parse_pcap**: scapy (可选依赖 eval extra) 读 pcap → 按五元组流聚合载荷 → 按协议切帧 → 逐帧 parse_auto; 尾部半帧标 partial 不静默丢弃; protocol 缺省按"完整帧数最多"自动判别; 未装 scapy 给出安装提示
|
|
119
|
+
- **钓鱼监听** (record_only / respond_normal 两档): streams.py 三协议流分帧纯函数 (listener 与 pcap 共用); respond_normal 只回读类请求 (modbus fc1-6 / fins 握手+0101 / melsec 0401), 数据恒 0, 未覆盖请求记帧不回复; EOF/IncompleteReadError/空闲超时一律 return (M2 事件循环饿死教训), 帧存内存环形缓冲 (上限 1000), port=0 支持系统分配
|
|
120
|
+
- **skill/SKILL.md** (proto-diag): BUILD.md 模板, 诊断流程 + 三种报文证据 + 钓鱼模式用法 + 字节序/半包注意事项
|
|
121
|
+
- **LICENSE**: MIT; README 补工具表与状态更新
|
|
122
|
+
- **测试**: 276 全绿 (新增 streams 12 + listener 13 + pcap 6 + smoke 扩展 2 + send_frame 端到端)
|
|
123
|
+
- 红线遵守: 帧样本全部合成 (scapy 生成 pcap); respond_normal 是诊断设施非通用模拟器 (HANDOFF 红线 4)
|
|
124
|
+
- 待用户环境: 五档评测对比表 (裸模型基线跑分需 OPENAI_API_KEY), 三端接入截图, GitHub/PyPI 发布, MCP 收录站提交
|
|
125
|
+
|
|
126
|
+
## 6. 交付物清单
|
|
127
|
+
|
|
128
|
+
- [x] eval/benchmark.py + corpus/ (M2 18/18; 裸模型基线跑分待用户 KEY)
|
|
129
|
+
- [x] skill/SKILL.md
|
|
130
|
+
- [x] LICENSE (MIT)
|
|
131
|
+
- [ ] GitHub 仓库 plctap(CI 已有, 待推送 + 双端接入截图)
|
|
132
|
+
- [ ] PyPI 可安装(uvx/pipx 一行跑起来)
|
|
133
|
+
- [ ] README: 五档评测对比表 + 三端接入截图(待用户环境)
|
|
134
|
+
- [ ] 提交 glama.ai / mcp.so / Pulse 收录
|
|
135
|
+
|
plctap-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 plctap contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
plctap-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: plctap
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Agent-PLC MCP Server: connect, read and diagnose Modbus / FINS / MELSEC PLCs
|
|
5
|
+
Project-URL: Homepage, https://github.com/ymxc152/plctap
|
|
6
|
+
Project-URL: Repository, https://github.com/ymxc152/plctap
|
|
7
|
+
Project-URL: Issues, https://github.com/ymxc152/plctap/issues
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: fins,industrial,mcp,melsec,modbus,plc,scada
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Human Machine Interfaces
|
|
19
|
+
Classifier: Topic :: System :: Networking :: Monitoring
|
|
20
|
+
Requires-Python: >=3.11
|
|
21
|
+
Requires-Dist: fastmcp>=2.0
|
|
22
|
+
Requires-Dist: pydantic>=2.0
|
|
23
|
+
Requires-Dist: pyyaml>=6.0
|
|
24
|
+
Provides-Extra: eval
|
|
25
|
+
Requires-Dist: scapy>=2.5; extra == 'eval'
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# plctap
|
|
29
|
+
|
|
30
|
+
> Agent 的 PLC 驱动层 — 让 Claude / Codex / Cursor 直接连接、读写、诊断
|
|
31
|
+
> Modbus TCP / FINS / MELSEC PLC 的 MCP Server。
|
|
32
|
+
|
|
33
|
+

|
|
34
|
+
|
|
35
|
+
**状态: 开发中 (M3: 三协议 + 诊断引擎 + 钓鱼监听)。** 本 README 将随里程碑补全:
|
|
36
|
+
五档评测对比表 (工具模式 vs 裸模型)、三端接入截图 (待用户环境)。
|
|
37
|
+
|
|
38
|
+
## 工具
|
|
39
|
+
|
|
40
|
+
| 层 | 工具 | 说明 |
|
|
41
|
+
|---|---|---|
|
|
42
|
+
| 连接 | `probe_device` | 连通性探测 + 四类失败分层归因 |
|
|
43
|
+
| 连接 | `plc_read` | 读数据区并按 datatype/字节序解释 (三协议) |
|
|
44
|
+
| 诊断 | `parse_frame` / `validate_frame` | 单帧结构化解析 / 规范校验清单 |
|
|
45
|
+
| 诊断 | `diagnose` | 规则引擎 + 故障知识库 → 结构化候选报告 |
|
|
46
|
+
| 诊断 | `parse_pcap` | 解析 Wireshark 导出 pcap, 逐流逐帧 (需 `uv sync --extra eval`) |
|
|
47
|
+
| 监听 | `start_listener` / `stop_listener` / `get_listener_frames` | 钓鱼模式: 设备只能当 client 时立假 server 收帧分析 |
|
|
48
|
+
| 执行 | `plc_write` / `send_frame` | **默认不注册**, `PLCTAP_ALLOW_WRITE=true` 才启用 (闸门) |
|
|
49
|
+
|
|
50
|
+
## 快速开始
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
uvx plctap # 或 pipx install plctap
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Claude Desktop 接入 (`claude_desktop_config.json`)
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
{
|
|
60
|
+
"mcpServers": {
|
|
61
|
+
"plctap": {
|
|
62
|
+
"command": "uvx",
|
|
63
|
+
"args": ["plctap"],
|
|
64
|
+
"env": { "PLCTAP_ALLOW_WRITE": "false" }
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
本地开发 (仓库检出路径):
|
|
71
|
+
|
|
72
|
+
```json
|
|
73
|
+
{
|
|
74
|
+
"mcpServers": {
|
|
75
|
+
"plctap": {
|
|
76
|
+
"command": "uv",
|
|
77
|
+
"args": ["--directory", "C:/path/to/plctap", "run", "plctap"]
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Codex 接入 (`~/.codex/config.toml`)
|
|
84
|
+
|
|
85
|
+
```toml
|
|
86
|
+
[mcp_servers.plctap]
|
|
87
|
+
command = "uvx"
|
|
88
|
+
args = ["plctap"]
|
|
89
|
+
|
|
90
|
+
[mcp_servers.plctap.env]
|
|
91
|
+
PLCTAP_ALLOW_WRITE = "false" # 写闸门默认关闭
|
|
92
|
+
PLCTAP_DEFAULT_TIMEOUT_MS = "2000"
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### 配置 (环境变量, 均有默认值)
|
|
96
|
+
|
|
97
|
+
| 变量 | 默认 | 说明 |
|
|
98
|
+
|---|---|---|
|
|
99
|
+
| `PLCTAP_ALLOW_WRITE` | `false` | **写类工具默认不注册** (安全闸门) |
|
|
100
|
+
| `PLCTAP_POOL_MAX_PER_TARGET` | `2` | 每目标连接池上限 |
|
|
101
|
+
| `PLCTAP_IDLE_TIMEOUT_SEC` | `30` | 空闲连接回收秒数 |
|
|
102
|
+
| `PLCTAP_DEFAULT_TIMEOUT_MS` | `2000` | 网络超时 |
|
|
103
|
+
|
|
104
|
+
## 安全
|
|
105
|
+
|
|
106
|
+
- 写操作默认**完全不注册**; 显式 `PLCTAP_ALLOW_WRITE=true` 才启用。
|
|
107
|
+
- 所有写/发送动作逐条写入 JSONL 审计日志 (`~/.plctap/audit.jsonl`, 不可关)。
|
|
108
|
+
- 发送类调用请配合客户端审批弹窗使用 (用户可见目标 IP 与完整帧)。
|
|
109
|
+
- 审计日志样例:
|
|
110
|
+
`{"ts":"2026-09-04T01:20:33+0800","tool":"plc_write","target":"modbus://127.0.0.1:15020 unit=1","frame_hex":"0002000000060106000104d2","caller":"mcp"}`
|
|
111
|
+
|
|
112
|
+
## 开发
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
uv sync
|
|
116
|
+
uv run pytest -q # codec 纯函数单测 (毫秒级) + MCP 冒烟测试
|
|
117
|
+
uv run plctap # 本地启动 stdio server
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## License
|
|
121
|
+
|
|
122
|
+
MIT
|