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.
@@ -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.2(2026-09-23,含 bundle 通道 + 统一反馈/用量日志 + 软失败识别 + 原生 Streamable HTTP) | ✅ |
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,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ht32-code-mcp
3
- Version: 0.2.3
3
+ Version: 0.2.5
4
4
  Summary: HT32 Code MCP Server - 为 AI Agent 提供 HT32 函数库/例程/模板检索与工程导出
5
5
  License-Expression: MIT
6
6
  Keywords: firmware,holtek,ht32,mcp,mcu
@@ -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 os
19
- import shutil
20
- import sys
21
- from pathlib import Path
22
-
23
- from fastmcp import Client
24
- from fastmcp.client.transports import StdioTransport
25
-
26
- CODE_MCP_DIR = Path(__file__).resolve().parent
27
- WIKI_MCP_DIR = Path(r"d:\HoltekWork\WorkDesktop\ht32_ai_mcp\ht32-wiki\.work\mcp-server")
28
- WIKI_ROOT = WIKI_MCP_DIR.parents[1]
29
-
30
- CODE_PY = CODE_MCP_DIR / ".venv" / "Scripts" / "python.exe"
31
- WIKI_PY = WIKI_MCP_DIR / ".venv" / "Scripts" / "python.exe"
32
-
33
- EXPECTED_TOOLS = {
34
- "get_code_version", "list_libraries", "find_example", "get_file",
35
- "search_code_knowledge", "get_project_manifest", "export_project_bundle",
36
- }
37
-
38
- WORK = Path(os.environ.get("TEMP", ".")) / "ht32_p0_4_e2e"
39
-
40
- failed = 0
41
-
42
-
43
- def check(name: str, cond: bool, detail: str = "") -> bool:
44
- global failed
45
- suffix = f" — {detail}" if detail else ""
46
- print(f"{'✅' if cond else '❌'} [{name}]{suffix}")
47
- if not cond:
48
- failed += 1
49
- return cond
50
-
51
-
52
- async def call_text(client: Client, tool: str, args: dict) -> str:
53
- r = await client.call_tool(tool, args)
54
- parts = [c.text for c in r.content if getattr(c, "text", None)]
55
- return "\n".join(parts)
56
-
57
-
58
- def wiki_transport() -> StdioTransport:
59
- env = dict(os.environ)
60
- env.update({"WIKI_ROOT": str(WIKI_ROOT), "MCP_TRANSPORT": "stdio",
61
- "MCP_WARMUP": "1", "DS_RAG_AUTOBUILD": "0"})
62
- for key in ("EMBED_API_KEY", "SILICONFLOW_API_KEY", "RERANK_API_KEY"):
63
- env.pop(key, None)
64
- return StdioTransport(str(WIKI_PY), ["-m", "ht32_wiki_mcp"],
65
- env=env, cwd=str(WIKI_MCP_DIR))
66
-
67
-
68
- def code_transport() -> StdioTransport:
69
- env = dict(os.environ)
70
- env["CODE_ROOT"] = str(CODE_MCP_DIR.parents[1])
71
- return StdioTransport(str(CODE_PY), ["-m", "ht32_code_mcp"],
72
- env=env, cwd=str(CODE_MCP_DIR))
73
-
74
-
75
- async def phase_wiki(chip: str) -> str:
76
- print("\n========== 阶段 1 · wiki-mcp 规格核对 ==========")
77
- async with Client(wiki_transport()) as client:
78
- model_text = await call_text(client, "get_model", {"model": chip})
79
- check("get_model 返回系列映射", "系列:" in model_text,
80
- model_text.splitlines()[0] if model_text else "(空)")
81
- chip_text = await call_text(client, "get_chip_info", {"model": chip})
82
- check("get_chip_info 命中系列页", "未找到" not in chip_text and len(chip_text) > 200,
83
- f"{len(chip_text)} 字符")
84
- (WORK / "wiki_chip_info.txt").write_text(
85
- f"== get_model({chip}) ==\n{model_text}\n\n== get_chip_info({chip}) ==\n{chip_text}",
86
- encoding="utf-8")
87
- print(f"规格核对全文已存 {WORK / 'wiki_chip_info.txt'}")
88
- return chip_text
89
-
90
-
91
- async def phase_code(chip: str) -> Path:
92
- print("\n========== 阶段 2 · code-mcp manifest + export ==========")
93
- stage = WORK / "stage" / f"{chip.lower()}_demo"
94
- if stage.exists():
95
- shutil.rmtree(stage)
96
- stage.parent.mkdir(parents=True, exist_ok=True)
97
-
98
- async with Client(code_transport()) as client:
99
- tools = {t.name for t in await client.list_tools()}
100
- check("7 工具注册齐全", EXPECTED_TOOLS <= tools,
101
- f"{len(tools)} 个" if EXPECTED_TOOLS <= tools else f"缺 {EXPECTED_TOOLS - tools}")
102
-
103
- manifest = await call_text(client, "get_project_manifest", {})
104
- check("manifest 返回模板清单", "工程模板清单" in manifest,
105
- manifest.splitlines()[0] if manifest else "(空)")
106
- (WORK / "manifest.txt").write_text(manifest, encoding="utf-8")
107
-
108
- export = await call_text(client, "export_project_bundle",
109
- {"chip": chip, "out_dir": str(stage)})
110
- check("export_project_bundle 成功", "导出失败" not in export and stage.is_dir(),
111
- export.splitlines()[0] if export else "(空)")
112
- (WORK / "export.txt").write_text(export, encoding="utf-8")
113
-
114
- uvprojx = list(stage.rglob("*.uvprojx"))
115
- check("落盘核对:.uvprojx 存在", len(uvprojx) == 1, str(uvprojx))
116
- files = [p for p in stage.rglob("*") if p.is_file()]
117
- check("落盘核对:工程文件数 > 20", len(files) > 20, f"{len(files)} 个文件")
118
- print(f"导出产物:{stage}")
119
- return stage
120
-
121
-
122
- async def main() -> int:
123
- parser = argparse.ArgumentParser()
124
- parser.add_argument("--chip", default="HT32F52352")
125
- args = parser.parse_args()
126
- WORK.mkdir(parents=True, exist_ok=True)
127
- if not (WIKI_PY.is_file() and CODE_PY.is_file()):
128
- print("❌ 找不到 venv 解释器", file=sys.stderr)
129
- return 1
130
-
131
- await phase_wiki(args.chip)
132
- stage = await phase_code(args.chip)
133
-
134
- print("\n========== 摘要 ==========")
135
- print(f"工作目录: {WORK}")
136
- print(f"导出工程: {stage}")
137
- print(f"结果: {'✅ 全部通过' if failed == 0 else f'❌ {failed} 项失败'}")
138
- return 0 if failed == 0 else 1
139
-
140
-
141
- if __name__ == "__main__":
142
- raise SystemExit(asyncio.run(main()))
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.3"
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"]