ht32-code-mcp 0.2.3__tar.gz → 0.2.5__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.
- {ht32_code_mcp-0.2.3 → ht32_code_mcp-0.2.5}/DEPLOY.md +1 -1
- {ht32_code_mcp-0.2.3 → ht32_code_mcp-0.2.5}/PKG-INFO +1 -1
- {ht32_code_mcp-0.2.3 → ht32_code_mcp-0.2.5}/e2e_scenario_a.py +164 -142
- {ht32_code_mcp-0.2.3 → ht32_code_mcp-0.2.5}/pyproject.toml +35 -35
- {ht32_code_mcp-0.2.3 → ht32_code_mcp-0.2.5}/src/ht32_code_mcp/exporter.py +362 -335
- {ht32_code_mcp-0.2.3 → ht32_code_mcp-0.2.5}/src/ht32_code_mcp/server.py +15 -10
- {ht32_code_mcp-0.2.3 → ht32_code_mcp-0.2.5}/tests/test_tools.py +971 -846
- {ht32_code_mcp-0.2.3 → ht32_code_mcp-0.2.5}/uv.lock +1 -1
- {ht32_code_mcp-0.2.3 → ht32_code_mcp-0.2.5}/.gitignore +0 -0
- {ht32_code_mcp-0.2.3 → ht32_code_mcp-0.2.5}/.gitkeep +0 -0
- {ht32_code_mcp-0.2.3 → ht32_code_mcp-0.2.5}/README.md +0 -0
- {ht32_code_mcp-0.2.3 → ht32_code_mcp-0.2.5}/e2e_scenario_b.py +0 -0
- {ht32_code_mcp-0.2.3 → ht32_code_mcp-0.2.5}/smoke_http.py +0 -0
- {ht32_code_mcp-0.2.3 → ht32_code_mcp-0.2.5}/smoke_stdio.py +0 -0
- {ht32_code_mcp-0.2.3 → ht32_code_mcp-0.2.5}/src/ht32_code_mcp/__init__.py +0 -0
- {ht32_code_mcp-0.2.3 → ht32_code_mcp-0.2.5}/src/ht32_code_mcp/__main__.py +0 -0
- {ht32_code_mcp-0.2.3 → ht32_code_mcp-0.2.5}/src/ht32_code_mcp/index.py +0 -0
- {ht32_code_mcp-0.2.3 → ht32_code_mcp-0.2.5}/src/ht32_code_mcp/knowledge.py +0 -0
- {ht32_code_mcp-0.2.3 → ht32_code_mcp-0.2.5}/src/ht32_code_mcp/usage.py +0 -0
- {ht32_code_mcp-0.2.3 → ht32_code_mcp-0.2.5}/tests/conftest.py +0 -0
|
@@ -363,7 +363,7 @@ uv lock && uv build && uv publish
|
|
|
363
363
|
|---|------|------|------|
|
|
364
364
|
| 3.1 | MCP 工具实现(7 检索/导出 + 1 反馈) | `.work/mcp-server/src/ht32_code_mcp/`(FastMCP) | ✅ |
|
|
365
365
|
| 3.2 | 工具级测试 | `pytest` 46 用例全绿 | ✅ |
|
|
366
|
-
| 3.3 | PyPI 发布(hatchling) | `ht32-code-mcp` 0.2.
|
|
366
|
+
| 3.3 | PyPI 发布(hatchling) | `ht32-code-mcp` 0.2.3(2026-09-24,含全系列固件库覆盖 + 6 模板多模板导出) | ✅ |
|
|
367
367
|
| 3.4 | 云端 git clone + 挂载 | `/opt/ht32-code:/app/code` | ✅ |
|
|
368
368
|
| 3.5 | 1Panel 部署(uvx + supergateway → streamableHttp stateful,镜像 next-uvx) | `https://mcp.s621.work/ht32-code` | ✅ |
|
|
369
369
|
| 3.6 | OpenResty 超时修复(`proxy_read_timeout 3600s`) | 修复 60s 空闲断连;2026-09-21 复核两端空闲 100s 后调用仍 200,未被 1Panel 回退 | ✅ |
|
|
@@ -1,142 +1,164 @@
|
|
|
1
|
-
"""P0-4 场景 a 端到端驱动:双 MCP 本地 stdio 真实子进程跑通新建工程链路。
|
|
2
|
-
|
|
3
|
-
流程(对应 ht32-skills/ht32-project-create):
|
|
4
|
-
wiki-mcp: get_model + get_chip_info(HT32F52352 规格核对)
|
|
5
|
-
code-mcp: tools/list(7 工具)→ get_project_manifest → export_project_bundle → 落盘核对
|
|
6
|
-
|
|
7
|
-
用法:
|
|
8
|
-
<code-venv-python> e2e_scenario_a.py [--chip HT32F52352]
|
|
9
|
-
输出:
|
|
10
|
-
%TEMP%/ht32_p0_4_e2e/wiki_chip_info.txt | manifest.txt | export.txt
|
|
11
|
-
%TEMP%/ht32_p0_4_e2e/stage/<chip>_demo/ 导出产物(编译对象)
|
|
12
|
-
退出码 0 全通过;1 失败。
|
|
13
|
-
"""
|
|
14
|
-
from __future__ import annotations
|
|
15
|
-
|
|
16
|
-
import argparse
|
|
17
|
-
import asyncio
|
|
18
|
-
import
|
|
19
|
-
import
|
|
20
|
-
import
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
from fastmcp
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
1
|
+
"""P0-4 场景 a 端到端驱动:双 MCP 本地 stdio 真实子进程跑通新建工程链路。
|
|
2
|
+
|
|
3
|
+
流程(对应 ht32-skills/ht32-project-create):
|
|
4
|
+
wiki-mcp: get_model + get_chip_info(HT32F52352 规格核对)
|
|
5
|
+
code-mcp: tools/list(7 工具)→ get_project_manifest → export_project_bundle → 落盘核对
|
|
6
|
+
|
|
7
|
+
用法:
|
|
8
|
+
<code-venv-python> e2e_scenario_a.py [--chip HT32F52352]
|
|
9
|
+
输出:
|
|
10
|
+
%TEMP%/ht32_p0_4_e2e/wiki_chip_info.txt | manifest.txt | export.txt
|
|
11
|
+
%TEMP%/ht32_p0_4_e2e/stage/<chip>_demo/ 导出产物(编译对象)
|
|
12
|
+
退出码 0 全通过;1 失败。
|
|
13
|
+
"""
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import argparse
|
|
17
|
+
import asyncio
|
|
18
|
+
import json
|
|
19
|
+
import os
|
|
20
|
+
import shutil
|
|
21
|
+
import sys
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
|
|
24
|
+
from fastmcp import Client
|
|
25
|
+
from fastmcp.client.transports import StdioTransport
|
|
26
|
+
|
|
27
|
+
CODE_MCP_DIR = Path(__file__).resolve().parent
|
|
28
|
+
CODE_ROOT = CODE_MCP_DIR.parents[1]
|
|
29
|
+
WIKI_MCP_DIR = Path(r"d:\HoltekWork\WorkDesktop\ht32_ai_mcp\ht32-wiki\.work\mcp-server")
|
|
30
|
+
WIKI_ROOT = WIKI_MCP_DIR.parents[1]
|
|
31
|
+
|
|
32
|
+
CODE_PY = CODE_MCP_DIR / ".venv" / "Scripts" / "python.exe"
|
|
33
|
+
WIKI_PY = WIKI_MCP_DIR / ".venv" / "Scripts" / "python.exe"
|
|
34
|
+
|
|
35
|
+
EXPECTED_TOOLS = {
|
|
36
|
+
"get_code_version", "list_libraries", "find_example", "get_file",
|
|
37
|
+
"search_code_knowledge", "get_project_manifest", "export_project_bundle",
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
WORK = Path(os.environ.get("TEMP", ".")) / "ht32_p0_4_e2e"
|
|
41
|
+
|
|
42
|
+
failed = 0
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def check(name: str, cond: bool, detail: str = "") -> bool:
|
|
46
|
+
global failed
|
|
47
|
+
suffix = f" — {detail}" if detail else ""
|
|
48
|
+
print(f"{'✅' if cond else '❌'} [{name}]{suffix}")
|
|
49
|
+
if not cond:
|
|
50
|
+
failed += 1
|
|
51
|
+
return cond
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
async def call_text(client: Client, tool: str, args: dict) -> str:
|
|
55
|
+
r = await client.call_tool(tool, args)
|
|
56
|
+
parts = [c.text for c in r.content if getattr(c, "text", None)]
|
|
57
|
+
return "\n".join(parts)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def wiki_transport() -> StdioTransport:
|
|
61
|
+
env = dict(os.environ)
|
|
62
|
+
env.update({"WIKI_ROOT": str(WIKI_ROOT), "MCP_TRANSPORT": "stdio",
|
|
63
|
+
"MCP_WARMUP": "1", "DS_RAG_AUTOBUILD": "0"})
|
|
64
|
+
for key in ("EMBED_API_KEY", "SILICONFLOW_API_KEY", "RERANK_API_KEY"):
|
|
65
|
+
env.pop(key, None)
|
|
66
|
+
return StdioTransport(str(WIKI_PY), ["-m", "ht32_wiki_mcp"],
|
|
67
|
+
env=env, cwd=str(WIKI_MCP_DIR))
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def devices_entry(chip: str) -> dict:
|
|
71
|
+
"""该型号在 raw/templates/*/devices.json 中的参数条目(用于核对导出产物)。"""
|
|
72
|
+
for dj in sorted((CODE_ROOT / "raw" / "templates").glob("*/devices.json")):
|
|
73
|
+
for d in json.loads(dj.read_text(encoding="utf-8")).get("devices", []):
|
|
74
|
+
if d.get("device", "").upper() == chip.upper():
|
|
75
|
+
return d
|
|
76
|
+
return {}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def code_transport() -> StdioTransport:
|
|
80
|
+
env = dict(os.environ)
|
|
81
|
+
env["CODE_ROOT"] = str(CODE_ROOT)
|
|
82
|
+
return StdioTransport(str(CODE_PY), ["-m", "ht32_code_mcp"],
|
|
83
|
+
env=env, cwd=str(CODE_MCP_DIR))
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
async def phase_wiki(chip: str) -> str:
|
|
87
|
+
print("\n========== 阶段 1 · wiki-mcp 规格核对 ==========")
|
|
88
|
+
async with Client(wiki_transport()) as client:
|
|
89
|
+
model_text = await call_text(client, "get_model", {"model": chip})
|
|
90
|
+
check("get_model 返回系列映射", "系列:" in model_text,
|
|
91
|
+
model_text.splitlines()[0] if model_text else "(空)")
|
|
92
|
+
chip_text = await call_text(client, "get_chip_info", {"model": chip})
|
|
93
|
+
check("get_chip_info 命中系列页", "未找到" not in chip_text and len(chip_text) > 200,
|
|
94
|
+
f"{len(chip_text)} 字符")
|
|
95
|
+
(WORK / "wiki_chip_info.txt").write_text(
|
|
96
|
+
f"== get_model({chip}) ==\n{model_text}\n\n== get_chip_info({chip}) ==\n{chip_text}",
|
|
97
|
+
encoding="utf-8")
|
|
98
|
+
print(f"规格核对全文已存 {WORK / 'wiki_chip_info.txt'}")
|
|
99
|
+
return chip_text
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
async def phase_code(chip: str) -> Path:
|
|
103
|
+
print("\n========== 阶段 2 · code-mcp manifest + export ==========")
|
|
104
|
+
stage = WORK / "stage" / f"{chip.lower()}_demo"
|
|
105
|
+
if stage.exists():
|
|
106
|
+
shutil.rmtree(stage)
|
|
107
|
+
stage.parent.mkdir(parents=True, exist_ok=True)
|
|
108
|
+
|
|
109
|
+
async with Client(code_transport()) as client:
|
|
110
|
+
tools = {t.name for t in await client.list_tools()}
|
|
111
|
+
check("7 工具注册齐全", EXPECTED_TOOLS <= tools,
|
|
112
|
+
f"{len(tools)} 个" if EXPECTED_TOOLS <= tools else f"缺 {EXPECTED_TOOLS - tools}")
|
|
113
|
+
|
|
114
|
+
manifest = await call_text(client, "get_project_manifest", {})
|
|
115
|
+
check("manifest 返回模板清单", "工程模板清单" in manifest,
|
|
116
|
+
manifest.splitlines()[0] if manifest else "(空)")
|
|
117
|
+
(WORK / "manifest.txt").write_text(manifest, encoding="utf-8")
|
|
118
|
+
|
|
119
|
+
export = await call_text(client, "export_project_bundle",
|
|
120
|
+
{"chip": chip, "out_dir": str(stage)})
|
|
121
|
+
check("export_project_bundle 成功", "导出失败" not in export and stage.is_dir(),
|
|
122
|
+
export.splitlines()[0] if export else "(空)")
|
|
123
|
+
(WORK / "export.txt").write_text(export, encoding="utf-8")
|
|
124
|
+
|
|
125
|
+
uvprojx = list(stage.rglob("*.uvprojx"))
|
|
126
|
+
check("落盘核对:.uvprojx 存在", len(uvprojx) == 1, str(uvprojx))
|
|
127
|
+
files = [p for p in stage.rglob("*") if p.is_file()]
|
|
128
|
+
check("落盘核对:工程文件数 > 20", len(files) > 20, f"{len(files)} 个文件")
|
|
129
|
+
# 导出参数化自检(W3 回归):内存布局 / Target 名 / SVD 都必须随型号改写,
|
|
130
|
+
# 否则链接器会按模板默认型号的容量静默放行
|
|
131
|
+
entry = devices_entry(chip)
|
|
132
|
+
text = uvprojx[0].read_text(encoding="utf-8") if uvprojx else ""
|
|
133
|
+
check("导出参数化:<Cpu> 与参数表一致",
|
|
134
|
+
bool(entry.get("cpu")) and f"<Cpu>{entry['cpu']}</Cpu>" in text,
|
|
135
|
+
entry.get("cpu", "参数表缺 cpu"))
|
|
136
|
+
check("导出参数化:Target/输出名 = 型号(缺省)",
|
|
137
|
+
f"<TargetName>{chip.upper()}</TargetName>" in text
|
|
138
|
+
and f"<OutputName>{chip.upper()}</OutputName>" in text)
|
|
139
|
+
check("导出参数化:<SFDFile> 指向型号", f"$$Device:{chip.upper()}$" in text)
|
|
140
|
+
print(f"导出产物:{stage}")
|
|
141
|
+
return stage
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
async def main() -> int:
|
|
145
|
+
parser = argparse.ArgumentParser()
|
|
146
|
+
parser.add_argument("--chip", default="HT32F52352")
|
|
147
|
+
args = parser.parse_args()
|
|
148
|
+
WORK.mkdir(parents=True, exist_ok=True)
|
|
149
|
+
if not (WIKI_PY.is_file() and CODE_PY.is_file()):
|
|
150
|
+
print("❌ 找不到 venv 解释器", file=sys.stderr)
|
|
151
|
+
return 1
|
|
152
|
+
|
|
153
|
+
await phase_wiki(args.chip)
|
|
154
|
+
stage = await phase_code(args.chip)
|
|
155
|
+
|
|
156
|
+
print("\n========== 摘要 ==========")
|
|
157
|
+
print(f"工作目录: {WORK}")
|
|
158
|
+
print(f"导出工程: {stage}")
|
|
159
|
+
print(f"结果: {'✅ 全部通过' if failed == 0 else f'❌ {failed} 项失败'}")
|
|
160
|
+
return 0 if failed == 0 else 1
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
if __name__ == "__main__":
|
|
164
|
+
raise SystemExit(asyncio.run(main()))
|
|
@@ -1,35 +1,35 @@
|
|
|
1
|
-
[build-system]
|
|
2
|
-
requires = ["hatchling"]
|
|
3
|
-
build-backend = "hatchling.build"
|
|
4
|
-
|
|
5
|
-
[project]
|
|
6
|
-
name = "ht32-code-mcp"
|
|
7
|
-
version = "0.2.
|
|
8
|
-
description = "HT32 Code MCP Server - 为 AI Agent 提供 HT32 函数库/例程/模板检索与工程导出"
|
|
9
|
-
requires-python = ">=3.11"
|
|
10
|
-
license = "MIT"
|
|
11
|
-
keywords = ["ht32", "mcu", "mcp", "firmware", "holtek"]
|
|
12
|
-
classifiers = [
|
|
13
|
-
"Development Status :: 3 - Alpha",
|
|
14
|
-
"Programming Language :: Python :: 3",
|
|
15
|
-
"Programming Language :: Python :: 3.11",
|
|
16
|
-
"Programming Language :: Python :: 3.12",
|
|
17
|
-
"Topic :: Software Development :: Embedded Systems",
|
|
18
|
-
]
|
|
19
|
-
dependencies = [
|
|
20
|
-
"fastmcp>=0.5.0",
|
|
21
|
-
]
|
|
22
|
-
|
|
23
|
-
[project.optional-dependencies]
|
|
24
|
-
dev = [
|
|
25
|
-
"pytest>=8.0",
|
|
26
|
-
]
|
|
27
|
-
|
|
28
|
-
[project.scripts]
|
|
29
|
-
ht32-code-mcp = "ht32_code_mcp.server:main"
|
|
30
|
-
|
|
31
|
-
[tool.hatch.build.targets.wheel]
|
|
32
|
-
packages = ["src/ht32_code_mcp"]
|
|
33
|
-
|
|
34
|
-
[tool.pytest.ini_options]
|
|
35
|
-
testpaths = ["tests"]
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ht32-code-mcp"
|
|
7
|
+
version = "0.2.5"
|
|
8
|
+
description = "HT32 Code MCP Server - 为 AI Agent 提供 HT32 函数库/例程/模板检索与工程导出"
|
|
9
|
+
requires-python = ">=3.11"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
keywords = ["ht32", "mcu", "mcp", "firmware", "holtek"]
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Development Status :: 3 - Alpha",
|
|
14
|
+
"Programming Language :: Python :: 3",
|
|
15
|
+
"Programming Language :: Python :: 3.11",
|
|
16
|
+
"Programming Language :: Python :: 3.12",
|
|
17
|
+
"Topic :: Software Development :: Embedded Systems",
|
|
18
|
+
]
|
|
19
|
+
dependencies = [
|
|
20
|
+
"fastmcp>=0.5.0",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[project.optional-dependencies]
|
|
24
|
+
dev = [
|
|
25
|
+
"pytest>=8.0",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[project.scripts]
|
|
29
|
+
ht32-code-mcp = "ht32_code_mcp.server:main"
|
|
30
|
+
|
|
31
|
+
[tool.hatch.build.targets.wheel]
|
|
32
|
+
packages = ["src/ht32_code_mcp"]
|
|
33
|
+
|
|
34
|
+
[tool.pytest.ini_options]
|
|
35
|
+
testpaths = ["tests"]
|