Files
autocad-mcp-pro/docs/analysis/00-RECON-dossier.md
T
Umutcan Edizsalan 0c7f64ceb6 fix(correctness-bundle): I16 correctness fixes + dead code removal + ruff clean
N1: COM hatch non-associative (AddHatch associativity=False)
N2: ezdxf refuses DWG saves with clear error; server.py derives fmt from extension
R19: dimension_aligned dist→distance; dimension_angular correct add_angular_dim_2l args (base/line1/line2/location)
R9: polar array 360° no-duplicate (divisor=n when fill_angle%360<ε)
R13: entity_offset side_x/side_y honored in ezdxf via cross-product (LINE) and distance-to-center (CIRCLE)
R18: drawing_new bootstrap failure surfaces status="degraded" instead of swallowing silently
Dead code: engineering/section.py, gear.generate_tooth_profile, CommandResult, _ACI_NAMES, set_layer_active, ensure_linetypes, ComBackend._batch_mode instance attr, engineering/__init__ build-seam comment
Ruff: 38 auto-fixed + 5 manual → codebase is now ruff-clean; CI lint is now blocking

184 tests pass.
2026-06-18 11:34:30 +03:00

209 lines
31 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
The lineweight truncation bug is confirmed devastating: ENGINEERING_LAYERS uses floats like 0.50, 0.25, 0.18, 0.13, 0.05 mm, and `layer_create` does `int(lineweight)` (ezdxf_backend.py:1034), so every one floors to 0. Counts confirmed: 109 tools, 6 resources, 5 prompts; LICENSE/benchmarks/ci.yml all absent. All load-bearing claims verified against source. I have everything needed to synthesize.
# AutoCAD MCP Pro — Understanding & Research Dossier
## 1. Architecture at a Glance
AutoCAD MCP Pro is a **single-file FastMCP 3.0 server** (`server.py`, 2881 lines) exposing **109 `@mcp.tool` registrations, 6 `@mcp.resource` endpoints, 5 `@mcp.prompt` templates** (verified by grep, not the docs' "87"). It uses a **strategy-pattern dual backend** behind one ABC (`backends/base.py`, ~73 abstract methods + 5 dataclasses): a **live COM backend** (`com_backend.py`, pywin32, single STA-thread `ThreadPoolExecutor`) and a **headless ezdxf backend** (`ezdxf_backend.py`, file-based DXF, all sync work via `asyncio.to_thread` under one `asyncio.Lock`). Backend selection (`server.py::_make_backend`, server.py:69): env `AUTOCAD_MCP_BACKEND`; Windows `auto`/`com` tries COM then falls back to ezdxf; non-Windows always ezdxf.
Request path: `ErrorHandlingMiddleware → AuditMiddleware → TimingMiddleware → LoggingMiddleware` (server.py:197-200) → tool fn → `_backend(ctx)` (server.py:207, raises `ToolError` if backend None) → identically-named backend method → `_dc()` dataclass→dict serialization (server.py:216). Lifespan (`autocad_lifespan`, server.py:107) builds the backend singleton into `ctx.lifespan_context["backend"]`.
A **deterministic engineering layer** (`engineering/` package) backs the "premium" workflow: parametric gear/keyway/titleblock primitives, a closed-enum `drawing_critique`, an 8-step `DrawingValidator`, ISO layer/linetype scaffolds, and a `PlanSpec` plan-before-draw contract. **The central architectural fact: the premium workflow is fully implemented only on ezdxf; the COM backend stubs all 8 premium meta-tools with `NotImplementedError` (com_backend.py:1637-1660, verified) — yet COM is the default Windows backend.**
---
## 2. Per-Subsystem Summary
**`server.py` (transport + tool registry).** Thin async wrappers over the backend singleton, grouped into 13 sections (1–12 + sub-sections 8b/8c/8d batch/template/validation + 13 premium). Owns server-side composition tools (`entity_delete_many` server.py:1065, `layer_isolate` :1216, `block_find_references` :1362, `analysis_layer_stats` :1506, `entity_batch_*` :1538/:1590, `template_*` :1695/:1730, `validation_check` :1749). Entry point manually parses `--transport/--port/--host` (server.py:2866) and refuses non-loopback HTTP unless `ALLOW_REMOTE_HTTP` + `MCP_AUTH_TOKEN` (`_validate_http_bind`).
**`backends/base.py` (the contract).** `AutoCADBackend` ABC: ~73 `@abstractmethod`, 2 concrete (`set_layer_active`, `ensure_linetypes`), 5 dataclasses (`EntityInfo/LayerInfo/BlockInfo/DrawingInfo/CommandResult`), 3 helpers (`shoelace_area`, `deg2rad`, `rad2deg`). Both backends define a body for every abstract method (no structural gaps); divergences are behavioral.
**`backends/ezdxf_backend.py` (headless).** Full create/modify/query/analysis/dimension/block/layer + premium meta-tools + corner ops. Transactions = full DXF snapshot to tempfile on `_undo_stack` (trimmed to `max_undo_stack=5`, O(drawing-size) per `transaction_begin`). View ops and `system_run_command/run_lisp` are documented no-ops. Screenshot/PDF via matplotlib.
**`backends/com_backend.py` (live AutoCAD).** All COM through one STA worker with per-call timeout (`com_call_timeout`, default 60s) and executor-rebuild on timeout/fatal HRESULT. Corner ops via `_safe_send_command` (CMDACTIVE-polling). Win32 `PrintWindow` screenshots. **8 premium meta-tools + `block_create_from_entities` are stubs.**
**`engineering/` (deterministic CAD).** `gear.py` (involute spur/helical outlines), `keyway.py` (DIN 6885), `titleblock.py` (ISO 7200 A3), `layers.py` (mech/pid/iso13567 sets), `critique.py` (6 focuses), `validator.py` (8-step gate), `plan_spec.py` (PlanSpec/Issue + ISO-128 lineweight set). `section.py` (`cutting_plane_line`, `apply_section_hatch`) and `gear.generate_tooth_profile` are **dead code** (exported, zero callers/tests).
**`tests/` (184 functions, 14 files).** One ezdxf-only `backend` fixture (`conftest.py`). COM backend, server→sanitizer wiring, and `section.py` have **zero coverage**.
---
## 3. Complete MCP Tool Inventory (grouped; ✅ real / ⚠️ partial / ❌ stub-or-missing)
> All 109 tools are **registered** in `server.py` and have **real ezdxf implementations**. "Status" below reflects the **COM backend** (the Windows default) plus correctness caveats. Nothing is purely aspirational/unregistered.
| § | Tools | ezdxf | COM | Notes |
|---|---|---|---|---|
| 1 Drawing (12, doc says 11) | info, new, open, save, save_as, export_dxf, export_pdf, purge, audit, close, undo, redo | ✅ (redo ❌ no-op ezdxf_backend.py:487) | ✅ | `drawing_redo` inert on ezdxf; header says 11 but 12 registered (drawing_redo server.py:426) |
| 2 Entity Create (13) | line, circle, arc, polyline, rectangle(→polyline), text, mtext, hatch, spline, ellipse, point, block_ref | ✅ | ✅ | mtext has no rotation param on ezdxf (:578); hatch pattern-only single loop |
| 3 Dimensions (5) | linear, aligned, angular, radius, diameter | ⚠️ aligned/angular **xfail** (tests/test_dimensions.py:16); angular ignores tx/ty + fixed dist=10 (ezdxf:705) | ✅ | No dimstyle/tolerance support either backend |
| 4 Modify (14) | move, copy, rotate, scale, mirror, offset, trim, extend, fillet, chamfer, delete, array_rectangular, array_polar, set_properties | ⚠️ offset ignores side (:868); corner ops LINE+LINE only (:1535-1750); polar 360° dup (:946) | ⚠️ offset ignores side (:860); corner ops via SendCommand | |
| 5 Query (3) | entity_get, entity_list, entity_delete_many | ✅ | ✅ | COM entity_list = O(n) per-item COM reads (:967) |
| 6 Layer (14) | list, create, delete, set_current, modify, freeze, thaw, lock, unlock, hide, show, isolate, linetype_list, linetype_load | ⚠️ **lineweight int() truncation** (:1034) | ✅ | See RISK-2 |
| 7 Block (7) | list, insert, explode, get_attributes, set_attributes, create_from_entities, find_references | ✅ | ⚠️ create_from_entities ❌ stub (:1253) | BlockInfo.description never populated |
| 8 Analysis (8) | stats, find_in_region, measure_distance, measure_area, bounding_box, select_by_layer, select_by_type, layer_stats | ⚠️ region fallback insert/start only (:1330) | ✅ | hand-rolled loops vs ezdxf query/groupby |
| 8b Batch (2) | entity_batch_create, entity_batch_modify | ✅ | ✅ | |
| 8c Template (2) | template_apply_layers, template_list | ✅ | ✅ | Duplicates `_LAYER_TEMPLATES`/prompt layer sets |
| 8d Validation (1) | validation_check | ✅ | ✅ | |
| 9 View (4, doc says 5) | zoom_extents, zoom_window, screenshot, zoom_and_screenshot | ⚠️ zoom = no-op (:1394) | ✅ | Header says 5 |
| 10 Transactions (3) | begin, commit, rollback | ✅ (commit file-I/O outside lock :1452) | ⚠️ flag-leak on error (:1402) | |
| 11 System (6) | status, get_variable, set_variable, run_command, run_lisp, about | ⚠️ run_command/lisp no-op | ⚠️ run_lisp result always 'nil' (:1487); about catalog drifted | |
| 12 Engineering (7) | gear_draw_helical/spur/section_aa, keyway_draw_keyed_bore/section, titleblock_apply_iso_a3, drawing_finalize | ✅ | ✅ (use generic entity_create_*) | Validator/titleblock import-guarded → None risk |
| 13 Premium (8) | drawing_plan, drawing_critique, point_from_snap, construction_xline, construction_clear, drawing_apply_iso_layers, dimension_auto, entity_select_smart | ✅ | ❌ **all NotImplementedError** (:1637-1660) | **The documented-mandatory workflow is dead on COM** |
**Documented-but-missing entirely:** none. **Documented-as-working but stubbed on default backend:** the 8 premium tools (§13) + `block_create_from_entities` on COM.
---
## 4. Cross-Cutting RISK Register (merged, deduped, severity-sorted)
| # | Sev | Risk | Location | Evidence |
|---|---|---|---|---|
| R1 | **CRITICAL** | All 8 premium meta-tools `raise NotImplementedError` on COM (the Windows default). The "non-negotiable for production" workflow (`drawing_plan→apply_iso_layers→critique→finalize`) 500s on the primary platform. | com_backend.py:1637-1660 | **Verified verbatim**; ezdxf impls real (ezdxf_backend.py:1753-2050) |
| R2 | **CRITICAL** | `layer_create` does `int(lineweight)` so every sub-1.0 ISO mm value (0.50/0.25/0.18/0.13/0.05) floors to **0**. The entire ISO-128 lineweight discipline is silently nulled; `drawing_apply_iso_layers`/`drawing_new` bootstrap produce flat 0-weight layers. | ezdxf_backend.py:1034; values engineering/layers.py:16-26 | **Both verified.** ezdxf wants integer hundredths (50=0.50mm), not raw mm |
| R3 | **HIGH** | `_check_iso128` is a permanent no-op: `lw <= 0: continue` (critique.py:50) skips every layer made 0 by R2. The advertised ISO gate can never fire. Comment "the layer dataclass already normalises to mm" (critique.py:53) is **false** (ezdxf_backend.py:189 stores raw `float(lw)`). | engineering/critique.py:37-64 | **Verified verbatim** |
| R4 | **HIGH** | `_check_dim_overlap` reads `properties['text_position'/'defpoint'/'insertion_point']`, but the ezdxf DIM extractor sets only `dim_type` (ezdxf_backend.py:166-168) → `dim_points` always empty → check is a silent no-op. | engineering/critique.py:194-197 | — |
| R5 | **HIGH** | COM timeout path (`stuck.shutdown(wait=False)`) cannot kill the STA worker still blocked in SendCommand → **permanently leaks one CoInitialize'd thread + COM proxy per timeout**; no `CoUninitialize` anywhere. | com_backend.py:415-433, 68-71 | — |
| R6 | **HIGH** | Zero COM-backend test coverage (~80 methods incl. UndoMark transactions, SendCommand corner ops, CMDACTIVE guard). Passing ezdxf transaction tests give **false confidence** for the semantically different COM model. | tests/ (no ComBackend import); conftest.py:8-15 | — |
| R7 | **HIGH** | Server→sanitizer wiring untested: `test_security.py` tests `sanitize_command/lisp/validate_path` in isolation; no integration test proves `system_run_command/lisp` actually call them. A refactor dropping the call (server.py:2035/2052) passes all security tests while raw commands reach SendCommand. | server.py:2035,2052; tests/test_security.py | — |
| R8 | **HIGH** | LISP-aliasing bypass (`(setq f command)(f "ERASE")`) is claimed-defended in a comment (security.py:138-140) but has **no adversarial test**; UNC/`~`/symlink path escapes in `validate_path` also untested. | security.py:138-179; test_security.py:177-231 | — |
| R9 | **MED** | `entity_array_polar` full-360° fill double-places: `step=radians(360)/(count-1)` puts the last copy at 0°==original → duplicate. Hits the common bolt-circle/gear pattern the engineering layer relies on. | ezdxf_backend.py:946-947 | — |
| R10 | **MED** | `drawing_finalize` calls `drawing_save_as(str(validated))` with no `fmt`. ezdxf default `fmt='dxf'` (ezdxf:345) vs COM `fmt='dwg'` (com:515) vs save_as tool default `'dwg'` (server:336) → 3-way inconsistency; format backend-dependent, not path-derived. | server.py:2299 | **Verified** (omits fmt) |
| R11 | **MED** | `drawing_save_as(fmt='dwg')` on ezdxf writes DXF bytes to a `.dwg` path and returns `{'format':'dwg'}` — silent wrong-format file. ezdxf cannot write DWG; should error or use `odafc`. | ezdxf_backend.py:345-352 | — |
| R12 | **MED** | `dimension_angular` ignores tx/ty, fixed `distance=10.0` → arc radius wrong for large/small parts; breaks COM parity. | ezdxf_backend.py:705-722 | — |
| R13 | **MED** | `entity_offset` ignores `side_x/side_y` (both backends); only `distance` sign flips side; ARC/LWPOLYLINE/ELLIPSE raise "not supported". | ezdxf:868-908; com:860-874 | — |
| R14 | **MED** | Engineering titleblock/validator symbols import-guarded to `None` (engineering/__init__.py:40-51); if module truly fails to import, tools raise opaque "NoneType not callable" instead of clear ToolError. | server.py:2266,2294 | — |
| R15 | **MED** | `system_about` static `tool_groups` catalog drifted: omits all 15 engineering+premium tools, misfiles `entity_delete_many`. Agents inspecting capabilities can't see the CLAUDE.md-mandated tools. | server.py:2070-2106 | — |
| R16 | **MED** | COM `transaction_active` left stale-True if commit/rollback `_run` raises; `rollback` issues `EndUndoMark`+`UNDO B` SendCommand without the CMDACTIVE guard used elsewhere → can interleave mid-command. | com_backend.py:1402,1411-1421 | — |
| R17 | **MED** | `_safe_send_command` busy-polls CMDACTIVE with `time.sleep(0.05)` on the single STA thread → monopolizes the executor up to deadline; `deadline_s`(8s) decoupled from `_run` timeout(60s). | com_backend.py:1521-1536 | — |
| R18 | **MED** | `drawing_new` swallows bootstrap failures into `result['bootstrap']={'error':...}` and reports success → un-bootstrapped drawing looks normal though CLAUDE.md treats the scaffold as a precondition for gear_*/titleblock_*. | server.py:280-294 | — |
| R19 | **MED** | `dimension_aligned`/`dimension_angular` permanently `xfail(strict=False)` — advertised tools shipped broken; an accidental fix wouldn't alert. | tests/test_dimensions.py:16-27 | — |
| R20 | LOW | `_registered_tool_count` reaches into FastMCP privates (`mcp._local_provider._components`, `'tool:'` prefix); a minor upgrade → tool_count=-1 surfaced to clients. | server.py:231-234 | — |
| R21 | LOW | Involute flank prepends a straight radial root→base segment (no trochoid fillet) → geometrically wrong dedendum for `root_r<base_r`; root-arc gap when `root_r>=base_r`; degenerate arc for teeth≤2. | engineering/gear.py:40-50,163-190 | — |
| R22 | LOW | `drawing_undo`/`transaction_rollback` share one `_undo_stack` with no transaction-id/depth separation; reload doesn't reset `_doc_path`/`_current_layer`. | ezdxf_backend.py:470-484,1462-1476 | — |
| R23 | LOW | COM `system_run_lisp` always reports `result='nil'` (SendCommand returns None); no CMDACTIVE guard → a prompting LISP form deadlocks next call. | com_backend.py:1487-1492 | — |
| R24 | LOW | `_apply_attrs` sets `entity.dxf.linetype` without `_ensure_linetype_loaded` (unlike set_properties/layer_modify) → `linetype='HIDDEN'` on create may render Continuous or raise on save. | ezdxf_backend.py:491-501 | — |
| R25 | LOW | `block_find_references` calls `entity_list(type_filter='INSERT')` with no limit (bypasses `max_list_limit`) → unbounded INSERTs into memory; assumes every backend populates `properties['block_name']`. | server.py:1362-1370 | — |
| R26 | LOW | View zoom ops unconditionally registered but no-op on ezdxf → success response implies the view changed when it didn't; `view_zoom_and_screenshot` may not frame the requested region on ezdxf. | server.py:1814-1831,1870 | — |
| R27 | LOW | `validator.py` title check substring-matches 'SPUR'/'HELICAL SPUR' on TEXT layer → false-positive blocks `drawing_finalize` if any note contains 'SPUR'. | engineering/validator.py:166-181 | — |
| R28 | LOW | COM `_find_autocad_hwnd` grabs first visible window whose title contains 'AutoCAD' → may capture palette/splash/About dialog; not tied to `AcadApplication.HWND`. | com_backend.py:283-300 | — |
| R29 | LOW | Broad `except Exception: pass` swallows COM faults (disconnected/busy) at debug level in ~20 sites → bypasses the HRESULT reconnect logic in `_run`. | com_backend.py:199,244,868,901,1216,… | — |
---
## 5. Doc-vs-Code Drift Table
| Claim (source) | Reality | Verdict |
|---|---|---|
| "87 tools" (CLAUDE.md, README:6) | **109** `@mcp.tool` (verified) | STALE (soft — both add "count is dynamic" caveat) |
| "104 tests" (README:83) | **184** test functions across 14 files (verified) | STALE (~80 under) |
| Section 1 "(11 tools)" (CLAUDE.md) | 12 (`drawing_redo` server.py:426) | WRONG |
| Section 9 "(5 tools)" (CLAUDE.md) | 4 view tools registered | WRONG |
| "12-section taxonomy" (CLAUDE.md) | 1–12 + 8b/8c/8d + 13 = 6 extra groups | INCOMPLETE |
| Premium workflow "non-negotiable for production" (CLAUDE.md) | 8/8 premium tools dead on COM (default Windows backend) | OVERCLAIM |
| "MIT — see [LICENSE](LICENSE)" (README:409) | No LICENSE file (verified absent) | **DEAD LINK / legal drift** |
| `benchmarks/` referenced (README:39,302,367) | Absent (verified) | DEAD LINK (forward-looking) |
| ".github/workflows/ci.yml" + "GitHub Actions CI" (README:84,358) | git status `D`; absent on disk (verified) | **CI REMOVED while docs claim it** |
| "Premium discipline lives in `.claude/skills/autocad-mcp-premium/`" (CLAUDE.md) | Entire tree git-deleted (28 deletions) + all `.cursor/` deleted | DANGLING POINTER |
| README tests/ tree (README:347-355) | Omits 6 of 14 test files + engineering/ package | STALE |
| `system_about` tool_groups | Omits 15 engineering+premium tools | DRIFT |
| `BlockInfo.description` field (base.py:71) | Never populated by either backend | DEAD FIELD |
| critique.py:53 "normalises to mm" comment | Stores raw DXF lineweight (ezdxf:189) | FALSE COMMENT |
| `engineering/__init__.py:37-39` "Agent B … in flight"; com_backend "Task #7/#8/#9" | Multi-agent build seams shipped unreconciled | TODO-IN-PROD |
| Dockerfile, .env.example (README) | Present (verified) | OK |
---
## 6. Test Coverage Gaps (ranked)
1. **COM backend: 0% coverage** (com_backend.py entire file) — transactions (UndoMark vs ezdxf snapshot are *different* semantics), SendCommand corner ops, CMDACTIVE guard, timeout/HRESULT recovery, all 8 premium stubs. Highest-risk untested surface; it's the production path. (R6)
2. **Server→sanitizer integration** — no test proves tools invoke `sanitize_command/lisp`/`validate_path`. (R7)
3. **Sanitizer adversarial cases** — LISP aliasing, command word-boundary evasion (`.ERASE`), UNC/`~`/symlink path escapes — the comment claims defenses with no tests. (R8)
4. **Critique detection paths** — `iso128`, `layer_color`, `dim_overlap` only exercised on a *clean* drawing (assert they *don't* fire); their actual detection logic and the unknown-focus branch (critique.py:241) are never proven. (R3, R4)
5. **`dimension_auto` ordinate** (ezdxf:1988) untested; chain/baseline covered.
6. **`dimension_aligned`/`angular`** permanently xfail — shipped broken. (R19)
7. **Uncovered backend methods (both backends):** `system_get/set_variable`, `system_status`, `drawing_close`, `drawing_export_pdf`, `entity_create_block_ref`, `block_set_attributes`, `linetype_list/load` (load-bearing for bootstrap), `view_zoom_*`, `view_screenshot`.
8. **`engineering/section.py`** (dead code) — 0 coverage.
9. **Transaction nesting/error semantics** — only single begin→commit/rollback tested; nested, rollback-without-begin, layer/block restoration untested.
10. **Infrastructure:** CI workflow deleted (no PR gate); `pytest-cov` declared but no `--cov`/`fail-under`; several "template/validation" tests assert nothing about the named feature (vacuous `if start:` guards, test_batch_template.py:30-34).
---
## 7. Research Synthesis
### 7a. Academic techniques (2025–2026) that validate or upgrade this design
- **GeoGramBench (ICLR 2026, arxiv.org/abs/2505.17653):** frontier LLMs score **<50%** at high-abstraction Program-to-Geometry. → Empirically justifies the repo's no-coordinate-guessing rule and `point_from_snap`/`entity_select_smart`. **Action:** expand the deterministic geometry surface (`point_intersection`, `point_offset`, `point_along`) so the LLM *never* computes coordinates.
- **Self-Improving CAD w/ FEA feedback (arxiv.org/html/2605.17448v2):** deterministic controller owns validation, LLM owns design; **+13.4pp** per feedback round; frontier models get **zero** first-attempt strict passes — *iteration beats more tokens*. → This is the repo's architecture, but `drawing_critique` is one-shot. **Action:** make it a routed repair loop; `Issue` (plan_spec.py:64-81) already carries severity/handles/detail — add per-finding repair hints.
- **CADSmith (arxiv.org/abs/2603.26512):** 5-agent Planner/Coder/Executor/Validator/**Refiner**, dual loop, exact-kernel measurements + **independent VLM judge** → Chamfer 28.37mm→0.74mm (**38×**), IoU 0.81→0.96; *removing the rendered image alone made hard cases 35× worse.* → The repo renders a screenshot at finalize (server.py:2301-2309) but only checks bytes exist (validator step 7). **Highest-leverage single upgrade: feed that render to a VLM judge + add an automated Refiner.**
- **CADFusion (ICML 2025, arxiv.org/abs/2501.19054)** & **Seek-CAD (arxiv.org/abs/2505.17702):** render is a first-class signal; step-wise visual feedback catches errors mid-build. → Add a `visual` critique focus; run critique per construction step using the existing transaction model.
- **ProCAD "Clarify Before You Draw" (arxiv.org/html/2602.03045v1):** one-round clarification → invalidity 14.6%→0.9%, Chamfer ~80% lower. → `drawing_plan` records intent passively; add a pre-plan ambiguity/conflict detector that returns targeted questions before any `entity_create_*`.
- **FutureCAD (arxiv.org/abs/2603.11831):** NL → exact B-Rep primitive grounding fixes the fillet/chamfer "which primitive" problem. → Directly targets the repo's hardest API: trim/extend/fillet/chamfer need exact handles **plus** keep_x/keep_y. NL grounding ("fillet the top-left corner") removes the burden GeoGramBench shows LLMs fail at.
- **Text-to-CadQuery (2505.06507), CAD-Coder (2505.14646), EvoCAD (2510.11631):** field converged on Python/CadQuery macros; open local VLMs read CAD images reliably; candidate-generate + critique-as-fitness raises first-try quality. → Add more parametric meta-tools (the gear/keyway pattern) and/or a sandboxed parametric-script tool; an open VLM (CAD-Coder, **Florence-2 for GD&T**, arxiv.org/abs/2411.03707) keeps the visual tier off frontier APIs.
- **MUSE (arxiv.org/html/2605.28579) + CadBench (2605.10873):** grade manufacturability/functionality, not just shape; standard metrics = volumetric/surface IoU, Chamfer, **Invalidity Ratio**. → Replace `validator.py`'s regex/title heuristics with a staged code→geometric→rubric-judge protocol; emit a single scalar "drawing score" for regression tests (the repo has *no* quantitative quality metric).
### 7b. Competitive landscape
| Product | 3D | Render-critique loop | Validation gate | HTTP auth | Code escape hatch |
|---|---|---|---|---|---|
| **AutoCAD MCP Pro** | ❌ 2D only | ⚠️ finalize-only, symbolic | ✅ **unique** (critique+validator+ISO) | ❌ none | ⚠️ COM-only run_command/lisp |
| build123d-mcp/agentcad (github.com/pzfreo/build123d-mcp) | ✅ | ✅ in-loop | ❌ | — | ✅ |
| FreeCAD Robust MCP (150+ tools) | ✅ + FEM | ⚠️ | ❌ | — | ✅ |
| Blender MCP (~22k★) | ✅ | ✅ | ❌ | — | ✅ Python exec |
| RhinoMCP | ✅ | ✅ viewport | ❌ | — | ✅ |
| Fusion360/Onshape MCP | ✅ parametric | — | ❌ | ✅ OAuth/keys | — |
Snyk's 9-CAD-MCP roundup (snyk.io/articles/9-mcp-servers-for-computer-aided-drafting-cad-with-ai/): **vision only in FreeCAD/Blender/Rhino; design validation in none; HTTP auth only in Onshape/Fusion.** → **Our moat is the design-rule validation gate; the field beat us on 3D, in-loop render-critique, and auth.** Autodesk's own in-AutoCAD assistant demo (aecmag.com/features/autodesk-shows-its-ai-hand/) flagship = **automated compliance checking** — exactly our niche, so we must make standards compliance the sharpest, most-cited capability. Adam/Zoo (techcrunch.com/2025/10/31/...) set the UX bar: **parametric editability + post-gen dimension sliders + entity-level conversational editing**.
### 7c. Under-used COM / ezdxf API + ISO standards
**COM (ActiveX) gaps (every create call hits ModelSpace, com_backend.py:125-127):**
- **PaperSpace/Layouts + Plot config** — `drawing_export_pdf` (com_backend.py:528-534) calls bare `Plot.PlotToFile(path,'DWG To PDF.pc3')` with no page setup → ignores the A3/scale `drawing_plan` promised. Add `layout_*`/`viewport_*` (AddPViewport, StandardScale, CanonicalMediaName). (help.autodesk.com ActiveX ref)
- **AddTable** (BOM/revision tables), **AddMLeader** (ISO leader notes — CLAUDE.md *reserves* "leader notes" but no tool creates them), **Fields** (auto-updating title block), **GetDynamicBlockProperties** (parametric symbol libraries — `block_insert` only does attribute text, com:1195-1251), **DimStyles.Add+CopyFrom** (no ISO-129 dimstyle is ever set → undercuts the "Dimensions are ISO 129" rule), **named UCS** (per-view frames for `view_count`), **AttachExternalReference** (shared borders), **gradient/island hatch**.
- **3D solids/regions/booleans** — `system_status` advertises `all_entity_types` (com:1443) which is **false**; either implement `solid_*` or correct the claim.
- **Push selection into AutoCAD:** `entity_list` (com:967) and `analysis_select_by_type` (com:1352) loop ModelSpace.Item(i) in Python (O(n) COM round-trips — the #1 perf killer); `analysis_select_by_layer` (com:1339) already shows the right `SelectionSet.SelectAll` group-code-filter pattern to copy.
**ezdxf gaps (Grep confirms zero use of `set_gradient/set_solid_fill/paperspace/add_viewport/odafc/export_dwg/groupby/dimstyles/MTextEditor/add_ordinate/qsave`):**
- **`addons.odafc.export_dwg`** = real DWG (R12–R2018) → fixes R11/R10 (drawing_save_as fake DWG). (ezdxf.mozman.at/docs/addons/odafc.html)
- **drawing-addon `Configuration`** (lineweight_policy ABSOLUTE, background_policy, SVGBackend/PyMuPdfBackend) → true ISO lineweights + vector output, replacing raw matplotlib (ezdxf:357-379,1400-1424).
- **`query()` mini-language + `groupby()`** → replace hand-rolled loops in analysis_stats/select/entity_select_smart (which pulls 5000 entities then filters in memory, ezdxf:2008-2050).
- **`recover.readfile` + `Auditor.fix`** → real repair; `drawing_audit` (ezdxf:448) reports but never fixes; `drawing_open` (ezdxf:309) has no recover fallback.
- **`MTextEditor`** (stacked tolerance fractions), **Hatch gradients/islands/edge-paths**, **DimStyle overrides** (no ISO dimstyle today, ezdxf:668-774), **path-module offset** (generalizes LINE/CIRCLE-only offset + LINE-only corner ops).
**ISO standards conformance:**
- ✅ **ISO 128-2** lineweight set encoded (plan_spec.py:85, verified) — but **nulled by R2** and group-vs-element-class not checked.
- ⚠️ **ISO 7200** title block — A3-only hardcoded (titleblock.py:16-17), informal labels ("DWG NO" vs ISO field names), len×0.3 centering heuristic (titleblock.py:171), missing "Document type"/"Date of issue".
- ❌ **ISO 129-1 dimensioning** — biggest functional gap: `dimension_*` (base.py:226-257) have **no** param for ±/limit/fit (H7/g6) tolerances or text override → toleranced production dims impossible. (iso.org/obp/ui/#iso:std:iso:129)
- ❌ **ISO 1101 / ASME Y14.5 GD&T** — entirely absent (grep-confirmed): no feature control frame, datum, or ISO 2768 general-tolerance note. (gdandtbasics.com/iso-vs-asme-standards/)
- ⚠️ **ISO 13567** — static mech-only starter list (layers.py:51-62), no field-grammar parser/validator.
---
## 8. "What Would Make This the BEST AutoCAD MCP" — Prioritized Thesis
The evidence converges: **the repo is architecturally ahead on dual-backend + standards-validation but behind on the closed-loop, the production-output plumbing, and the very workflow it markets.** Priorities, evidence-backed:
**P0 — Make the marketed product actually work on its default platform.**
1. **Implement the 8 premium meta-tools on COM** (R1, com_backend.py:1637-1660). Today the CLAUDE.md-mandatory workflow 500s on every Windows install. Nothing else matters until this ships.
2. **Fix the lineweight `int()` truncation** (R2, ezdxf_backend.py:1034 → multiply mm by 100 / map to ACAD enum). This single bug nullifies the ISO-128 system *and* makes the iso128 critique a permanent no-op (R3). The standards-validation gate is the **competitive moat** (Snyk roundup; Autodesk's own flagship demo) — it must be real.
3. **Add CI + a coverage gate + the first COM smoke tests** (R6, ci.yml deleted; pytest-cov unconfigured). Restore the LICENSE file (legal drift).
**P1 — Own the moat by closing the loop (the #1 accuracy lever in every 2025–26 paper).**
4. **Turn `drawing_critique` into a routed iterative Refiner loop** with per-`Issue` repair hints and a bounded budget (CADSmith Refiner; FEA-agent +13.4pp/round; Physics-in-the-Loop 3.3–6.0 iters converge). The `Issue` dataclass already carries handles+detail.
5. **Promote the finalize screenshot to a first-class `visual` critique focus fed to a VLM judge** (CADSmith: dropping the render → 35× worse; CADFusion). Use an **open local VLM** (CAD-Coder / Florence-2 for GD&T) behind a `[vision]` extra so the headless path is unaffected. Add a **dimension round-trip check** (VLM-read dims vs created `dimension_*` entities) to catch what proximity-only `dim_overlap` (R4) cannot.
6. **Add a ProCAD-style pre-plan clarification pass to `drawing_plan`** (invalidity 14.6%→0.9%): detect missing/conflicting dims, material, tolerance, view_count-vs-intent before any geometry.
7. **Emit a scalar "drawing score" / Invalidity-Ratio** (MUSE/CadBench) so the premium workflow is regression-testable — the repo has no quantitative accuracy metric.
**P2 — Reach production-drawing parity (what un-toleranced ISO 128 can't deliver).**
8. **ISO 129 tolerances + ISO 1101/Y14.5 GD&T**: add ±/limit/fit params to `dimension_*` and a `gd_frame(symbol, tol, datums, modifiers)` + datum/ISO-2768-note primitive. This is the categorical gap blocking real part drawings; no surveyed text-to-CAD product covers 2D GD&T — a differentiator.
9. **Real plotted sheets**: COM Layout/PaperSpace/Plot-config so `drawing_plan(sheet_size, scale)` and `titleblock_apply_iso_a3` produce a correct A-series plot; on ezdxf wire `odafc.export_dwg` (kills the fake-DWG R11) and the drawing-addon `Configuration` for true-lineweight SVG/PDF.
10. **An ISO-129 `DimStyle`** created once and applied to all dims (COM `DimStyles.Add+CopyFrom`; ezdxf dimstyle overrides) so "Dimensions are ISO 129" becomes true, and **MLEADER** for the leader notes the docs already reserve.
**P3 — Catch the field on breadth (table-stakes elsewhere, absent here).**
11. **A 3D solids tier** (extrude/revolve from 2D profiles + booleans on COM; STEP/STL on ezdxf) — *or* correct the false `all_entity_types` claim (com:1443). Every competitor has 3D.
12. **Parametric editability + entity-level conversational editing** (Adam/Zoo): capture driving dims in PlanSpec, expose a re-drive tool; "select this circle → make it the 20mm bore" via existing handle model + `entity_select_smart`/`point_from_snap`.
13. **Authenticated remote HTTP** (API-key middleware) given `dangerous_commands_enabled` exists (server.py:111); a **standard-parts/symbol library** (`library_insert` over `block_insert`, dynamic-block props); **NL→B-Rep grounding** (FutureCAD) over the brittle trim/fillet/chamfer keep-point API.
**P4 — Hygiene that compounds with the above.** Generalize LINE-only corner ops + LINE/CIRCLE-only offset via the ezdxf path module; replace hand-rolled selection with `query()`/`groupby()` (ezdxf) and `SelectionSet.SelectAll` filters (COM); fix `entity_array_polar` 360° dup (R9), `dimension_angular` tx/ty (R12), the COM thread-leak `CoUninitialize` (R5); delete dead `section.py`/`generate_tooth_profile`; reconcile `system_about` catalog (R15) and the stale README/CLAUDE.md drift (§5).
**Files central to every priority:** `backends/com_backend.py:1637-1660` (P0.1), `backends/ezdxf_backend.py:1034` + `engineering/layers.py:14-26` (P0.2), `engineering/critique.py:37-64,194-197` (P0.2/P1.5), `engineering/plan_spec.py:64-87` (P1.4/P1.6/P2.8), `backends/base.py:226-257` (P2.8), `server.py:528-534/2294-2309` (P2.9).