mcp server
catia-mcp
Version-adaptive MCP server for CATIA (V5 and compatible releases) over COM automation - 161 tools
Set up
Runs on your computer. Pick your agent and operating system.
Before you start
- uv (`curl -LsSf https://astral.sh/uv/install.sh | sh`)
{
"mcpServers": {
"catia-mcp": {
"command": "uvx",
"args": [
"catia-mcp"
]
}
}
}same format Cursor, GitHub Copilot CLI, Junie, JetBrains AI Assistant, Augment (Auggie CLI), Factory Droid, Kiro, Devin CLI +12
Values in angle brackets are placeholders. Restart Claude Desktop after editing its config.Claude Desktop docs
README
synced Oct 26, 2026catia-mcp
A Model Context Protocol server that lets an AI assistant drive CATIA on a Windows machine through its COM automation interface.
It targets CATIA V5 (roughly R14 through V5-6R20xx) and adapts to whatever release and licence bundle it finds rather than assuming one. 161 tools cover document handling, sketching, Part Design, dress-up, patterns and booleans, Generative Shape Design, assemblies, measurement, Knowledge parameters and formulas, drafting, viewing and data exchange — plus a VBScript escape hatch to everything else in the CATIA object model.
catia_connect → catia_new_part → catia_create_sketch(support="xy")
→ catia_sketch_centered_rectangle(width=80, height=50)
→ catia_pad(length=20)
→ catia_fillet(edges=["edge@40,25,10"], radius=6)
→ catia_screenshot() # returns the picture, not a path
Why this exists
Driving CATIA from a script means solving five problems. None of them are exotic — every client that talks to CATIA over COM meets them — but each one fails quietly, which is what makes them expensive:
1. Measurements silently return zeros.
CATIA declares Measurable::GetCOG, GetInertia, GetBoundingBox and
Product::Position::GetComponents as taking an [in] array that it then writes
into. Hand a late-bound Python list to one of those and CATIA fills a copy —
the call succeeds, and the caller reads back the zeros it passed in. Centre of
gravity, inertia, bounding boxes and component positions are all affected.
This server reads those arrays through a VT_BYREF VARIANT, and cross-checks
an all-zero answer against a VBScript trampoline evaluated inside CATIA before
believing it.
2. Naming an edge is not the same as selecting it.
CATIA's own handle for a face or an edge is a BRep name like
RSur:(Face:(Brp:(Pad.1;0:(Brp:(Sketch.1;2)))...), which is unstable across
releases and across edits to the model. Without a way to resolve geometry
live, a client ends up filleting "the last feature" instead of the edge that
was asked for.
3. COM threading is easy to get wrong.
pythoncom.CoInitialize() is per-thread, while MCP handlers run on a thread
pool — so a proxy obtained on one thread is routinely used from another. And
CATIA rejects calls outright with RPC_E_CALL_REJECTED whenever a modal dialog
is open, which a native client handles with an IMessageFilter and Python
cannot.
4. Enumeration values are not exposed.
CATIA's automation constants cannot be read through COM, so every client
hardcodes the integers; when one is wrong the failure is often silent — a
capture written as CGM into a .jpg, a minimal-propagation fillet where you
asked for tangency.
5. Prose results are hard to chain.
"Pad created: 20 mm (normal). Feature: 'Pad.1'" is fine for a human and poor
for a model that has to decide what to do next.
Everything below is how this server addresses them.
What is different
One COM apartment, with busy-retry
Every CATIA call is marshalled onto a single dedicated STA thread that owns
CoInitialize. Transient rejections (RPC_E_CALL_REJECTED,
RPC_E_SERVERCALL_RETRYLATER) are retried with bounded exponential backoff —
the job a native client gives to IMessageFilter. Lost connections are detected
and transparently re-attached. See catia_mcp/core/apartment.py.
A reference grammar for geometry
The hardest part of scripting CATIA is pointing at things. BRep names like
RSur:(Face:(Brp:(Pad.1;0:(Brp:(Sketch.1;2)))...) are unstable across releases
and across edits. Instead, every tool that needs geometry takes a token:
| Token | Means |
|---|---|
xy yz zx | the part's origin planes |
last | the most recent feature this server created |
sketch:Sketch.2 | a sketch by name |
body / body:PartBody | the main body, or a body by name |
Pad.1 / name:Pad.1 | any tree element by its CATIA name |
face#3 edge#7 vertex#2 | the n-th topological element |
face@12,0,40 edge@0,0,10 | whichever face/edge is nearest that point |
Index tokens are cheap but any model change renumbers them. Proximity tokens
survive edits, which makes them the right choice for anything referenced more
than once — catia_list_faces and catia_list_edges report the centroids to
aim at, along with area, length and the outward normal of planar faces.
The same tokens work inside an assembly: Selection returns product-context
references, so catia_assembly_constraint(elements=["face@0,0,10","face@0,0,50"])
constrains real geometry across two components.
Adaptive invocation instead of one hardcoded signature
ShapeFactory and HybridShapeFactory grew ...FromRef overloads and extra
parameters across releases. comutil.try_variants walks a list of candidate
calls and keeps the first CATIA accepts, re-raising immediately on busy or
disconnected (which say nothing about whether the signature was right). One
code path serves old and new releases; when every candidate fails you get all
of the attempts back, not just the last error.
Constants that are probed, not trusted
Where a wrong enumeration fails silently, the value is discovered at runtime.
catia_screenshot writes one throwaway capture per candidate integer and reads
the file's magic bytes to learn what that integer means on this installation
(catia_capture_formats shows the result). Everything else can be corrected
per-site through a JSON file named by CATIA_MCP_CONSTANTS, without patching
code.
Structured results with remediation
Every tool returns JSON with an ok flag. Failures carry a stable error code, a
decoded HRESULT, and a remediation field:
{
"ok": false,
"error": {
"code": "catia_busy",
"message": "CATIA is busy and rejected the call (Call was rejected by callee.)",
"remediation": "CATIA rejected the call because it is busy - usually a modal
dialog is open, or a command is still running. Dismiss any
dialog in the CATIA window, then retry."
}
}
Nothing escapes as a protocol-level error, so a model always gets something it can read and act on.
Screenshots come back as images
catia_screenshot returns an actual MCP image (downscaled PNG when Pillow is
installed), so the assistant can look at the model rather than inferring its
shape from the feature tree.
Diagnosis before blame
catia_check_environment works with no CATIA connection at all: Python version
and bitness, whether pywin32 imported, whether CATIA.Application is registered
in the COM class table, which MCP SDK is in use, and a list of concrete
problems. catia_capabilities then probes what the installation can actually
do — licences, not release numbers, decide whether GSD or Sheet Metal work.
Connecting does not launch CATIA by default
A cold CATIA start takes minutes and checks out a licence. catia_connect
attaches to a running session; passing launch=true is an explicit choice.
Requirements
- Windows, with CATIA installed and started at least once as the current user
(that is what registers the
CATIA.ApplicationCOM class) - Python 3.10+
pywin32,mcp>=1.2,pydantic>=2— plus optionalpillowfor smaller screenshots
CATIA's automation server is out-of-process, so 32-bit/64-bit Python both work. CATIA and Python must run as the same Windows user at the same elevation — a CATIA started as administrator is invisible to a non-elevated client.
Install
pip install -e ".[images]"
or, without the package install:
pip install -r requirements.txt
Check it works — before involving CATIA
python -m catia_mcp --doctor
python scripts/protocol_check.py
protocol_check.py starts the server over stdio, performs the MCP handshake,
lists the tools, validates every input schema, and confirms that calling a CATIA
tool with no CATIA returns a structured error rather than a crash. It needs
neither CATIA nor a licence.
Check it works — with CATIA running
Start CATIA, then:
python scripts/live_smoke.py
It builds a small part end to end — sketch, pad, fillet an edge chosen by
proximity, drill a hole, measure it, screenshot it, export STEP — and prints
PASS / FAIL / SKIP per step. Steps needing a licence you do not have are
reported as SKIP, so the output doubles as a capability report for your seat.
Add --close to discard the scratch part afterwards.
Drive it by hand
The MCP Inspector gives you a browser UI over every tool — pick one, fill in its arguments from a generated form, fire it, and read the raw JSON-RPC:
npx @modelcontextprotocol/inspector uv run python catia_mcp/server.py
This is the fastest way to isolate a problem, because there is no model in the
loop deciding anything: you supply the exact arguments and see the exact
result. --cli and --tui give the same thing headless and in the terminal.
Wire it to a client
The simplest route, which needs no extra tooling:
python scripts/install_client_config.py --dry-run
python scripts/install_client_config.py
It writes the entry into the Claude desktop app's claude_desktop_config.json,
backing the file up first. --target code targets Claude Code's ~/.claude.json
instead, --target project writes a .mcp.json beside the repository,
--target all does every one, and --remove undoes it. The interpreter it
records is the one you ran it with — which is the one that has pywin32 and
mcp installed, and the usual cause of a server that starts and then cannot do
anything.
claude mcp addwill not work unless you have separately installed the Claude Code CLI (npm install -g @anthropic-ai/claude-code). It is a different package from the desktop app. The script above avoids needing it.
To write the configuration by hand instead, add this to
claude_desktop_config.json — on Windows at
%APPDATA%\Claude\claude_desktop_config.json:
{
"mcpServers": {
"catia": {
"command": "C:\\path\\to\\python.exe",
"args": ["-m", "catia_mcp"],
"env": { "PYTHONPATH": "C:\\path\\to\\catia-mcp" }
}
}
}
Use the full path to the interpreter you installed the dependencies into,
not bare python; the client does not inherit your shell's PATH. Restart the
client afterwards.
An HTTP transport is available for remote or multi-client use:
python -m catia_mcp --transport http --port 8765
The tools
161 tools in 14 groups. python -m catia_mcp --list-tools prints the full
inventory as JSON.
| Group | n | Covers |
|---|---|---|
session | 11 | connect, disconnect, status, environment diagnosis, capability probe, reference help, VBScript execution, undo/redo, batch mode |
document | 11 | new/open/save/save-as/close/activate for parts, products and drawings; document info; part number, revision and definition |
tree | 15 | describe the spec tree, list bodies / features / faces / edges / vertices, resolve and verify a reference token, find by wildcard, show/hide, rename, delete, select, update |
sketch | 16 | sketches on planes or faces, points, lines, circles, arcs, ellipses, rectangles, centred rectangles, polygons, slots, polylines, splines, constraints, geometry listing |
part_design | 10 | pad, pocket, shaft, groove, rib, slot, stiffener, holes (five types, from a point or a sketch), solid combine — with up-to-next / up-to-last / up-to-plane / up-to-surface limits and thin walls |
dressup | 10 | edge fillet, variable fillet, face-face fillet, tritangent fillet, chamfer, shell, thickness, draft, thread, remove-face |
transform | 8 | rectangular / circular / user patterns, mirror, translate-rotate-symmetry-scale, new body, boolean add/remove/intersect/union-trim, split |
gsd | 28 | geometrical sets, points, lines, circles, splines, polylines, helices, planes, extrude, revolve, sweep, multi-section, fill, offset, blend, join, split, trim, intersect, project, extract, healing, transforms, thick-surface, close-surface, axis systems |
assembly | 12 | insert components, new parts and sub-assemblies, list, remove, read and set placement, nine constraint kinds, list/delete constraints, bill of materials, update |
measure | 9 | measure any element, minimum distance with contact points, angle, bounding box, mass properties, point coordinates, face plane and normal, list and apply materials |
knowledge | 12 | list/get/set/create/delete parameters, list relations, create formulas, activate/deactivate, design tables and configurations |
drafting | 7 | sheets, generative views in seven orientations, text, scale, regenerate, delete |
view | 8 | standard orientations, fit, zoom, background colour, screenshot, capture-format probe, render style, window list |
exchange | 4 | export (STEP, IGES, STL, VRML, 3D XML, CGR, model, and DXF/DWG/PDF/CGM/SVG for drawings), export every open document, bill of materials to CSV, format list |
Trimming the tool surface
161 tools is a lot of context. Restrict it with CATIA_MCP_GROUPS:
CATIA_MCP_GROUPS=document,tree,sketch,part_design,dressup python -m catia_mcp
The session group is always registered so connection and diagnosis stay
available. With the example above the server exposes 73 tools instead of 161.
Configuration
| Variable | Effect |
|---|---|
CATIA_MCP_GROUPS | comma-separated tool groups to register; default is all |
CATIA_MCP_CONSTANTS | path to a JSON file overriding CATIA enumeration values |
CATIA_MCP_LOG | also write logs to this file |
CATIA_MCP_LOG_LEVEL | DEBUG, INFO, WARNING, ERROR |
CATIA_MCP_TRANSPORT | stdio (default), http, sse |
Logs go to stderr, never stdout — stdout carries the MCP transport.
Notes, limits and honest caveats
-
Sheet Metal has no dedicated tools.
catia_capabilitiesreports whether theSheetMetalFactoryis available, but the wall/bend/unfold API varies enough between releases that shipping unverified signatures would be worse than pointing you atcatia_run_script. The same applies to DMU kinematics and clash analysis; for clearance checking,catia_measure_distancebetween two components works well and needs no extra licence. -
Selection.Searchonly finds visible geometry. Ifcatia_list_facesreturns nothing, the body is probably hidden —catia_show_elementfixes it. This is a CATIA behaviour, not a bug here. -
Face and edge indices are not stable. Adding a fillet renumbers every face. Use
face@x,y,zfor anything you reference twice. -
catia_start_commandandcatia_undoare best-effort. CATIA gives automation clients no way to confirm an interactive command ran, and the command names follow the user-interface language. -
Pattern direction defaults.
catia_rect_patternandcatia_circ_patternwill use CATIA's own defaults if you do not pass direction or axis tokens. Pass an edge token when the direction matters. -
This server can modify and delete work. It is designed for a CATIA session you are supervising. Tools that delete or overwrite are annotated
destructiveHintso a client can prompt before running them.
Layout
catia_mcp/
compat.py MCP SDK shim (MCPServer 1.29+ / FastMCP earlier)
server.py entry point, transports, tool inventory
core/
apartment.py single STA thread, busy-retry
comutil.py by-ref arrays, adaptive invocation, defensive reads
connection.py attach/launch, version detection, capability probing
errors.py HRESULT decoding and the error taxonomy
refs.py the reference-token grammar and topology search
capture.py runtime probing of the capture formats
constants.py CATIA enumerations, overridable per site
result.py the result envelope
tools/ 14 tool modules, one per domain
scripts/
install_client_config.py register the server with an MCP client
protocol_check.py MCP conformance check, no CATIA needed
live_smoke.py end-to-end build against a real CATIA
tests/test_offline.py 36 tests, no CATIA and no Windows required
Development
python -m pytest tests/ -q
ruff check catia_mcp
python scripts/protocol_check.py
Licence
GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later). See LICENSE.
The AGPL's network clause is the point: if you run a modified version of this server so that others interact with it over a network, you have to offer them its source. Using it privately, or driving your own CATIA with it, carries no such obligation.
CATIA is a registered trademark of Dassault Systèmes. This project automates CATIA; it neither includes nor replaces it, and is not affiliated with or endorsed by them. You need your own CATIA installation and licence.