Content
# AutoCAD MCP Server
MCP server for AutoCAD 2026 / AutoCAD LT automation, headless DXF generation, and read-only
querying of AutoCAD Plant 3D projects. Developed for IDEA IT (Ingeniería y Diseño Estructural
Avanzado, S.L.).
Two backends, one API:
| Backend | Runtime | Requires AutoCAD? | Screenshot |
|---------|---------|-------------------|------------|
| **File IPC** | Windows Python | Yes — AutoCAD LT 2024+ / AutoCAD 2026 (Windows) | Win32 PrintWindow |
| **ezdxf** | Any platform | No (headless) | matplotlib render |
The server exposes **9 consolidated tools** (`drawing`, `entity`, `layer`, `block`, `annotation`, `pid`, `view`, `system`, `plant3d`) over the MCP stdio transport. An MCP client (Claude Desktop, Claude Code, etc.) connects and drives AutoCAD through natural-language requests.
The `plant3d` tool queries Plant 3D project databases (`.dcf` SQLite files) directly in read-only mode — no .NET plugin or open AutoCAD session required for most operations.
## Prerequisites (File IPC backend)
- **Windows 10/11** (the File IPC backend uses Win32 APIs for focus-free window messaging)
- **AutoCAD LT 2024 or newer** — AutoLISP support was added in LT 2024 for Windows. AutoCAD LT for Mac exists but does **not** support AutoLISP.
- **Python 3.10+** (Windows native — not WSL Python)
- **uv** package manager ([install guide](https://docs.astral.sh/uv/getting-started/installation/))
> The ezdxf headless backend works on any platform (Linux, macOS, WSL) for offline DXF generation without AutoCAD installed.
## Quick Start
### 1. Clone and install
```powershell
git clone https://github.com/Romeraaa/autocadplant3d-mcp.git
cd autocadplant3d-mcp
uv sync
```
### 2. Load the LISP dispatcher in AutoCAD LT
Open AutoCAD LT and load `mcp_dispatch.lsp` using **APPLOAD**:
1. Type `APPLOAD` in the AutoCAD command line
2. Browse to `<repo>/lisp-code/mcp_dispatch.lsp`
3. Click **Load**
4. You should see: `=== MCP Dispatch v3.1 loaded ===` and `Ready for commands via (c:mcp-dispatch)`
> **Tip:** Add the file to your AutoCAD Startup Suite (in the APPLOAD dialog) so it loads automatically with every drawing.
### 3. Configure your MCP client
Add to your MCP client configuration (e.g. Claude Desktop `claude_desktop_config.json`):
```json
{
"mcpServers": {
"autocad-mcp": {
"command": "C:\\path\\to\\autocadplant3d-mcp\\.venv\\Scripts\\python.exe",
"args": ["-m", "autocad_mcp"],
"env": { "AUTOCAD_MCP_BACKEND": "auto" }
}
}
}
```
**Key points:**
- The `command` must point to the **Windows Python** inside the project venv (not WSL python).
- `AUTOCAD_MCP_BACKEND` can be `auto` (default — tries File IPC, falls back to ezdxf), `file_ipc` (requires AutoCAD), or `ezdxf` (headless only).
#### Running from WSL
If your MCP client runs in WSL (e.g. Claude Code), launch the server through `cmd.exe` so it runs as a native Windows process:
```json
{
"mcpServers": {
"autocad-mcp": {
"type": "stdio",
"command": "cmd.exe",
"args": ["/d", "/s", "/c", "cd /d C:\\path\\to\\autocadplant3d-mcp && .venv\\Scripts\\python.exe -m autocad_mcp"],
"env": { "AUTOCAD_MCP_BACKEND": "auto" }
}
}
}
```
### 4. Verify
From your MCP client, call:
```
system(operation="status")
```
You should see `backend: "file_ipc"` if AutoCAD is running, or `backend: "ezdxf"` for headless mode.
## Tools
### `drawing` — File/drawing management
| Operation | Description | File IPC | ezdxf |
|-----------|-------------|----------|-------|
| `create` | Reset to clean drawing (erase all + purge) | Yes | Yes |
| `open` | Open an existing drawing | Yes | Yes (DXF) |
| `info` | Get entity count and layers | Yes | Yes |
| `save` | Save current drawing (to path if given) | Yes | Yes |
| `save_as_dxf` | Export as DXF | Yes | Yes |
| `plot_pdf` | Plot to PDF | Yes | No |
| `purge` | Purge unused objects | Yes | Yes |
| `get_variables` | Get system variables by name | Yes | Yes |
| `undo` | Undo last operation | Yes | No |
| `redo` | Redo last undone operation | Yes | No |
### `entity` — Entity CRUD + modification
**Create:** `create_line`, `create_circle`, `create_polyline`, `create_rectangle`, `create_arc`, `create_ellipse`, `create_mtext`, `create_hatch`
**Read:** `list`, `count`, `get`
**Modify:** `copy`, `move`, `rotate`, `scale`, `mirror`, `offset`\*, `array`, `fillet`\*, `chamfer`\*, `erase`
> \* `offset`, `fillet`, `chamfer` are File IPC only (not supported in ezdxf headless backend).
### `layer` — Layer management
`list`, `create`, `set_current`, `set_properties`, `freeze`, `thaw`, `lock`, `unlock`
### `block` — Block operations
| Operation | File IPC | ezdxf |
|-----------|----------|-------|
| `list` | Yes | Yes |
| `insert` | Yes | Yes |
| `insert_with_attributes` | Yes | Yes |
| `get_attributes` | Yes | Yes |
| `update_attribute` | Yes | Yes |
| `define` | No | Yes |
### `annotation` — Text, dimensions, leaders
`create_text`, `create_dimension_linear`, `create_dimension_aligned`, `create_dimension_angular`, `create_dimension_radius`, `create_leader`
### `pid` — P&ID operations (CTO symbol library)
`setup_layers`, `insert_symbol`, `list_symbols`, `draw_process_line`, `connect_equipment`, `add_flow_arrow`, `add_equipment_tag`, `add_line_number`, `insert_valve`, `insert_instrument`, `insert_pump`, `insert_tank`
> P&ID symbol insertion requires the [CAD Tools Online](https://www.cadtoolsonline.com/) (CTO) P&ID Symbol Library installed at `C:\PIDv4-CTO\`. The ezdxf backend has built-in CTO library support. For the File IPC backend, some P&ID operations require additional LISP helpers — see the P&ID section in the wiki for setup details.
### `view` — Viewport and screenshot
| Operation | Description |
|-----------|-------------|
| `zoom_extents` | Zoom to show all entities |
| `zoom_window` | Zoom to a specified window |
| `get_screenshot` | Capture current AutoCAD view as PNG |
Screenshots use `PrintWindow` (Win32) for the File IPC backend — works even when AutoCAD is minimized or in the background. The ezdxf backend renders via matplotlib.
### `system` — Server management
`status`, `health`, `get_backend`, `runtime`, `init`, `execute_lisp`
> **Warning:** `execute_lisp` runs arbitrary AutoLISP code (File IPC only). Pass `data: {code: "(+ 1 2)"}`. However, on AutoCAD 2026 it triggers the APPLOAD dialog and blocks the thread — **do not use it**; use the native MCP operations instead.
### `plant3d` — Plant 3D project queries (read-only)
| Operation | Description |
|-----------|-------------|
| `detect_project` | Resolve the Plant 3D project currently open in AutoCAD (via `DWGPREFIX`) or by explicit path/name. |
| `list_projects` | List Plant 3D projects found under `AUTOCAD_MCP_PLANT3D_ROOT`. |
| `line_summary` | Per-line component summary for a given project: counts, specs, and diameters per `LineNumberTag`. |
| `find_untagged` | Piping components without a valid `LineNumberTag` (NULL / empty / `?`), broken down by class (`PartCategory`) and spec. |
| `validate_specs` | Spec-consistency checks: Spec vs. Required Spec, mixed specs per line, empty/ghost specs, material/schedule outside the spec catalogue. |
| `list_lines` | The project line list — one row per line number with service, spec, size, insulation, and model DWGs. |
| `list_components` | Generic piping component list (`PipeRunComponent` ⨝ `EngineeringItems`) with optional filters: `classes` (pipe/valve/fitting/flange/instrument/support), `line`, `spec`, `size` (requires unit). Returns `pnpid`, `class`, `tag`, `description`, `spec`, `size`, `line` per component, plus `by_class` summary and `omitted` count. |
| `list_valves` | Read-only preset of `list_components` with class fixed to valve (`classes=["valve"]`). Any `classes` argument supplied by the caller is overridden. Remaining filters (`line`, `spec`, `size`, `limit`) and output format are identical to `list_components`. |
| `list_instruments` | Read-only preset of `list_components` with class fixed to instrument (`classes=["instrument"]`, maps to `PartCategory="Instruments"`). Any `classes` argument supplied by the caller is overridden. Remaining filters (`line`, `spec`, `size`, `limit`) and output format are identical to `list_components`. |
| `bom` | Bill of Materials — aggregates `list_components` output, grouping by **(class, spec, size, description)**; each distinct combination becomes one BOM line with its `quantity` (component count, not lengths). Accepts the same scope filters as `list_components` (`classes`, `line`, `spec`, `size`, `limit`). Output: `{ok, project, path, limit, filters, total_components, line_count, omitted, by_class, bom, notes}`; each BOM line: `{class, spec, size, description, quantity}`. Issues no SQL of its own — read-only aggregation over `PipeRunComponent ⨝ EngineeringItems`. |
| `pipe_length` | Real pipe lengths — reads the `Pipe` table (`Length` column, unit from `LengthUnit`) joined to `PipeRunComponent` and `EngineeringItems`; only `PartCategory='Pipe'` rows. Groups by `group_by` (`"line"` default \| `"spec"` \| `"size"`). Scope filters: `line`, `spec`, `size` (`{"value","unit"}`), `limit` (default 50, 0 = unlimited; limits groups, reports `omitted`). Untagged pipe (NULL/empty/`?` `LineNumberTag`) always reported in `untagged {pipe_count, length}` and as group `"(SIN LÍNEA)"` when `group_by="line"`. Output: `{ok, project, path, limit, group_by, filters, length_unit, total_pipe_count, total_length, untagged, group_count, omitted, groups, notes}`; each group: `{group, pipe_count, length, length_unit}`. Lengths rounded to 2 decimal places. Degrades gracefully if the `Pipe` table or `Length`/`LengthUnit` columns are absent. |
| `weld_list` | Weld count and breakdown — queries the three dedicated weld tables (`Buttweld`, `Socketweld`, `TapWeld`); weld subtype derives from the source table. Each weld is joined to `EngineeringItems` (diameter/spec) and the line resolved via `P3dLineGroupPartRelationship` → `P3dLineGroup.Tag` (~97% coverage). `Shop_Field` is populated (SHOP/FIELD) → shop-vs-field breakdown always present. `WeldNumber` is 100% NULL (isometric numbering not yet assigned) — the tool counts and breaks down, it does not number. Parameters: `group_by` (`"line"` default \| `"size"` \| `"spec"` \| `"shop_field"` \| `"type"`); filters `line`, `spec`, `size` (`{"value","unit"}`), `shop_field` (`"shop"` \| `"field"`), `weld_type` (`"butt"` \| `"socket"` \| `"tap"`), `limit` (default 50, 0 = unlimited; limits groups, reports `omitted`). Output: `{ok, project, path, limit, group_by, filters, total_welds, by_type[], by_shop_field[], untagged{weld_count}, group_count, omitted, groups[], notes}`. `by_type` and `by_shop_field` are global breakdowns always present (descending order). Welds without a valid line appear in `untagged` and, when `group_by="line"`, also as group `"(SIN LÍNEA)"`. Degrades gracefully if the three tables, `Shop_Field`, or the line-relation tables/columns are absent. |
| `bolt_gasket_list` | Bolt and gasket list (flange assembly material) — queries two dedicated tables in `Piping.dcf`: `BoltSet` (bolt sets; columns `BoltSize`, `NumberInSet`, `BoltCompatibleStd`, `Shop_Field`) and `Gasket`. Ignores the generic `Fasteners` table (unreliable). Each item is joined to `EngineeringItems` (Spec, NominalDiameter/NominalUnit — flange diameter in in/mm, not collapsed; Material) and the line resolved via `P3dLineGroupPartRelationship` → `P3dLineGroup.Tag` (~80% coverage; ~20% untagged is legitimate for branch-head flanges). Dual quantity metrics: `item_count` (rows: sets + gaskets), `bolt_sets`, `individual_bolts` (Σ `NumberInSet`, parsed from text and exposed as int; non-numeric `NumberInSet` → 0 with a note), `gaskets` (each gasket = 1). Parameters: `group_by` (`"line"` default \| `"size"` \| `"spec"` \| `"material"` \| `"item_type"` \| `"shop_field"` \| `"bolt_size"`); filters `item_type` (`"bolt"` \| `"gasket"`), `line`, `spec`, `size` (`{"value","unit"}`), `shop_field` (`"shop"` \| `"field"`), `limit` (default 50, 0 = unlimited; limits groups, reports `omitted`). Output: `{ok, project, path, limit, group_by, filters, totals{item_count, bolt_sets, individual_bolts, gaskets}, by_item_type[], by_shop_field[], untagged{...}, group_count, omitted, groups[], notes}`. `by_item_type` and `by_shop_field` are global breakdowns always present. Items without a valid line appear in `untagged` and, when `group_by="line"`, also as group `"(SIN LÍNEA)"`. Degrades gracefully if `BoltSet`/`Gasket` tables, optional columns (`NumberInSet`, `BoltSize`, `Shop_Field`), or the line-relation tables/columns are absent. |
> These operations read the Plant 3D project databases (`.dcf` SQLite files) and spec catalogues (`Spec Sheets\*.pspc`, also SQLite) directly, in **read-only mode** (`mode=ro`). They **do not require the .NET plugin or an open AutoCAD session** — except `detect_project`, which reads `DWGPREFIX` from the active drawing when no project path is supplied. The `plant3d` tool **never modifies the project**.
>
> Set the optional env var `AUTOCAD_MCP_PLANT3D_ROOT` to a folder containing Plant 3D project subfolders; this lets `list_projects` and `detect_project` resolve projects by name instead of full path.
## Architecture
```
MCP Client (Claude)
│ stdio (JSON-RPC)
▼
Python MCP Server (autocad_mcp)
│
├── File IPC Backend ──► C:/temp/*.json ──► mcp_dispatch.lsp (AutoCAD LT)
│ PostMessageW(WM_CHAR) to MDIClient — no focus steal
│
└── ezdxf Backend ──► in-memory DXF (headless, no AutoCAD needed)
```
The File IPC backend sends keystrokes to AutoCAD's MDIClient window via `PostMessageW(WM_CHAR)`, triggering the `(c:mcp-dispatch)` AutoLISP command. This approach does **not** steal window focus — you can continue working in other applications while automation runs.
## Environment Variables
| Variable | Default | Description |
|----------|---------|-------------|
| `AUTOCAD_MCP_BACKEND` | `auto` | Backend selection: `auto`, `file_ipc`, `ezdxf` |
| `AUTOCAD_MCP_IPC_DIR` | `C:/temp` | Directory for IPC command/result JSON files (must match on both Python and LISP sides) |
| `AUTOCAD_MCP_IPC_TIMEOUT` | `10.0` | IPC command timeout in seconds (1-300) |
| `AUTOCAD_MCP_ONLY_TEXT` | `false` | Disable screenshot capture (text feedback only) |
| `AUTOCAD_MCP_PLANT3D_ROOT` | | Optional root folder containing Plant 3D project subfolders; lets the `plant3d` tool resolve projects by name instead of full path. |
> **Note:** If you change `AUTOCAD_MCP_IPC_DIR`, you must also update the `*mcp-ipc-dir*` variable in `mcp_dispatch.lsp` to match.
## Development
```powershell
uv sync
uv run pytest tests/ -v
```
## AutoCAD LT AutoLISP Compatibility
AutoLISP was added to AutoCAD LT in the **2024 release (Windows only)**. AutoCAD LT for Mac does not support AutoLISP.
| Supported (LT 2024+ Windows) | Not Supported |
|-------------------------------|---------------|
| `.lsp` / `.fas` / `.vlx` / `.dcl` | VLIDE (Visual LISP IDE) |
| All `vl-*` utility functions | `vlax-*` (ActiveX/COM) |
| File I/O (`open`, `read-line`, etc.) | Express Tools |
| Entity access (`entget`, `entmod`, etc.) | 3D operations |
| Selection sets | AutoLISP on Mac |
The `mcp_dispatch.lsp` dispatcher is fully compatible with LT 2024+.
## What's New — Plant 3D query tools
The `plant3d` tool adds read-only querying of AutoCAD Plant 3D projects directly from the
project's SQLite databases, with no .NET plugin and no AutoCAD session required:
- **`find_untagged`** — identify piping components without a valid `LineNumberTag`, broken down by class and spec.
- **`validate_specs`** — four spec-consistency checks (Spec vs. Required Spec, mixed specs per line, ghost specs, material/schedule outside catalogue).
- **`list_lines`** — generate the project line list (service, spec, size, insulation, model DWGs) using a hybrid query over `P3dLineGroup` + `EngineeringItems`.
- **`list_components`** — generic piping component list with optional filters by class, line, spec and size; includes `by_class` breakdown and `omitted` count.
- **`list_valves`** — read-only preset of `list_components` with class fixed to valve; same filters and output as `list_components`.
- **`list_instruments`** — read-only preset of `list_components` with class fixed to instrument; same filters and output as `list_components`.
- **`bom`** — Bill of Materials: aggregates `list_components` by (class, spec, size, description) and returns one BOM line per distinct combination with its `quantity` (component count). Accepts the same scope filters (`classes`, `line`, `spec`, `size`, `limit`).
- **`pipe_length`** — real pipe lengths from the `Pipe` table (`Length` column, unit from `LengthUnit`), grouped by line/spec/size. Untagged pipe always surfaced in `untagged` field. Accepts filters `line`, `spec`, `size`, `limit`.
- **`weld_list`** — weld count and breakdown from `Buttweld`, `Socketweld` and `TapWeld` tables; subtype from source table, line resolved via `P3dLineGroupPartRelationship`. Shop-vs-field breakdown always present (`Shop_Field` populated). Groups by `group_by` (`"line"` \| `"size"` \| `"spec"` \| `"shop_field"` \| `"type"`). Accepts filters `line`, `spec`, `size`, `shop_field`, `weld_type`, `limit`. Untagged welds surfaced in `untagged` field.
- **`bolt_gasket_list`** — bolt and gasket list (flange assembly material) from `BoltSet` and `Gasket` tables; line resolved via `P3dLineGroupPartRelationship` (~80% coverage). Dual quantity metrics: `bolt_sets`, `individual_bolts` (Σ `NumberInSet`), `gaskets`. Groups by `group_by` (`"line"` \| `"size"` \| `"spec"` \| `"material"` \| `"item_type"` \| `"shop_field"` \| `"bolt_size"`). Accepts filters `item_type`, `line`, `spec`, `size`, `shop_field`, `limit`. Global breakdowns `by_item_type` and `by_shop_field` always present. Untagged items surfaced in `untagged` field.
- **`line_summary`**, **`detect_project`**, **`list_projects`** — project navigation and per-line breakdowns.
All operations open `.dcf` and `.pspc` files with `mode=ro` and never write back to the project.
## What's New in v3.1
- **`execute_lisp`** — Run arbitrary AutoLISP code via temp file pattern. Turns the server from a fixed command set into an extensible automation platform.
- **Undo / Redo** — Single-step undo and redo via `drawing` tool.
- **Drawing open** — Open existing `.dwg` files programmatically (FILEDIA suppressed).
- **Drawing create** — Now resets current drawing (erase all + purge) instead of `_.NEW`, preserving the LISP dispatcher namespace.
- **Drawing save with path** — `save` with a `path` parameter uses SAVEAS; without path uses QSAVE.
- **`get_variables` fix** — Respects the `names` parameter; returns requested variables with proper type handling.
- **Polyline/leader fix** — Point arrays properly encoded via semicolon-delimited format.
- **ESC prefix** — Sends 2x ESC before each dispatch to cancel stale pending commands from prior timeouts.
- **UTF-8/cp1252 fallback** — Handles non-ASCII characters in LISP result files (AutoCAD writes Windows-1252).
- **Configurable IPC timeout** — `AUTOCAD_MCP_IPC_TIMEOUT` env var (1–300 seconds, default 10).
- **Thread-safe backend init** — `asyncio.Lock` prevents parallel initialization races.
## License
MIT
MCP Config
Below is the configuration for this MCP Server. You can copy it directly to Cursor or other MCP clients.
mcp.json
Connection Info
You Might Also Like
No recommendations available