{"id":"b836c241-d66c-4b6e-adb8-cc8d702ad607","slug":"catia-mcp","kind":"mcp","name":"catia-mcp","summary":"Version-adaptive MCP server for CATIA (V5 and compatible releases) over COM automation - 161 tools","owner":"rutwikg","repoUrl":"https://github.com/rutwikg/catia-mcp","license":"AGPL-3.0","licenseTier":"strong-copyleft","trust":"community","stars":1,"match":null,"fewStars":true,"format":"mcp","createdAt":"2026-08-31T04:57:36.000Z","addedAt":"2026-10-21T21:49:59.764Z","logoUrl":"https://avatars.githubusercontent.com/u/63323403?v=4","linkOnly":false,"collection":null,"skillPath":null,"categories":["cloud-infra"],"tags":[],"runtime":"python","local":true,"cloudCapable":false,"cloudCapability":"unknown","cloudReadyOnPlatform":false,"needsApiKey":false,"reviewed":false,"syncedAt":"2026-10-26T22:35:32.525Z","sample":false,"url":"https://conduits.dev/tools/catia-mcp","language":"Python","transports":["stdio"],"install":{"pypi":"catia-mcp"},"requirements":{"env":[],"upstreamMayCharge":false},"tools":null,"readme":"# catia-mcp\n\n[![License](https://img.shields.io/badge/license-AGPL--3.0-blue)](LICENSE)\n\nA Model Context Protocol server that lets an AI assistant drive **CATIA** on a\nWindows machine through its COM automation interface.\n\nIt targets **CATIA V5** (roughly R14 through V5-6R20xx) and adapts to whatever\nrelease and licence bundle it finds rather than assuming one. 161 tools cover\ndocument handling, sketching, Part Design, dress-up, patterns and booleans,\nGenerative Shape Design, assemblies, measurement, Knowledge parameters and\nformulas, drafting, viewing and data exchange — plus a VBScript escape hatch to\neverything else in the CATIA object model.\n\n```\ncatia_connect → catia_new_part → catia_create_sketch(support=\"xy\")\n              → catia_sketch_centered_rectangle(width=80, height=50)\n              → catia_pad(length=20)\n              → catia_fillet(edges=[\"edge@40,25,10\"], radius=6)\n              → catia_screenshot()          # returns the picture, not a path\n```\n\n---\n\n## Why this exists\n\nDriving CATIA from a script means solving five problems. None of them are\nexotic — every client that talks to CATIA over COM meets them — but each one\nfails *quietly*, which is what makes them expensive:\n\n**1. Measurements silently return zeros.**\nCATIA declares `Measurable::GetCOG`, `GetInertia`, `GetBoundingBox` and\n`Product::Position::GetComponents` as taking an `[in]` array that it then writes\ninto. Hand a late-bound Python list to one of those and CATIA fills a *copy* —\nthe call succeeds, and the caller reads back the zeros it passed in. Centre of\ngravity, inertia, bounding boxes and component positions are all affected.\nThis server reads those arrays through a `VT_BYREF` VARIANT, and cross-checks\nan all-zero answer against a VBScript trampoline evaluated inside CATIA before\nbelieving it.\n\n**2. Naming an edge is not the same as selecting it.**\nCATIA's own handle for a face or an edge is a BRep name like\n`RSur:(Face:(Brp:(Pad.1;0:(Brp:(Sketch.1;2)))...)`, which is unstable across\nreleases *and* across edits to the model. Without a way to resolve geometry\nlive, a client ends up filleting \"the last feature\" instead of the edge that\nwas asked for.\n\n**3. COM threading is easy to get wrong.**\n`pythoncom.CoInitialize()` is per-thread, while MCP handlers run on a thread\npool — so a proxy obtained on one thread is routinely used from another. And\nCATIA rejects calls outright with `RPC_E_CALL_REJECTED` whenever a modal dialog\nis open, which a native client handles with an `IMessageFilter` and Python\ncannot.\n\n**4. Enumeration values are not exposed.**\nCATIA's automation constants cannot be read through COM, so every client\nhardcodes the integers; when one is wrong the failure is often *silent* — a\ncapture written as CGM into a `.jpg`, a minimal-propagation fillet where you\nasked for tangency.\n\n**5. Prose results are hard to chain.**\n`\"Pad created: 20 mm (normal). Feature: 'Pad.1'\"` is fine for a human and poor\nfor a model that has to decide what to do next.\n\nEverything below is how this server addresses them.\n\n---\n\n## What is different\n\n### One COM apartment, with busy-retry\n\nEvery CATIA call is marshalled onto a single dedicated STA thread that owns\n`CoInitialize`. Transient rejections (`RPC_E_CALL_REJECTED`,\n`RPC_E_SERVERCALL_RETRYLATER`) are retried with bounded exponential backoff —\nthe job a native client gives to `IMessageFilter`. Lost connections are detected\nand transparently re-attached. See `catia_mcp/core/apartment.py`.\n\n### A reference grammar for geometry\n\nThe hardest part of scripting CATIA is *pointing at things*. BRep names like\n`RSur:(Face:(Brp:(Pad.1;0:(Brp:(Sketch.1;2)))...)` are unstable across releases\nand across edits. Instead, every tool that needs geometry takes a token:\n\n| Token | Means |\n|---|---|\n| `xy` `yz` `zx` | the part's origin planes |\n| `last` | the most recent feature this server created |\n| `sketch:Sketch.2` | a sketch by name |\n| `body` / `body:PartBody` | the main body, or a body by name |\n| `Pad.1` / `name:Pad.1` | any tree element by its CATIA name |\n| `face#3` `edge#7` `vertex#2` | the n-th topological element |\n| `face@12,0,40` `edge@0,0,10` | whichever face/edge is **nearest that point** |\n\nIndex tokens are cheap but any model change renumbers them. Proximity tokens\nsurvive edits, which makes them the right choice for anything referenced more\nthan once — `catia_list_faces` and `catia_list_edges` report the centroids to\naim at, along with area, length and the outward normal of planar faces.\n\nThe same tokens work inside an assembly: `Selection` returns product-context\nreferences, so `catia_assembly_constraint(elements=[\"face@0,0,10\",\"face@0,0,50\"])`\nconstrains real geometry across two components.\n\n### Adaptive invocation instead of one hardcoded signature\n\n`ShapeFactory` and `HybridShapeFactory` grew `...FromRef` overloads and extra\nparameters across releases. `comutil.try_variants` walks a list of candidate\ncalls and keeps the first CATIA accepts, re-raising immediately on *busy* or\n*disconnected* (which say nothing about whether the signature was right). One\ncode path serves old and new releases; when every candidate fails you get all\nof the attempts back, not just the last error.\n\n### Constants that are probed, not trusted\n\nWhere a wrong enumeration fails silently, the value is discovered at runtime.\n`catia_screenshot` writes one throwaway capture per candidate integer and reads\nthe file's magic bytes to learn what that integer means *on this installation*\n(`catia_capture_formats` shows the result). Everything else can be corrected\nper-site through a JSON file named by `CATIA_MCP_CONSTANTS`, without patching\ncode.\n\n### Structured results with remediation\n\nEvery tool returns JSON with an `ok` flag. Failures carry a stable error code, a\ndecoded HRESULT, and a `remediation` field:\n\n```json\n{\n  \"ok\": false,\n  \"error\": {\n    \"code\": \"catia_busy\",\n    \"message\": \"CATIA is busy and rejected the call (Call was rejected by callee.)\",\n    \"remediation\": \"CATIA rejected the call because it is busy - usually a modal\n                    dialog is open, or a command is still running. Dismiss any\n                    dialog in the CATIA window, then retry.\"\n  }\n}\n```\n\nNothing escapes as a protocol-level error, so a model always gets something it\ncan read and act on.\n\n### Screenshots come back as images\n\n`catia_screenshot` returns an actual MCP image (downscaled PNG when Pillow is\ninstalled), so the assistant can *look* at the model rather than inferring its\nshape from the feature tree.\n\n### Diagnosis before blame\n\n`catia_check_environment` works with no CATIA connection at all: Python version\nand bitness, whether pywin32 imported, whether `CATIA.Application` is registered\nin the COM class table, which MCP SDK is in use, and a list of concrete\nproblems. `catia_capabilities` then probes what the installation can actually\ndo — licences, not release numbers, decide whether GSD or Sheet Metal work.\n\n### Connecting does not launch CATIA by default\n\nA cold CATIA start takes minutes and checks out a licence. `catia_connect`\nattaches to a running session; passing `launch=true` is an explicit choice.\n\n---\n\n## Requirements\n\n- Windows, with CATIA installed and **started at least once as the current user**\n  (that is what registers the `CATIA.Application` COM class)\n- Python 3.10+\n- `pywin32`, `mcp>=1.2`, `pydantic>=2` — plus optional `pillow` for smaller screenshots\n\nCATIA's automation server is out-of-process, so 32-bit/64-bit Python both work.\nCATIA and Python must run as the **same Windows user at the same elevation** —\na CATIA started as administrator is invisible to a non-elevated client.\n\n## Install\n\n```bash\npip install -e \".[images]\"\n```\n\nor, without the package install:\n\n```bash\npip install -r requirements.txt\n```\n\n## Check it works — before involving CATIA\n\n```bash\npython -m catia_mcp --doctor\n```\n\n```bash\npython scripts/protocol_check.py\n```\n\n`protocol_check.py` starts the server over stdio, performs the MCP handshake,\nlists the tools, validates every input schema, and confirms that calling a CATIA\ntool with no CATIA returns a structured error rather than a crash. It needs\nneither CATIA nor a licence.\n\n## Check it works — with CATIA running\n\nStart CATIA, then:\n\n```bash\npython scripts/live_smoke.py\n```\n\nIt builds a small part end to end — sketch, pad, fillet an edge chosen by\nproximity, drill a hole, measure it, screenshot it, export STEP — and prints\nPASS / FAIL / SKIP per step. Steps needing a licence you do not have are\nreported as SKIP, so the output doubles as a capability report for your seat.\nAdd `--close` to discard the scratch part afterwards.\n\n## Drive it by hand\n\nThe MCP Inspector gives you a browser UI over every tool — pick one, fill in\nits arguments from a generated form, fire it, and read the raw JSON-RPC:\n\n```bash\nnpx @modelcontextprotocol/inspector uv run python catia_mcp/server.py\n```\n\nThis is the fastest way to isolate a problem, because there is no model in the\nloop deciding anything: you supply the exact arguments and see the exact\nresult. `--cli` and `--tui` give the same thing headless and in the terminal.\n\n## Wire it to a client\n\nThe simplest route, which needs no extra tooling:\n\n```bash\npython scripts/install_client_config.py --dry-run\n```\n\n```bash\npython scripts/install_client_config.py\n```\n\nIt writes the entry into the Claude desktop app's `claude_desktop_config.json`,\nbacking the file up first. `--target code` targets Claude Code's `~/.claude.json`\ninstead, `--target project` writes a `.mcp.json` beside the repository,\n`--target all` does every one, and `--remove` undoes it. The interpreter it\nrecords is **the one you ran it with** — which is the one that has `pywin32` and\n`mcp` installed, and the usual cause of a server that starts and then cannot do\nanything.\n\n> `claude mcp add` will not work unless you have separately installed the Claude\n> Code CLI (`npm install -g @anthropic-ai/claude-code`). It is a different\n> package from the desktop app. The script above avoids needing it.\n\nTo write the configuration by hand instead, add this to\n`claude_desktop_config.json` — on Windows at\n`%APPDATA%\\Claude\\claude_desktop_config.json`:\n\n```json\n{\n  \"mcpServers\": {\n    \"catia\": {\n      \"command\": \"C:\\\\path\\\\to\\\\python.exe\",\n      \"args\": [\"-m\", \"catia_mcp\"],\n      \"env\": { \"PYTHONPATH\": \"C:\\\\path\\\\to\\\\catia-mcp\" }\n    }\n  }\n}\n```\n\nUse the **full path** to the interpreter you installed the dependencies into,\nnot bare `python`; the client does not inherit your shell's PATH. Restart the\nclient afterwards.\n\nAn HTTP transport is available for remote or multi-client use:\n\n```bash\npython -m catia_mcp --transport http --port 8765\n```\n\n---\n\n## The tools\n\n161 tools in 14 groups. `python -m catia_mcp --list-tools` prints the full\ninventory as JSON.\n\n| Group | n | Covers |\n|---|---|---|\n| `session` | 11 | connect, disconnect, status, environment diagnosis, capability probe, reference help, VBScript execution, undo/redo, batch mode |\n| `document` | 11 | new/open/save/save-as/close/activate for parts, products and drawings; document info; part number, revision and definition |\n| `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 |\n| `sketch` | 16 | sketches on planes **or faces**, points, lines, circles, arcs, ellipses, rectangles, centred rectangles, polygons, slots, polylines, splines, constraints, geometry listing |\n| `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 |\n| `dressup` | 10 | edge fillet, variable fillet, face-face fillet, tritangent fillet, chamfer, shell, thickness, draft, thread, remove-face |\n| `transform` | 8 | rectangular / circular / user patterns, mirror, translate-rotate-symmetry-scale, new body, boolean add/remove/intersect/union-trim, split |\n| `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 |\n| `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 |\n| `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 |\n| `knowledge` | 12 | list/get/set/create/delete parameters, list relations, create formulas, activate/deactivate, design tables and configurations |\n| `drafting` | 7 | sheets, generative views in seven orientations, text, scale, regenerate, delete |\n| `view` | 8 | standard orientations, fit, zoom, background colour, screenshot, capture-format probe, render style, window list |\n| `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 |\n\n### Trimming the tool surface\n\n161 tools is a lot of context. Restrict it with `CATIA_MCP_GROUPS`:\n\n```bash\nCATIA_MCP_GROUPS=document,tree,sketch,part_design,dressup python -m catia_mcp\n```\n\nThe `session` group is always registered so connection and diagnosis stay\navailable. With the example above the server exposes 73 tools instead of 161.\n\n---\n\n## Configuration\n\n| Variable | Effect |\n|---|---|\n| `CATIA_MCP_GROUPS` | comma-separated tool groups to register; default is all |\n| `CATIA_MCP_CONSTANTS` | path to a JSON file overriding CATIA enumeration values |\n| `CATIA_MCP_LOG` | also write logs to this file |\n| `CATIA_MCP_LOG_LEVEL` | `DEBUG`, `INFO`, `WARNING`, `ERROR` |\n| `CATIA_MCP_TRANSPORT` | `stdio` (default), `http`, `sse` |\n\nLogs go to stderr, never stdout — stdout carries the MCP transport.\n\n---\n\n## Notes, limits and honest caveats\n\n- **Sheet Metal has no dedicated tools.** `catia_capabilities` reports whether\n  the `SheetMetalFactory` is available, but the wall/bend/unfold API varies\n  enough between releases that shipping unverified signatures would be worse\n  than pointing you at `catia_run_script`. The same applies to DMU kinematics\n  and clash analysis; for clearance checking, `catia_measure_distance` between\n  two components works well and needs no extra licence.\n\n- **`Selection.Search` only finds visible geometry.** If `catia_list_faces`\n  returns nothing, the body is probably hidden — `catia_show_element` fixes it.\n  This is a CATIA behaviour, not a bug here.\n\n- **Face and edge indices are not stable.** Adding a fillet renumbers every\n  face. Use `face@x,y,z` for anything you reference twice.\n\n- **`catia_start_command` and `catia_undo` are best-effort.** CATIA gives\n  automation clients no way to confirm an interactive command ran, and the\n  command names follow the user-interface language.\n\n- **Pattern direction defaults.** `catia_rect_pattern` and `catia_circ_pattern`\n  will use CATIA's own defaults if you do not pass direction or axis tokens.\n  Pass an edge token when the direction matters.\n\n- **This server can modify and delete work.** It is designed for a CATIA session\n  you are supervising. Tools that delete or overwrite are annotated\n  `destructiveHint` so a client can prompt before running them.\n\n---\n\n## Layout\n\n```\ncatia_mcp/\n  compat.py               MCP SDK shim (MCPServer 1.29+ / FastMCP earlier)\n  server.py               entry point, transports, tool inventory\n  core/\n    apartment.py          single STA thread, busy-retry\n    comutil.py            by-ref arrays, adaptive invocation, defensive reads\n    connection.py         attach/launch, version detection, capability probing\n    errors.py             HRESULT decoding and the error taxonomy\n    refs.py               the reference-token grammar and topology search\n    capture.py            runtime probing of the capture formats\n    constants.py          CATIA enumerations, overridable per site\n    result.py             the result envelope\n  tools/                  14 tool modules, one per domain\nscripts/\n  install_client_config.py  register the server with an MCP client\n  protocol_check.py         MCP conformance check, no CATIA needed\n  live_smoke.py             end-to-end build against a real CATIA\ntests/test_offline.py     36 tests, no CATIA and no Windows required\n```\n\n## Development\n\n```bash\npython -m pytest tests/ -q\nruff check catia_mcp\npython scripts/protocol_check.py\n```\n\n## Licence\n\n**GNU Affero General Public License v3.0 or later** (AGPL-3.0-or-later). See\n[LICENSE](LICENSE).\n\nThe AGPL's network clause is the point: if you run a modified version of this\nserver so that others interact with it over a network, you have to offer them\nits source. Using it privately, or driving your own CATIA with it, carries no\nsuch obligation.\n\nCATIA is a registered trademark of Dassault Systèmes. This project automates\nCATIA; it neither includes nor replaces it, and is not affiliated with or\nendorsed by them. You need your own CATIA installation and licence.\n"}