mcp server
uncad
Rust library for parsing, rendering (SVG/PNG), and writing CAD DWG/DXF files — safe FFI bindings over LibreDWG.
Set up
Runs on your computer. Pick your agent and operating system.
This project does not publish an install method we can turn into a config. Follow the README instead.
README
synced Oct 26, 2026uncad
An open-source Rust library that parses CAD files (DWG/DXF) into a model and exports that model as JSON, SVG or PNG. Read-only: it does not write DWG or DXF, and does not convert between them.
Quick start
git submodule update --init # test fixtures only -- not needed to build (see "Platform")
cargo build --workspace
cargo test --workspace
let db = uncad::parse("drawing.dwg")?; // same model for DWG and DXF (entities + tables)
println!("{} entities", db.entities.len());
// The header variables that give the numbers a meaning come beside the model:
let (db, header) = uncad::parse_with_header("drawing.dxf")?;
println!("units: {:?}", header.units()); // $INSUNITS; None when the file does not say
// Bytes already in memory (from the network, an archive, ...):
let bytes = std::fs::read("drawing.dwg")?;
let db = uncad::parse_bytes(&bytes, uncad::Format::Dwg)?;
let json = db.to_json(uncad::ToJsonOptions { pretty: true })?; // the model, serialized as-is
std::fs::write("drawing.json", json)?;
// Rendering is the iron-render-cad crate's (uncad-cli uses it):
let result = iron_render_cad::to_svg(&db, iron_render_cad::ToSvgOptions::default());
std::fs::write("drawing.svg", result.svg)?;
CLI
The binary is uncad. From a clone, cargo install --path crates/uncad-cli
puts it on your PATH; the lines below run it through cargo run instead.
cargo run -p uncad-cli -- drawing.dwg # summary: entity count per type
cargo run -p uncad-cli -- drawing.dwg -o drawing.json --pretty # export the parsed model
cargo run -p uncad-cli -- drawing.dwg -o drawing.svg # render to SVG (model space)
cargo run -p uncad-cli -- drawing.dwg -o drawing.png # render to PNG (via SVG)
cargo run -p uncad-cli -- drawing.dwg -o drawing.png --scale 2 # rasterize at twice the size
cargo run -p uncad-cli -- drawing.dwg -o drawing.png --fit 4000 # the longer side 4000 px
cargo run -p uncad-cli -- drawing.dwg -o drawing.png --fit 4000 --stroke 2 --paper dark # 2 px lines, black page
cargo run -p uncad-cli -- drawing.dwg -o detail.svg --window 0,0,500,300 # just this rectangle (drawing units)
cargo run -p uncad-cli -- drawing.dwg -o drawing.svg --no-trim # draw far-away outliers too
cargo run -p uncad-cli -- drawing.dwg -o sheet.svg --space paper # sheet borders / title blocks
cargo run -p uncad-cli -- drawing.dwg -o all.svg --space all # every space in one document
cargo run -p uncad-cli -- export drawing.dwg -o pkg/ # images + JSON an LLM or a vision model reads
A render is drawn on a white page by default, where pure white (ACI 7) is drawn
black. --paper dark draws it on a black page instead: pure white stays white,
pure black is drawn white, and every other color is the file's -- the light ones
a white page washes out included. Lines are about 1/6000 of the picture's
diagonal unless --stroke <px> sets their width in pixels: pixels of the image
in a PNG, pixels on the screen at any zoom in an SVG (a style rule with
vector-effect: non-scaling-stroke).
Questions about a drawing, each answered as one line of JSON -- the result
structure of iron-scout-cad or
iron-diff-cad, serialized as it is:
uncad summarize drawing.dwg # what the drawing contains
uncad summarize drawing.dwg --type circle --layer pipes # ... and the circles on a layer, each with its box
uncad summarize drawing.dwg --id 812 --detail # ... and one entity's model record (the fields `set` takes)
uncad hit-test drawing.dwg --x 120 --y 45 --tolerance 0.5 # the entities at a point
uncad relate drawing.dwg # what each dimension's definition points sit on
uncad diff rev-a.dwg rev-b.dwg # exact numeric difference (by reference ID when the files show one drawing)
uncad diff a.dxf b.dxf --matching geometry --length-tolerance 0.01
uncad diff a.dxf b.dxf --matching geometry --min-similarity 1.5 # ... pairing only equal shapes
uncad diff rev-a.dwg rev-b.dwg --omit within,unstated # only what changed beyond tolerance
A change set lists every field that differs, including those that moved within
tolerance and those one drawing does not state (a drawing saved again in a newer
format states values the older format had no place for). --omit leaves either
out -- for an agent reading the answer into a limited context -- and the answer
counts what it left out in omitted, so nothing left out reads as unchanged.
One verb edits: set changes one field of one entity and writes the result as a
new model JSON file -- never over an existing file, never into its input -- and
answers with the difference it made, as diff reports it. The field and the
value are named as the model JSON names them (the value is JSON: a number, a
string in quotes, an object). Every verb reads model JSON as a drawing, so edits
chain, and the last state can be compared with the first -- or rendered, since
every command but export (which reads the drawing's header) takes model JSON:
uncad set part.dwg --id 42 --path radius --value 6.5 -o step1.json
uncad set step1.json --id 42 --path center.x --value 10 -o step2.json
uncad diff part.dwg step2.json # both edits, and nothing else
uncad step2.json -o step2.svg # what the edited state looks like
redline draws that difference on top of the first drawing: the first drawing
exactly as it renders alone, and over it, in one color, what each changed entity
became, what was removed (dashed), and a revision cloud around every change. A
change whose counterpart is uncertain gets a dashed cloud and no geometry. The
color is the first of red, purple, blue, green and orange that the drawing
itself does not come close to, and the answer says which (proposal_color);
--proposal-color gives one instead, and when the drawing uses colors close to
the one given, the answer lists them and a warning suggests another. It
writes an SVG or PNG file -- never over an existing one -- and answers with what
it marked and what it could not show, and why. --paper and --stroke mean what
they mean for a render: a stroke is pixels of the image in a PNG, and pixels on
the screen in an SVG:
uncad redline part.dwg step2.json -o proposal.svg
uncad redline part.dwg step2.json -o proposal.png --fit 2000 --omit within # 2000 px, beyond tolerance only
uncad redline plan.dwg step2.json -o detail.png --fit 1200 --frame changes # just the changes, and around them
uncad redline plan.dwg step2.json -o proposal.svg --proposal-color '#0057b8' # a color of your own
uncad redline plan.dwg step2.json -o review.png --fit 1200 --stroke 3 --paper dark # 3 px lines, black page
uncad redline plan.dwg step2.json -o review.svg --stroke 1 --paper dark # 1 px on screen at any zoom
uncad mcp serves summarize, hit-test, relate, diff, set and redline as Model Context
Protocol tools over stdio, for an agent to
call. A tool's answer is byte for byte what the command prints; a warning the
command writes to stderr is a further content block. Each call reads its files
again -- the server keeps no state -- and calls run one at a time.
{ "mcpServers": { "uncad": { "command": "uncad", "args": ["mcp"] } } }
Scope
- DWG — read through LibreDWG
(GPLv3+), bound directly via Rust FFI (
bindgen). All versions. - DXF — ASCII and binary, R2007 and later included, read by the pure-Rust
undxfcrate (MIT) rather than by LibreDWG; chosen by file extension (parse) or by the caller (parse_bytes). A drawing's DXF and DWG read to the same model except where the two formats state different things (docs/CAVEATS.md, "DXF"). - Output — the parsed model as JSON (
CadDatabase::to_json, fromuncad-model). SVG and PNG come from theiron-render-cadcrate (MIT), whichuncad-cliuses. Writing DWG/DXF is not offered; the write API that existed in 0.1.0 was removed (seeCHANGELOG.md).
The model itself -- CadDatabase, Entity, Tables and their JSON form -- is
the uncad-model crate (MIT), re-exported
here as uncad::model / uncad::tables / uncad::json. This crate is one backend
that fills it; anything that only needs to read a drawing depends on the model
crate alone and inherits nothing from this crate's license.
Platform
Pure Rust plus native FFI. WebAssembly and the browser are not targets — the intended consumers are libraries and binaries (CLI, server, desktop app).
bindgen needs libclang, so LLVM/Clang has to be installed (Windows:
winget install LLVM.LLVM, Ubuntu: apt install libclang-dev). The LibreDWG C
sources are vendored into crates/libredwg-sys/vendor/libredwg/, so building
does not need the lib/libredwg submodule. Running cargo test --workspace
does: the real-file tests under crates/uncad/tests/ and uncad-cli's
tests/documented_invocations.rs read fixtures from that submodule's
test/test-data/. Clone with
git clone --recurse-submodules, or run git submodule update --init in an
existing clone. Builds and tests pass on Linux (x86_64-unknown-linux-gnu) as
well as Windows.
License
GPLv3-or-later. LibreDWG (GPLv3+) is the only third-party component linked
in, and its license carries over. The vendored copy carries local patches;
crates/libredwg-sys/NOTICE.md lists each one and is their modification
notice, inside the crate so that it reaches the published tarball. Copyright and license details for
third-party components are in
docs/THIRD_PARTY_NOTICES.md.
Repository layout
lib/libredwg/ LibreDWG upstream, as a git submodule. Not used by the build --
it is the source vendor/ is regenerated from, and where the
real-file test fixtures (test/test-data/) come from
crates/
libredwg-sys/ raw FFI (cc + bindgen). vendor/libredwg/ holds the subset of C
sources actually compiled (for publishing to crates.io), with
local patches marked "uncad local patch" and listed in
NOTICE.md; shim/ holds the C accessors for opaque types, and
vendor-config/config.h stands in for autotools
uncad/ the safe API: parse() / parse_bytes() -> uncad_model::CadDatabase,
and the drawing's Header beside it from parse_with_header()
uncad-cli/ the CLI binary (uncad)
crates/*/tests/ integration tests against the public API. crates/*/examples/ are
manual-check tools, and #[cfg(test)] blocks inside src/*.rs are
unit tests -- docs/ARCHITECTURE.md's "Test layout" says which
belongs where
scripts/ sync-libredwg-vendor.sh -- regenerates vendor/ after a submodule
update
samples/ gitignored except its README -- drop any DWG/DXF in here for
manual testing, no license clearance needed. No automated tests
read from it
docs/ architecture, known limitations, third-party notices
Further reading:
docs/ARCHITECTURE.md— crate layout, build system, test layout, the FFI/bindgen boundary, thread safety, the entity modeldocs/CAVEATS.md— entity type coverage, known limitations and bugs, cross-platform notes