Files
OneCAD-Draft/docs/automation/mcp_acceptance.py
T
Luis MartinezandClaude Opus 5.5 dddf60d79e Add audited, versioned MCP save workflow (#1452)
- `--export ... --target-version`, and explicit target_format /
  target_version on automation and protocol saves; a conversion that
  would drop unsupported records is refused unless allow_lossy is set.
- `audit` read op: dangling layers/blocks, duplicate handles, non-finite
  bounds, serialization, lossy records and the source DXF handle graph.
- `save_verified`: writes the requested version from the same snapshot a
  normal save writes, reopens it, checks the version and the entity
  manifest, audits the ASCII DXF handle graph and returns a SHA-256.
- ZOOM EXTENTS includes SDF text glyphs.
- Live MCP acceptance script and an optional reference-console audit
  runner under docs/automation.

Squashed from the PR with review fixes: merged with the DWT save path,
the save snapshot, the tool lists added since, and vendor-neutral
harness names; the dated verification record is left out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 16:49:11 +03:00

153 lines
5.3 KiB
Python

"""End-to-end GUI/MCP acceptance with versioned, verified CAD outputs."""
import base64
import json
from pathlib import Path
import struct
import sys
import time
import uuid
from mcp_eval import Client, META
def request_id(prefix: str) -> str:
return f"accept-{prefix}-{uuid.uuid4().hex}"
def main() -> None:
server = Path(sys.argv[1] if len(sys.argv) > 1 else "target/debug/OpenCADStudio.exe").resolve()
root = Path(sys.argv[2] if len(sys.argv) > 2 else "target/acceptance").resolve()
output = root / time.strftime("%Y%m%d-%H%M%S")
output.mkdir(parents=True, exist_ok=False)
client = Client(server)
session = None
report: dict = {"ok": False, "server": str(server), "output": str(output)}
try:
sessions = client.tool("ocs_sessions", {"launch_if_none": True})["result"]
session = sessions[0]["session_id"]
while sessions[0].get("modal"):
client.tool(
"ocs_execute",
{
"ocs_session_id": session,
"request": {
"op": "action",
"request_id": request_id("close-modal"),
"name": "close_modal",
},
},
)
sessions = client.tool("ocs_sessions", {"launch_if_none": False})["result"]
client.tool(
"ocs_execute",
{
"ocs_session_id": session,
"request": {"op": "new", "request_id": request_id("new")},
},
)
drawing = client.tool(
"ocs_execute",
{
"ocs_session_id": session,
"response_detail": "changed_entities",
"request": {
"op": "batch",
"request_id": request_id("draw"),
"steps": [
{"op": "run", "cmd": "PLINE 0,0 200,0 200,120 0,120 C"},
{"op": "run", "cmd": "CIRCLE 50,60 15"},
{"op": "run", "cmd": "CIRCLE 150,60 15"},
{"op": "run", "cmd": "LINE 0,60 200,60"},
{"op": "run", "cmd": "TEXT 100,135 8 0 OPEN CAD MCP ACCEPTANCE"},
{"op": "action", "name": "zoom_extents"},
],
},
},
)
assert drawing["completed_steps"] == 6, drawing
audits = {}
saves = {}
targets = [("dwg", "2000"), ("dwg", "2013"), ("dwg", "2018"), ("dxf", "2000")]
for target_format, target_version in targets:
key = f"{target_format}-{target_version}"
audit = client.tool(
"ocs_read",
{
"ocs_session_id": session,
"op": "audit",
"parameters": {
"target_format": target_format,
"target_version": target_version,
},
},
)
assert audit["ok"] is True, audit
audits[key] = audit
destination = output / f"mcp-acceptance-{target_version}.{target_format}"
saved = client.tool(
"ocs_execute",
{
"ocs_session_id": session,
"response_detail": "full",
"request": {
"op": "save_verified",
"request_id": request_id(f"save-{key}"),
"path": str(destination),
"target_format": target_format,
"target_version": target_version,
},
},
)
verified = saved.get("result", saved)
assert verified["verified"] is True, saved
assert destination.is_file(), destination
saves[key] = verified
capture = client.rpc(
"tools/call",
{
"name": "ocs_capture",
"arguments": {
"ocs_session_id": session,
"scope": "viewport",
"max_dimension": 1600,
},
"_meta": META,
},
)
image = capture["content"][0]
assert image["type"] == "image" and image["mimeType"] == "image/png", capture
png = base64.b64decode(image["data"], validate=True)
assert png[:8] == b"\x89PNG\r\n\x1a\n", "capture is not a PNG"
width, height = struct.unpack(">II", png[16:24])
capture_path = output / "viewport.png"
capture_path.write_bytes(png)
report.update(
{
"ok": True,
"session_id": session,
"drawn_entities": drawing["changed_entities"],
"audits": audits,
"saves": saves,
"capture": {"path": str(capture_path), "width": width, "height": height},
"metrics": {
"tool_calls": client.tool_calls,
"tasks": client.tasks,
"rpc_calls": client.rpc_calls,
},
}
)
finally:
client.close()
report_path = output / "report.json"
report_path.write_text(json.dumps(report, indent=2), encoding="utf-8")
print(json.dumps(report, indent=2))
if __name__ == "__main__":
main()