Files
autocad-mcp-pro/pyproject.toml
T
Umut CANandClaude Opus 5 b03a7615b1 release: v1.5.0 — discovery layer, honesty debt closed, Wave A
Tool count 131 -> 154; 474 -> 1242 collected tests.

The release exists for two measured reasons. A 149-tool catalog costs a client
~40,300 tokens before it has asked for anything, and a drafter searching the way
drafters think -- FILLET, BPOLY, QSELECT, WBLOCK -- scored df=0 against the stock
index, because those words were simply not in the data.

Discovery and batching
- DISCOVERY_MODE=search replaces the catalog with search_tools + call_tool,
  ranked over an AutoCAD command and synonym corpus (discovery/aliases.py) on
  top of fastmcp's BM25 index. Measured: 40,305 -> 356 tokens advertised.
- cad_batch runs a step list in one round trip; fields= projects result-heavy
  responses. The `core` profile is gone -- discovery replaced it.

Honesty debt (Tier 0)
- OCS/ECS: `extrusion` appeared nowhere in this codebase, so mirrored and
  rotated geometry reported coordinates that were quietly wrong. backends/ocs.py
  translates at the backend boundary; every tool coordinate is WCS on both
  engines, and a tilted plane refuses rather than answering with a number that
  looks fine.
- drawing_save_as(fmt="dwg") wrote DXF bytes to a .dwg path. drawing_audit never
  repaired. analysis_measure_area shoelaced points the caller typed in.
- ezdxf calls gained a per-call timeout, so one hung call can no longer wedge a
  server whose document lock is a single asyncio.Lock.

Architecture
- The 1854-line backend ABC is 19 contract modules; @capability(key, reason=)
  makes optional methods declarative and a gate fails on undeclared holes.
- 8 layout/viewport tools, CHSPACE, CAD_PROGID escape hatch.
- Wave A: selection filters, boundary tracing, hatch depth, annotation objects,
  measurement -- 13 tools of a planned 39. The REGION and 2D-boolean family was
  cut on evidence: add_region() yields zero ACIS bytes and the greiner_hormann
  substitute loses 28.2% of the area on a square with one semicircular edge.

Verified against a live AutoCAD 2026, which found four COM defects no amount of
reading would have caught -- AcadPViewport has no ViewCenter, and the line that
set it had been swallowed by a bare except since v1.4.

Benchmark v3 (15 tasks) and what it caught
- v2 scored this server 10/10, which carried no information: every task in it
  exercised something the server was built around. Five tasks were added because
  they can fail, and three did while being written -- a hatch could not be
  measured though the refusal listed HATCH as measurable, the boundary tools and
  the measure tool disagreed about the same shape (139.2177 vs 139.2699), and a
  circle stored as two bulged vertices measured 0.0.
- Competitor reports are NOT re-scored. They are pinned v1.4 runs against v2 and
  were never asked these five questions; the chart shows those rows `not run`.
- Correctness A/B: 21/24 -> 24/24, zero regressions.

Performance, stated plainly
The published v1.4 perf report was recorded on CPython 3.14 and the new one on
3.11.15, so the apparent 2-6x speedup is the interpreter, not this release.
Re-measured on one interpreter, v1.5.0 is 28% slower creating entities and 22%
slower on the 10k roundtrip. Fully attributed: EZDXF_CALL_TIMEOUT=0 restores
v1.4.0's exact numbers. That is the deadlock fix being paid for, the knob is
documented, and winning it back is on the 1.6 roadmap.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 09:45:20 +03:00

147 lines
5.1 KiB
TOML

[project]
name = "autocad-mcp-pro"
version = "1.5.0"
description = "Production-grade AutoCAD MCP server — COM API + ezdxf dual engine"
readme = "README.md"
requires-python = ">=3.11"
license = "MIT"
license-files = ["LICENSE"]
authors = [
{ name = "Umutcan Edizsalan" },
]
keywords = [
"autocad",
"mcp",
"model-context-protocol",
"cad",
"dxf",
"ezdxf",
"drafting",
"engineering-drawing",
]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"Intended Audience :: Manufacturing",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Topic :: Scientific/Engineering",
"Topic :: Software Development :: Libraries :: Python Modules",
]
dependencies = [
# These ranges are what an *installer* gets from PyPI, so they stay ranges:
# a library that pins its dependencies exactly is unco-installable. They are
# deliberately narrow because a range is all a `pip install` has to protect
# it. The floor of each range is the version this release is tested against.
#
# What CI and contributors get is different and exact: uv.lock pins the
# whole transitive graph (including the dev tools) to the set the suite was
# last run against, and every locked lane installs with `--locked` so an
# untested upstream cannot appear silently. Ranges guard users; the lock
# guards us. Regenerate the lock with `uv lock` whenever these change.
#
# Every ceiling is the next major. A tighter cap (e.g. `<3.5`) would block a
# user whose environment already holds a newer minor from installing us at
# all, and it buys nothing here: the lock is what protects our own runs, and
# `deps-floating.yml` runs the suite against unpinned latest on a schedule,
# so a breaking upstream minor surfaces as a failing scheduled job rather
# than as a silent CI-green-but-users-broken state.
#
# fastmcp 3.x moves fast and `fastmcp` is now a thin meta-package
# (Requires-Dist: fastmcp-slim[client,server]), so a new minor can reshape
# the API we build on. If deps-floating goes red, add a targeted `!=` for
# the bad version rather than lowering this ceiling.
"fastmcp>=3.4.5,<4",
"ezdxf>=1.4,<2",
"pydantic>=2.0,<3",
]
[project.urls]
Homepage = "https://github.com/U-C4N/Autocad-MCP"
Repository = "https://github.com/U-C4N/Autocad-MCP"
Issues = "https://github.com/U-C4N/Autocad-MCP/issues"
Changelog = "https://github.com/U-C4N/Autocad-MCP/blob/main/CHANGELOG.md"
[project.optional-dependencies]
# Rendering needs an extra — the core install cannot draw pixels.
# ezdxf.addons.drawing.frontend does an unconditional top-level `import
# PIL.Image`, so Pillow is required by *every* ezdxf render path (PNG
# screenshot and PDF export alike), not only by the COM window-capture path
# it is listed under below. `pip install autocad-mcp-pro` on its own therefore
# renders nothing. Install `[pdf]` (or `[full]`) for rendering on any platform:
# Pillow comes in transitively as a matplotlib dependency.
com = [
"pywin32>=306",
# Win32 window capture for the live-AutoCAD screenshot path.
"Pillow>=10.0",
]
pdf = [
# Also satisfies ezdxf's hard Pillow requirement, transitively.
"matplotlib>=3.8",
]
full = [
"pywin32>=306",
"Pillow>=10.0",
"matplotlib>=3.8",
]
[project.scripts]
autocad-mcp = "server:main"
# Alias matching the PyPI distribution name so `uvx autocad-mcp-pro` works
# without a --from indirection (MCP registry / uvx convention).
autocad-mcp-pro = "server:main"
# PEP 735 dependency group (was [tool.uv] dev-dependencies, which uv now warns
# is deprecated). The standard spelling is understood by uv *and* by pip >=25.1
# (`pip install --group dev`), so the dev toolchain has one definition whether
# or not you use uv.
#
# These are the exact gates CI runs, and uv.lock freezes them: `uv sync
# --locked` reproduces this list bit-for-bit, so a lint or test job can no
# longer be broken by a tool release nobody chose.
[dependency-groups]
dev = [
"pytest>=8.0",
"pytest-asyncio>=0.23",
"pytest-cov>=5.0",
# Exact pin *and* locked: the formatter's output changes between minors, so
# a floating ruff makes `ruff format --check .` fail on unchanged code. This
# is now the only place the version is written — the workflows install ruff
# from the lock rather than restating it.
"ruff==0.16.1",
]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel]
# Flat layout: no directory matches the project name, so hatchling needs an
# explicit file selection (GH issue #2). server.py imports config + security.
only-include = [
"server.py",
"config.py",
"security.py",
"version.py",
"backends",
"engineering",
"discovery",
]
[tool.ruff]
line-length = 100
target-version = "py311"
[tool.ruff.lint]
select = ["E", "F", "I", "W", "UP", "B"]
ignore = ["E501"]
[tool.ruff.format]
quote-style = "double"
[tool.pytest.ini_options]
asyncio_mode = "strict"