61 lines
2.5 KiB
Markdown
61 lines
2.5 KiB
Markdown
# Architecture
|
|
|
|
## File Structure
|
|
|
|
```
|
|
server.py FastMCP 3.0 server — all tool/resource/prompt registrations
|
|
├── config.py Settings class (pydantic-settings, env vars)
|
|
├── security.py validate_path() + sanitize_command() + sanitize_lisp()
|
|
└── backends/
|
|
├── base.py AutoCADBackend ABC + data models
|
|
├── ezdxf_backend.py Headless DXF ops via ezdxf library
|
|
└── com_backend.py Live AutoCAD control via pywin32 COM
|
|
```
|
|
|
|
## Dual-Backend Strategy Pattern
|
|
|
|
`AutoCADBackend` (in `base.py`) is an abstract base class. Both backends implement the full interface. The server creates exactly one backend at startup via `_make_backend()`:
|
|
|
|
1. Check `AUTOCAD_MCP_BACKEND` env var
|
|
2. If `auto` or `com` on Windows: try COM → fall back to ezdxf
|
|
3. If non-Windows or `ezdxf`: use ezdxf
|
|
|
|
## Data Models
|
|
|
|
Defined in `base.py`:
|
|
|
|
- **EntityInfo**: `{handle, type, layer, color, linetype, visible, properties}`
|
|
- **LayerInfo**: `{name, color, linetype, lineweight, is_on, is_frozen, is_locked, is_current}`
|
|
- **BlockInfo**: `{name, origin, attribute_count, entity_count, is_xref, description}`
|
|
- **DrawingInfo**: `{name, full_path, saved, entity_count, layer_count, block_count, extents_min, extents_max, units, version, backend}`
|
|
- **CommandResult**: `{ok, payload, error}`
|
|
|
|
## Backend Differences
|
|
|
|
| Feature | ezdxf | COM |
|
|
|---------|-------|-----|
|
|
| Platform | Any | Windows only |
|
|
| AutoCAD needed | No | Yes (running) |
|
|
| View/zoom | No-op | Full control |
|
|
| Screenshots | matplotlib render | Win32 window capture |
|
|
| Commands/LISP | Not supported | Full support |
|
|
| Undo | DXF snapshot stack | Native AutoCAD undo |
|
|
| Threading | `asyncio.to_thread` via `_async()` | `ThreadPoolExecutor` (single thread, STA) |
|
|
| File formats | DXF only | DWG + DXF |
|
|
|
|
## FastMCP Server
|
|
|
|
The `mcp` object is configured with:
|
|
- **Lifespan**: `autocad_lifespan` — initializes backend, stores in `ctx.lifespan_context["backend"]`
|
|
- **Middleware**: ErrorHandling → AuditLog (custom timing) → Timing → Logging
|
|
- **`_backend(ctx)`**: Helper that retrieves backend from context, raises `ToolError` if unavailable
|
|
|
|
## Adding a New Tool
|
|
|
|
1. Add abstract method to `AutoCADBackend` in `backends/base.py`
|
|
2. Implement in `EzdxfBackend` — wrap sync calls with `_async(func)`
|
|
3. Implement in `ComBackend` — wrap COM calls with `self._com(func)`
|
|
4. Register in `server.py`: `@mcp.tool(...)` calling `_backend(ctx).method(...)`
|
|
5. Use `_dc(result)` to convert dataclass returns to dicts
|
|
6. Write tests in `tests/`
|