Skip to main content
Glama
xuantinhnbs-rgb

navisworks-mcp

navisworks-mcp

English · Tiếng Việt

CI License: MIT Python Platform

An MCP server that lets Claude — or any MCP client such as Claude Code, Claude Desktop, Cursor or Cline — drive Autodesk Navisworks running on your Windows machine: browse the model tree, read IFC/Revit properties, search, select, colour, hide, isolate, zoom, and capture the window.

Verified on Navisworks Manage 2026 (Roamer 23.3.1460.83), Python 3.14, Windows 10. All 36 end-to-end checks pass against a real model.

Every tool returns JSON with an ok key — {"ok": true, ...} on success, {"ok": false, "error": "..."} on failure. No tool ever lets a raw COM exception escape.

Navisworks driven by the MCP server: MSE wall panels found by name, coloured
orange and zoomed to

Everything above happened without a single click in Navisworks: find_objects("PANEL") → 375 matches, set_color(...) on twenty of them, zoom_to_objects(...), capture_screenshot(...).

How it talks to Navisworks

Navisworks registers an out-of-process COM server:

HKLM\SOFTWARE\Classes\Navisworks.Document.23\CLSID
  -> LocalServer32 = C:\Program Files\Autodesk\Navisworks Manage 2026\Roamer.exe

That handle gives you the whole COM API, not just open/save commands:

doc   = win32com.client.Dispatch("Navisworks.Document.23")
state = doc.State          # InwOpState10: model tree, properties, search,
                           # selection, colour, visibility, viewpoints...

No .NET add-in to write, nothing to compile. The server attaches to a running Navisworks if there is one (GetActiveObject), and starts a hidden instance otherwise.

Requirements

  • Windows

  • Autodesk Navisworks (Manage or Simulate) — verified on 2026; 2022–2025 connect through the matching ProgID in PROG_IDS but are unverified

  • Python 3.10 or newer

Installation

git clone https://github.com/xuantinhnbs-rgb/navisworks-mcp.git
cd navisworks-mcp
pip install -r requirements.txt
python test_connection.py          # finds a .nwd/.nwc on your machine and tests against it

Then declare the server in your MCP client's configuration. Copy mcp.json.example and replace <PYTHON> with the path to your python.exe and <PATH> with the directory you just cloned into:

{
  "mcpServers": {
    "navisworks": {
      "command": "<PYTHON>/python.exe",
      "args": ["<PATH>/navisworks-mcp/server.py"],
      "cwd": "<PATH>/navisworks-mcp",
      "env": { "PYTHONUNBUFFERED": "1", "PYTHONIOENCODING": "utf-8" }
    }
  }
}

PYTHONIOENCODING=utf-8 is not optional: the Windows console defaults to cp1252 and any non-ASCII message will kill the server with UnicodeEncodeError.

Object identifiers

Objects are addressed by a 1-based index path counted from the model root:

""                 the model root (whole file)
"1"                first child of the root
"1/1/1/2/1/1/1"    seven levels down

This is Navisworks' own InwOaPath.ArrayData, and it converts both ways. Get ids from get_model_tree, find_objects or get_selection — never invent one.

A model exported from Revit/IFC has a typical tree:

File > Assembly > IfcSite > IfcBuilding > IfcBuildingStorey > Category > Family
     > Instance > Mesh

Tools

24 tools in total:

Group

Tools

Connection

check_navisworks_connection, show_navisworks_window

Document

open_model, append_model, save_model_as, get_model_info

Tree & properties

get_model_tree, get_node_info, get_node_properties

Search

find_objects

Selection

get_selection, select_objects, clear_selection

Appearance

set_color, set_transparency, reset_appearance, hide_objects, isolate_objects, show_all_objects

Views

zoom_to_objects, list_saved_views, apply_saved_view, list_selection_sets

Images

capture_screenshot

Performance: read this before a large query

Every field in a result is one cross-process COM round trip. Measured on this machine:

Condition

Cost per object

Window hidden, detail="basic"

~19 ms

Window hidden, detail="full"

~26 ms

Window visible, detail="basic"

~200 ms

A visible window makes every call drag a UI repaint along with it — 5–10× more expensive. So: hide the window for bulk queries, show it only when you need to look at something or take a screenshot. find_objects defaults to detail="basic"; call get_node_info on a single object when you need the detail.

Three Navisworks behaviours that surprise people

1. Search is case-sensitive by default in Navisworks. The server sets case_sensitive=False instead, because component names in civil models are mostly upper case (TAM PANEL...). Searching "panel" with case_sensitive=True returns 0 results where "PANEL" returns 374.

2. Search results collapse onto the parent node. Navisworks defaults to "disjoint": once a node matches, its descendants are not listed separately. Searching for a type containing "Ifc" therefore returns exactly 1 row (IfcSite — ancestor of everything) instead of 779. The server turns collapsing off by default; re-enable it with only_topmost_match=True.

3. isolate_objects has to compute the complement itself. InwOpSelection.Invert() does not give a tree-wise complement: calling it on a single-node selection returns empty, so the obvious "select, invert, hide" approach reports success while hiding exactly nothing. The server instead walks from the root and hides every branch that is neither an ancestor nor a descendant of the target.

Three pywin32 traps with this API

  1. doc.State is a property, not a method. doc.State() raises Member not found.

  2. Parameterised properties must be called through the Set/Get prefix: state.SetSelectionHidden(sel, True).

  3. Pointers obtained via GetActiveObject and via Dispatch do not bind the same way: the same IsModified is a method on one and a plain bool on the other. Read through the _member helper.

Not supported yet

Stated plainly so you do not waste time trying:

  • Clash Detective: not wired up. Clash lives outside InwOpState10 and needs Navisworks.Clash.Mfc.Interop — unverified, so it is not included.

  • TimeLiner / Quantification: likewise, not wired up.

  • state.CreatePicture: COM exposes it, but on Navisworks 2026 it raises Catastrophic failure for every parameter combination, even with the window visible. capture_screenshot therefore grabs the Roamer window directly through GDI (PrintWindow with PW_RENDERFULLCONTENT, falling back to BitBlt), with automatic detection of a blank white/black capture.

  • InwOaPath.Serialise: Navisworks returns Not implemented, which is why ids are index paths rather than serialised strings.

  • Creating or editing selection sets and viewpoints: read and apply only, no creation.

Testing

python test_connection.py                                  # auto-detects a model
python test_connection.py "D:\projects\bridge_super_t.nwd" # specific model

The script runs 36 real operations against a real model, including negative checks (bad id, colour out of range, wrong file extension must all fail) and one semantic check: an isolate that hides 0 branches counts as FAIL, because that is exactly the "reports ok but the screen does not change" failure mode.

Development

pip install -r requirements-dev.txt
ruff check .        # lint
pytest              # 62 tests, Navisworks NOT required

tests/ runs on a machine without Navisworks because the client connects lazily — registering tools never touches COM:

File

Covers

Needs Windows

test_server.py

Id parsing, value conversion, COM member access, file validation, the safe() contract

partly

test_tool_contracts.py

The tool surface the model sees: count, descriptions, JSON schemas

yes

test_repo_layout.py

Documentation and code stay in sync — the tool tables in both READMEs must match server.py

no

The part that needs a real Navisworks lives in test_connection.py and is run by hand.

See CONTRIBUTING.md for conventions and the pre-PR checklist, SECURITY.md for the trust model (this server gives a language model control of an application on your machine), and CHANGELOG.md for release history.

License

MIT © 2026 Xuân Tình