TianshangCAD
# TianshangCAD
A modern **CAD CLI + MCP Server** system. 2D/3D drawing, editing,
measurement, validation and JSON-driven workflows are available both from the
command line and as standardized tools callable by any MCP client (AI agent).
[](https://glama.ai/mcp/servers/Tianshang301/TianshangCAD)
[](https://glama.ai/mcp/servers/Tianshang301/TianshangCAD)
[](https://github.com/Tianshang301/TianshangCAD/actions/workflows/ci.yml)
[](https://pypi.org/project/tianshangcad/)
[](https://pypi.org/project/tianshangcad/)
[](LICENSE)
[](https://github.com/Tianshang301/TianshangCAD/actions/workflows/ci.yml)
[](https://github.com/Tianshang301/TianshangCAD/actions/workflows/ci.yml)
> **Status**: v0.13.0 — plugin SDK + gltf/cam example plugins; 20 core
> aggregate tools (+ 2 plugin tools).
> 1065 tests passing, ~85% coverage (measured with optional extras
> installed), `ruff` and `mypy` clean.
**中文文档**: [readme/README.zh-CN.md](readme/README.zh-CN.md)
**[Changelog](CHANGELOG.md)** · **[Migration guide v0.6.0 → v0.9.0](MIGRATION.md)**
## Features
- **CAD CLI** — `file`, `draw`, `edit`, `view`, `measure`, `layer`, `batch`
command groups with short aliases (`l` = `draw line`, `c` = `draw circle`, ...)
- **MCP Server** — 20 core JSON-RPC aggregate tools (each with an `action`
discriminator) over stdio, streamable HTTP or
WebSocket (collaboration), callable
from Claude, Cursor and other MCP clients
- **Plugin ecosystem** — plugin SDK (manifest + permissions + lifecycle +
entry-point discovery) with two official plugins: `plugin-gltf` (glTF 2.0
import/export) and `plugin-cam` (2.5-axis toolpaths → G-code), exposing
`cad_gltf` / `cad_cam`
- **3D views** — JSON-defined `View3DDefinition` with spherical camera pose,
named views (iso / top / front / side / back / bottom), perspective /
orthographic projection, plane sections (XY / YZ / XZ), exploded views and
orbit GIF animation; incremental WebGL delta sync for browser clients
- **Batch automation** — schedule one-off / cron / dependency-chained jobs,
sandboxed Python / SCR / batch script execution, webhook notifications,
SQLite persistence and reusable Jinja2 command templates
- **Geometry validation** — self-intersection, degenerate-face and
non-manifold-edge checks with structured `type` / `location` /
`fix_suggestion` diagnostics; box-box interference volumes; topology metrics
- **Rendering** — 2D orthographic PNG (top / front / side, DPI 72–300), shaded
3D preview and Three.js WebGL export with a bundled browser viewer
- **Versioning** — full document snapshots with `deepdiff`-based
save / list / diff / restore
- **Natural language** — `cad_nlp` maps English / Chinese requests to
tool calls with ambiguity handling
- **JSON-driven** — scenes and geometry defined and validated with Pydantic
schemas; full import/export round-trip
- **Pluggable kernel** — analytic (default, no native deps) / OCC
(`cadquery`) / FreeCAD
- **File IO** — JSON, DXF, STL (STEP via the OCC backend)
- **Production hardening** — Docker image with healthcheck, Prometheus
metrics (`/metrics`), API-key authentication (401/403), sliding-window
rate limiting (429) and a `/health` endpoint
- **Quality gates** — `mypy` strict typing, `ruff` linting, `pytest` with a
80% coverage floor; GitHub Actions CI runs lint + tests on every push.
The reported ~87% coverage assumes the optional extras (`boolean`,
`solver`, `occ`, `collab`, `sim`) are installed; the base
`pip install -e .` suite measures lower.
## Install
```bash
python -m venv .venv
# Windows
.venv\Scripts\activate
# Linux / macOS
source .venv/bin/activate
pip install -e ".[dev]"
```
> The `[sim]` extra (`pip install -e '.[sim]'`) provides FEA and kinematics.
> CalculiX FEA requires the `ccx` solver binary installed separately;
> install it from [calculix.de](https://www.calculix.de) and ensure `ccx` is
> in `PATH`.
Self-contained Debian package (Linux amd64, bundles all runtime wheels — no
network access needed at install time):
```bash
wget <release>/tianshangcad_<version>_amd64.deb
sudo dpkg -i tianshangcad_<version>_amd64.deb
```
Optional OCC kernel:
```bash
pip install -e ".[occ]"
```
## CLI Usage
```bash
tianshangcad --version
tianshangcad file new design.json --unit mm
tianshangcad draw line 0,0 100,0
tianshangcad draw circle 50,50 --radius 25
tianshangcad draw box 0,0,0 --dimensions 100,50,30
tianshangcad edit move line_1 --dx 50
tianshangcad view zoom --extents
tianshangcad measure distance 0,0 100,100
```
Short aliases are expanded automatically:
`tianshangcad l 0,0 100,0` equals `tianshangcad draw line 0,0 100,0`.
`tianshangcad --version` prints the current version (e.g. `tianshangcad 0.12.0`).
### Command groups
| Group | Commands |
|-------|----------|
| `file` | new, open, save, close, list, info, export, import |
| `draw` | line, circle, arc, rectangle, polygon, polyline, box, cylinder, sphere |
| `edit` | move, copy, rotate, scale, erase, list, undo, redo |
| `view` | zoom, pan, list |
| `measure` | distance, area, list |
| `layer` | create, list, set, on, off, delete |
| `render` | view, 3d, webgl, view3d, section, explode, gif, views, status |
| `batch` | schedule, run-script, list, status, cancel, templates, logs |
## MCP Server
Run the server and connect any MCP client to it.
### stdio (local agents)
```bash
python -m tianshangcad --transport stdio
```
### Streamable HTTP
```bash
python -m tianshangcad --transport http --host 127.0.0.1 --port 8081
```
The server then serves MCP at `http://127.0.0.1:8081/mcp`, exposes a health
check at `/health` and Prometheus metrics at `/metrics`.
When an API key is configured (via the `TIANSHANGCAD_API_KEYS` env var, comma-separated),
HTTP requests must send it as `x-api-key` or `Authorization: Bearer <key>`:
missing keys get `401`, invalid keys get `403`. Requests are also subject to a
sliding-window rate limit (default 100 requests / 60 s, configurable via
`TIANSHANGCAD_RATE_LIMIT_MAX` and `TIANSHANGCAD_RATE_LIMIT_WINDOW`); exceeding it returns `429`.
`/health` and `/metrics` are always public. stdio mode is unaffected.
### Tool Search (progressive discovery)
`tools/list` accepts an optional `query` string and returns only the tools
whose name or description matches, so clients can progressively discover the
right tool before calling it:
```
tools/list {"query": "measure"} -> [cad_measure, cad_object, cad_status, cad_validate] (cad_measure first)
tools/list {"query": "layer"} -> [cad_layer, cad_status] (cad_layer first)
tools/list {} -> all 22 tools (20 core + cad_gltf + cad_cam)
```
Name matches rank highest, then description matches; multi-word queries
require every token to match; stopword-only queries match nothing.
### Tools (20 core aggregate + 2 plugin)
| Group | Tools |
|-------|-------|
| Files | `cad_file` (action: create/open/save/close/delete/list/import/export) |
| Objects | `cad_object` (action: create/read/update/delete/copy/transform/list/boolean) |
| Layers | `cad_layer` (action: create/read/update/delete/list) |
| JSON | `cad_json` (action: load/parse/validate/save/import_geometry/export_geometry/import_scene/export_scene) |
| Measure | `cad_measure` (action: distance/area) |
| Validate | `cad_validate` (action: geometry/interference/topology/metrics) |
| Status | `cad_status` (target: check/file/object/layer/health/logs_get/logs_clear) |
| Render | `cad_render` (mode: ortho/view_3d/section/explode/animation/webgl) |
| 3D Views | `cad_view` (action: create/read/list/update/delete) |
| NLP | `cad_nlp` (action: command/chat) |
| Version | `cad_version` (action: save/list/diff/restore) |
| Variables | `cad_variable` (action: set/list) |
| Batch | `cad_batch` (action: execute/schedule/status/cancel/list/templates/run_script) |
| Constraints | `cad_constraint` (action: add/remove/list/solve) |
| Assembly | `cad_assembly` (action: create/add_part/add_subasm/add_mate/remove_part/solve/bom/explode) |
| Drawing | `cad_drawing` (action: create/add_view/add_section/add_dimension/add_tolerance/delete/export) |
| Features | `cad_feature` (action: sweep/loft/fillet/chamfer/pattern_linear/pattern_circular/pattern_mirror) |
| Simulation | `cad_sim` (action: mesh/setup/run/result/list/delete) |
| Collaboration | `cad_collab` (tool: session/branch/annotation/presence/history/resolve/permission/sync) |
| Plugins | `cad_plugin` (action: install/uninstall/list/enable/disable/manifest) |
| glTF (plugin) | `cad_gltf` (action: export/import/preview) |
| CAM (plugin) | `cad_cam` (action: toolpath/simulate/export_gcode) |
### Validation, rendering, 3D views & NLP
Validate geometry with structured diagnostics, render orthographic views, snapshot
and restore document versions, drive tools from natural language, and create
named 3D views with camera, section, explode and animation:
```bash
# Render a 300 DPI top view PNG
tianshangcad render view --view top --dpi 300 --output preview.png
tianshangcad render 3d --output preview3d.png
tianshangcad render webgl --output viewer_data.json --viewer examples/threejs_viewer.html
# 3D views
tianshangcad render view3d iso --output iso.png
tianshangcad render section XY --offset 0 --output section.png
tianshangcad render explode --scale 1.5 --output explode.png
tianshangcad render gif --frames 48 --output orbit.gif
tianshangcad render views
# NLP examples (via the MCP tool cad_nlp)
"new file design.dwg" -> cad_file {file: {action: create, filename: design.dwg}}
"draw a line from 0,0 to 10,10" -> cad_object {object: {action: create, type: line, params: {...}}}
"render the side view" -> cad_render {render: {mode: ortho, view: side}}
"save a version" -> cad_version {version: {action: save}}
```
`cad_nlp` (action=`chat`) adds multi-turn dialogue with anaphora resolution: each
`session_id` remembers the last created object so later turns can refer to
it with pronouns or descriptions. Create intents are executed against the
current document, so "it" / "它" resolves to the real object id.
```bash
# Turn 1: draw a circle (creates the object, records it in the session)
"draw a circle at 5,5 radius 3" -> cad_object, object_id tracked
# Turn 2: move the referenced circle (same session_id)
"move it to 10,10" -> cad_object {object: {action: update, object_id, params}}
"move the circle I just drew to 3,3" -> same, explicit anaphora
"把它移到 4,4" -> same, Chinese pronoun
```
Version diffing uses `deepdiff` and reports changed fields, added/removed
items and the raw result. The WebGL export writes Three.js `BufferGeometry`
JSON consumable by `examples/threejs_viewer.html`. View definitions
(camera pose, projection, section/explode parameters) are persisted with the
document and are also exposed as MCP tools (`cad_view` for view definitions,
`cad_render` for section / explode / animation / webgl modes).
### Real-time collaboration
Phase 9 collaboration builds on the LWW-Map CRDT: a session holds the shared
document state as keyed registers (geometry / layers / variables /
constraints / assembly), with 4-role × 4-scope RBAC (viewer / editor / admin /
owner over document / scene / assembly / settings). Sessions support
presence, annotations, document branches (fork / edit / merge with explicit
conflict resolution) and a transport-agnostic sync primitive:
```bash
# Optional dependency for the WebSocket hub
pip install -e ".[collab]"
tianshangcad collab create --name review # seed a session over the current doc
tianshangcad collab list
tianshangcad collab annotate <session_id> "check the hole"
tianshangcad collab perm <session_id> bob --role editor
# WebSocket transport (default port 8082)
python -m tianshangcad --transport ws --port 8082
```
MCP clients use `cad_collab_session`, `cad_collab_branch`,
`cad_collab_annotation`, `cad_collab_presence`, `cad_collab_history`,
`cad_collab_resolve`, `cad_collab_permission` and `cad_collab_sync`.
WebSocket clients speak a small JSON envelope (`subscribe` / `op` / `sync` /
`ping`) that maps onto the sync tool. A multi-client hub fans an applied
`op` out as a `deltas` broadcast to every subscriber of the same session
(excluding the origin sender, which already received its live response).
### Batch & automation
Schedule jobs with a standard 5-field cron expression, dependency chains and
webhook notifications; run scripts through a sandboxed engine; persist job
state to SQLite:
```bash
# One-off job
tianshangcad batch schedule commands.json --name report
# Cron job (daily at 02:00) using a built-in template
tianshangcad batch schedule commands.json --cron "0 2 * * *"
# Run a sandboxed Python script
tianshangcad batch run-script script.py --type python --timeout 30
# Inspect results
tianshangcad batch list
tianshangcad batch status <job_id>
tianshangcad batch logs --source batch --job-id <job_id>
```
Scripts run in an isolated subprocess (`python -I`) with an import whitelist
(`os`, `subprocess`, `socket`, ... are blocked), a runtime `sys.modules`
guard and a hard timeout.
## Plugins
Plugins extend the server with new MCP tools and CLI commands. The SDK
(`core/plugins/`) provides a manifest + permission declaration, a
`load → initialize → run → shutdown` lifecycle and four extension points
(tools / commands / kernel / solver). Plugins are discovered from the
`tianshangcad.plugins` entry-point group of installed distributions.
```bash
tianshangcad plugin list # discover + list
tianshangcad plugin enable <name> # enable / disable
tianshangcad plugin manifest <name> # inspect the manifest
```
Two official plugins ship with the package:
- `plugin-gltf` — glTF 2.0 import/export (PBR materials); `cad_gltf`, `gltf` CLI.
- `plugin-cam` — 2.5-axis contour + drilling toolpaths to G-code; `cad_cam`, `cam` CLI.
> **Security**: plugins run in-process, in the same trust domain as the
> server, and are **not sandboxed**. The MCP `cad_plugin` `install` action
> only loads plugins from installed distributions' entry-points (it never
> imports an arbitrary `module:attr` path); only install plugins from
> trusted sources. Process-level sandboxing is a future hardening step.
## Docker
A multi-stage image (< 500 MB, `python:3.12-slim`) is provided in
`docker/` for headless deployment:
```bash
docker compose -f docker/docker-compose.yml up -d
```
The container runs the MCP server over streamable HTTP on port `8081` with a
`/health` healthcheck, and mounts `data/` + `config/` volumes. Environment
overrides: `TIANSHANGCAD_RUNTIME`, `TIANSHANGCAD_HEADLESS`, `TIANSHANGCAD_TEMP_DIR`, `TIANSHANGCAD_API_KEYS`,
`TIANSHANGCAD_LOG_LEVEL`, `TIANSHANGCAD_RATE_LIMIT_MAX`, `TIANSHANGCAD_RATE_LIMIT_WINDOW`.
Example MCP client configuration (Claude Desktop `~/.config/claude/mcp.json`):
```json
{
"mcpServers": {
"cad-server": {
"command": "python",
"args": ["-m", "tianshangcad", "--transport", "stdio"],
"autoApprove": [
"cad_json",
"cad_measure",
"cad_render",
"cad_validate"
]
}
}
}
```
## Development
```bash
bash scripts/setup_dev.sh # venv + editable install + stubs
bash scripts/run_tests.sh # ruff + mypy + pytest (coverage gate >= 80%)
bash scripts/build_docs.sh
```
Or run each gate directly:
```bash
ruff check . # lint
mypy src # type check
pytest # tests (coverage gate >= 80%)
```
### Benchmark harness (CADGenBench)
`scripts/cadgenbench_harness.py` is an offline demo harness that drives the
real MCP server over stdio to build a small set of 3D parts, export them as
STEP, and run a local validity check (watertight manifold) mirroring
CADGenBench's scoring gate -- no external API or HuggingFace token needed:
```bash
python scripts/cadgenbench_harness.py # analytic AP203 exporter
python scripts/cadgenbench_harness.py --occ # OCCT kernel path
# Results: dist/cadgenbench/run_summary.json
```
To turn this into a real CADGenBench submission, read a sample's
`description.yaml`, let an LLM choose the tool calls with this server as the
backend, and upload the resulting `output.step` candidates to the leaderboard
Space.
## Project Layout
```
src/tianshangcad/
|-- cli/ # typer CLI: commands + alias expansion
|-- mcp/ # MCP server, transports, security and tool registry
| |-- server.py # MCPServer wiring (20 core tools + plugin discovery)
| |-- transport.py # stdio / streamable HTTP (+ auth, rate limiting)
| |-- security.py # tool permission whitelist
| |-- auth.py # API-key authentication
| |-- rate_limit.py # sliding-window rate limiter
| `-- tools/ # crud, json_ops, status, validate, batch, boolean,
| # file_io, variables, render, versioning, nlp, view3d,
| # features, simulation
|-- core/ # document, entity, layer, kernel, session, history,
| # variables, scheduler, script_runner, batch_templates,
| # validation, versioning, view_manager, features, simulation,
| # assembly, drawing, constraint, plugins (SDK + manager)
|-- plugins/ # official example plugins: gltf (glTF 2.0), cam (2.5-axis)
|-- io/ # JSON / DXF / STL importers and exporters
|-- schemas/ # Pydantic geometry, scene and view3d schemas
|-- render/ # 2D / 3D PNG rendering, WebGL export, section, explode,
| # animation
`-- utils/ # logger, config, errors, validators, units, metrics
examples/
`-- threejs_viewer.html # browser viewer for WebGL exports
docker/
|-- Dockerfile # multi-stage image (python:3.12-slim)
|-- docker-compose.yml # service definition with healthcheck
`-- entrypoint.sh
tests/
|-- unit/ # CLI, core, IO, MCP tool unit tests
`-- integration/ # MCP e2e, batch, JSON workflow and performance tests
```
## Documentation
- `readme/README.zh-CN.md` — Chinese README
## Continuous Integration
`.github/workflows/ci.yml` runs `ruff` + `mypy` on every push / PR,
`pytest` with the 80% coverage gate on Python 3.12, and a separate
`stress` job for the concurrency / soak suite. Pushing a `v*` tag triggers
`.github/workflows/release.yml`, which builds the Windows executables
(`tianshangcad.exe`, `tianshangcad-server.exe` via PyInstaller) and the self-contained
Debian package (`scripts/build_deb.py`, bundles runtime wheels for Linux
amd64) and publishes them to a GitHub Release.
## License
**Apache License 2.0** — see [`LICENSE`](LICENSE).
Community guidelines: [Code of Conduct](CODE_OF_CONDUCT.md) ·
Security: [SECURITY.md](SECURITY.md) · Contributing via pull requests is
welcome.
Third-party runtime dependencies are all permissive-licensed (MIT / BSD /
Apache-2.0 / ISC / PSF, plus MPL-2.0 for `certifi`); the full inventory is in
[`THIRD_PARTY_LICENSES.md`](THIRD_PARTY_LICENSES.md).
Optional backends: `cadquery` (Apache-2.0) is compatible. The optional
FreeCAD / OpenCASCADE backends are LGPL-2.1 and are **not** bundled; if you
enable them you must comply with the LGPL (retain notices, keep the library
re-linkable). The default `AnalyticKernel` is self-authored and fully
Apache-2.0.
TDQS
Scored across 22 tools
Each cad_* tool targets a distinct domain and the 'When not to use' sections actively separate overlapping areas like cad_file vs cad_json vs cad_gltf and cad_status vs cad_validate. A few boundaries remain blurry (cad_status metrics vs cad_validate metrics, cad_version snapshots vs cad_collab branches), but the descriptions mostly prevent misselection.
All tools follow a consistent cad_<domain> pattern, making the surface highly predictable. This uniform prefix convention is clearer than many servers using mixed verb styles.
22 tools is on the heavy side, but each maps to a distinct CAD subdomain such as file, object, layer, assembly, drawing, simulation, CAM, and collaboration. The count is justified for a full CAD server rather than padding, though it is slightly above the typical 3-15 well-scoped range.
The surface covers the CAD lifecycle unusually well: document/file management, geometry CRUD, layers, views, constraints, assemblies, drawings, features, simulation, CAM, glTF, plugins, validation, status, and collaboration. There are no obvious dead ends—each domain includes the key create/read/update/delete or equivalent operations needed.