@rui.branco/revit-mcp 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Rui Branco
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,496 @@
1
+ # revit-mcp
2
+
3
+ [![Revit](https://img.shields.io/badge/Revit-2025%20%7C%202026%20%7C%202027-006666)](https://www.autodesk.com/products/revit/overview)
4
+ [![Platform](https://img.shields.io/badge/platform-Windows-0078D4)](#installation)
5
+ [![Node.js](https://img.shields.io/badge/node-%E2%89%A518-339933)](https://nodejs.org)
6
+ [![Tests](https://img.shields.io/badge/tests-507%20passing-success)](#development)
7
+ [![License](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
8
+
9
+ **Drive Autodesk Revit from Claude.** Query the model open on your desktop, edit
10
+ it, and produce a full drawing set in plain language — levels and elements,
11
+ sheets and schedules, views and PDF exports.
12
+
13
+ **Windows · Revit 2025, 2026 or 2027 · Node.js 18+.** The Revit add-in ships
14
+ precompiled, so there is nothing to build and no .NET SDK to install. `npm` is
15
+ never run at startup — the Desktop extension carries its dependencies, and the
16
+ Claude Code route fetches the package once via `npx`.
17
+
18
+ ## Installation
19
+
20
+ ### 1. Register the server
21
+
22
+ #### Claude Desktop — extension
23
+
24
+ 1. **[⬇ Download `revit-mcp.mcpb`](https://github.com/rui-branco/revit-mcp/releases/latest/download/revit-mcp.mcpb)**
25
+ 2. In Claude Desktop, open **Settings → Extensions**
26
+ 3. **Drag the `.mcpb` file onto that window**, then press **Install**
27
+
28
+ No JSON to edit and nothing to download at runtime: the extension carries the
29
+ server, its dependencies and the Revit add-in. Bridge URL and request timeout
30
+ are exposed as settings under **Settings → Extensions → Revit**.
31
+
32
+ > Double-clicking the file only works if Claude Desktop has registered the
33
+ > `.mcpb` file type; on many installs it has not, so use the drag-and-drop step
34
+ > above.
35
+
36
+ <details>
37
+ <summary>Or configure it by hand</summary>
38
+
39
+ Open **Settings → Developer → Edit Config** (that is
40
+ `%APPDATA%\Claude\claude_desktop_config.json`) and add the `revit` entry,
41
+ keeping any servers already there:
42
+
43
+ ```json
44
+ {
45
+ "mcpServers": {
46
+ "revit": {
47
+ "command": "npx",
48
+ "args": ["-y", "github:rui-branco/revit-mcp"]
49
+ }
50
+ }
51
+ }
52
+ ```
53
+
54
+ Then **quit Claude Desktop from the system tray** — closing the window is not
55
+ enough — and reopen it.
56
+
57
+ If the server shows as failed, Claude Desktop could not find `npx` on its
58
+ `PATH`. Use absolute paths instead (`where.exe npx.cmd` prints yours), or point
59
+ `command` at your `node.exe` with `args` of
60
+ `["C:\\path\\to\\revit-mcp\\index.js"]`.
61
+
62
+ </details>
63
+
64
+ #### Claude Code — one command
65
+
66
+ ```bash
67
+ claude mcp add revit --scope user -- npx -y github:rui-branco/revit-mcp
68
+ ```
69
+
70
+ `--scope user` enables it in every project; `--scope project` writes it to the
71
+ repo's `.mcp.json` instead, to share with a team. Check it with `claude mcp list`.
72
+
73
+ <details>
74
+ <summary><b>Other MCP clients</b></summary>
75
+
76
+ Any stdio MCP client works. Use `npx -y github:rui-branco/revit-mcp` as the
77
+ server command; it takes no arguments and needs no environment beyond the
78
+ optional [configuration](#configuration).
79
+
80
+ </details>
81
+
82
+ ### 2. Install the Revit add-in
83
+
84
+ Ask Claude to **install the Revit bridge**. That runs `revit_install_bridge`,
85
+ which copies the add-in and its `.addin` manifest into
86
+ `%APPDATA%\Autodesk\Revit\Addins\<version>\` for every Revit it finds. Revit
87
+ need not be running, and re-running is safe.
88
+
89
+ ### 3. Restart Revit and approve the add-in
90
+
91
+ Revit scans the Addins folder only at startup. On first load it shows a
92
+ **"Security - Unsigned Add-In"** dialog for `RevitMcpBridge` — this is expected,
93
+ the add-in is not code-signed. Choose **Always Load**; *Do Not Load* is
94
+ remembered and the bridge will never start.
95
+
96
+ ### 4. Verify
97
+
98
+ Open a model and ask Claude to run `revit_status`. It reports the bridge
99
+ version, Revit version and active document, confirming the chain end to end.
100
+
101
+ <details>
102
+ <summary><b>Installing manually, or uninstalling</b></summary>
103
+
104
+ From the package's `revit-bridge` directory:
105
+
106
+ ```powershell
107
+ .\install.ps1
108
+ ```
109
+
110
+ | Flag | Effect |
111
+ | --- | --- |
112
+ | `-RevitVersion 2026` | Only that version, even outside the default Program Files location. |
113
+ | `-Build` | Compile a fresh add-in with `dotnet build -c Release`. Needs the .NET SDK. |
114
+ | `-SkipBuild` | Never build. Already the default when the bundled add-in is present. |
115
+ | `-Uninstall` | Remove the manifest and the install folder. |
116
+ | `-Json` | Emit one JSON result object as the only stdout output — the mode the MCP tools use. |
117
+
118
+ Exit codes: `0` success, `1` unhandled failure, `2` no Revit found, `3` no
119
+ supported Revit version, `4` build failed or .NET SDK missing, `5` no add-in to
120
+ install. Runs under Windows PowerShell 5.1 and PowerShell 7.
121
+
122
+ To uninstall: ask Claude to uninstall the Revit bridge
123
+ (`revit_uninstall_bridge`) and restart Revit, then `claude mcp remove revit` or
124
+ delete the entry from `claude_desktop_config.json`.
125
+
126
+ </details>
127
+
128
+ <details>
129
+ <summary><b>Version support and prerequisites</b></summary>
130
+
131
+ | | |
132
+ | --- | --- |
133
+ | **OS** | Windows (Revit has no macOS build) |
134
+ | **Revit** | 2025, 2026 or 2027 |
135
+ | **Node.js** | 18 or newer |
136
+ | **.NET SDK** | Not required — only to build the C# side yourself |
137
+
138
+ One prebuilt add-in covers all three Revit versions: it is compiled against the
139
+ Revit 2025 reference assemblies, which load unchanged in 2026 and 2027. Tested
140
+ end to end against Revit 2027.3. **Revit LT is unsupported** (no add-in API) and
141
+ **2024 and earlier are refused** (.NET Framework 4.8 cannot load a
142
+ `net8.0-windows` assembly).
143
+
144
+ </details>
145
+
146
+ ## Usage
147
+
148
+ With a model open in Revit, ask in plain language:
149
+
150
+ ```
151
+ What levels are in this model?
152
+ How many walls are there, and what types?
153
+ Add a level at 3 m and a second at 6 m.
154
+ Set Comments to "reviewed" on everything I have selected.
155
+ Create sheets L.02.001 through L.02.010 with the A0 title block.
156
+ Make a plan of each level at 1:100 and place it on its sheet.
157
+ Schedule the planting by type, with quantities, and put it on L.09.017.
158
+ Export every sheet to one PDF.
159
+ ```
160
+
161
+ Two habits worth keeping:
162
+
163
+ - Ask for **one phase per call** — all ten sheets in one request, not ten
164
+ requests. Each call is one undo step.
165
+ - After a batch of writes, ask for `revit_diagnostics`. Warnings the bridge
166
+ resolved silently are recorded there and nowhere else.
167
+
168
+ ## Features
169
+
170
+ - **Query the model** — levels, categories, element filters, selection, and
171
+ parameters, with paging and total counts.
172
+ - **Edit the model** — create levels, walls, floors, toposolids and pipes,
173
+ place families, planting and openings, set parameters, delete elements.
174
+ - **Build drawings** — sheets, views, sections, plans, legends, schedules,
175
+ title blocks, and sheet collections.
176
+ - **Control graphics** — view templates, category overrides, crop regions,
177
+ display styles, sun and background settings.
178
+ - **Export** — view images and native multi-sheet PDFs.
179
+ - **Runs unattended** — modal dialogs and transaction warnings are answered by
180
+ the bridge and recorded for you to read back.
181
+ - **Hot reload** — rebuild the C# endpoints and reload them into a running
182
+ Revit with the model still open.
183
+ - **One call, one undo step** — every write is a single transaction group.
184
+
185
+ ## Tools
186
+
187
+ 88 tools, grouped below; every write is one undo step.
188
+
189
+ ### Conventions
190
+
191
+ - Units are Revit internal units (decimal feet) in both directions, unconverted: a level at 3 m is `9.8425`.
192
+ - `revit_query_elements` caps at 500 rows (default 100) and always returns the total match count.
193
+ - `revit_get_elements` returns only the parameters named in `params`; without it, identity fields only.
194
+ - One tool call is one undo step: each write is a transaction group, assimilated on success, rolled back on failure.
195
+ - `revit_create_sheets` skips sheet numbers that already exist rather than failing, so re-running is safe.
196
+ - A view lives on exactly one sheet; a placed view returns `VIEW_ALREADY_PLACED`, so use `revit_duplicate_view`.
197
+
198
+ <details>
199
+ <summary><b>Status &amp; diagnostics</b> (4 tools)</summary>
200
+
201
+ | Tool | Description |
202
+ | --- | --- |
203
+ | `revit_status` | Returns bridge status, Revit version, units, user, and the active document (`null` when none). |
204
+ | `revit_diagnostics` | Lists dialogs the bridge answered and warnings it resolved; check after a batch of writes. |
205
+ | `revit_set_auto_dismiss` | Toggles dialog auto-dismiss at runtime, optionally clearing the diagnostics buffer. |
206
+ | `revit_get_warnings` | Reads the warnings the model carries right now — Revit's own Review Warnings list. |
207
+
208
+ </details>
209
+
210
+ <details>
211
+ <summary><b>Query &amp; read</b> (6 tools)</summary>
212
+
213
+ | Tool | Description |
214
+ | --- | --- |
215
+ | `revit_list_levels` | Lists every level in the model as `{ id, name, elevation }`. |
216
+ | `revit_list_categories` | Lists every category present in the model with its element count, busiest first. |
217
+ | `revit_query_elements` | Filters elements by category, level and type name with `limit`/`offset`; returns the total. |
218
+ | `revit_get_elements` | Returns rows for specific element ids, carrying only the parameters named in `params`. |
219
+ | `revit_get_selection` | Returns what the user currently has selected in the Revit UI. |
220
+ | `revit_inspect_elements` | Measures elements: geometry, bounds and placement that `revit_get_elements` leaves out. |
221
+
222
+ </details>
223
+
224
+ <details>
225
+ <summary><b>Document &amp; project</b> (5 tools)</summary>
226
+
227
+ | Tool | Description |
228
+ | --- | --- |
229
+ | `revit_new_project` | Creates and opens a project from an `.rte` template; `overwrite: true` rebuilds it in place. |
230
+ | `revit_open_project` | Opens an existing `.rvt` as the active document; works with no document open. |
231
+ | `revit_save` | Saves the active document in place; a never-saved model returns `NOT_SAVEABLE`. |
232
+ | `revit_save_as` | Saves the active document to a new path; refuses an existing file unless `overwrite` is true. |
233
+ | `revit_close_project` | Closes the active document, activating another first; discards changes unless `save` is true. |
234
+
235
+ </details>
236
+
237
+ <details>
238
+ <summary><b>Parameters &amp; element edits</b> (5 tools)</summary>
239
+
240
+ | Tool | Description |
241
+ | --- | --- |
242
+ | `revit_set_parameters` | Sets one named instance parameter to one value across a batch of element ids. |
243
+ | `revit_create_project_parameter` | Creates a project parameter and binds it to categories; an existing binding is reported, not an error. |
244
+ | `revit_set_sheet_parameters` | Writes a different parameter value per sheet in one call; sheets missing it come back as `failed`. |
245
+ | `revit_delete_elements` | Deletes elements by id; Revit may remove dependents, so the count can exceed the request. |
246
+ | `revit_move_elements` | Moves elements by a vector in feet without rebuilding them. |
247
+
248
+ </details>
249
+
250
+ <details>
251
+ <summary><b>Geometry &amp; modelling</b> (10 tools)</summary>
252
+
253
+ | Tool | Description |
254
+ | --- | --- |
255
+ | `revit_create_levels` | Creates levels from a list of `{ name, elevation }` pairs. |
256
+ | `revit_create_walls` | Creates walls on a level from a wall type, a height and a list of `{ start, end }` curves. |
257
+ | `revit_create_toposolid` | Builds the site surface from survey points, as a Toposolid or a legacy TopographySurface. |
258
+ | `revit_flatten_toposolid` | Levels a region of the site surface to one elevation and reports the residual error. |
259
+ | `revit_create_floor` | Creates a floor from a closed boundary at the level's elevation; `offset` lifts it off the level. |
260
+ | `revit_create_directshape` | Builds elements from raw solids with no family: cylinder, box, sphere, cone, extrusion. |
261
+ | `revit_place_planting` | Creates one Planting element per point, a trunk cylinder with a crown sphere; no family needed. |
262
+ | `revit_create_pipes` | Models pipe runs as geometry, one element per run and a cylinder per segment; no MEP family. |
263
+ | `revit_place_sprinklers` | Places one sprinkler head element per point and reports the category Revit allowed. |
264
+ | `revit_excavate_toposolid` | Cuts a toposolid with the elements sunk into it — a pool, a basement — via `Toposolid.ExcavateBy`. |
265
+
266
+ </details>
267
+
268
+ <details>
269
+ <summary><b>Families &amp; content</b> (4 tools)</summary>
270
+
271
+ | Tool | Description |
272
+ | --- | --- |
273
+ | `revit_load_families` | Loads `.rfa` files into the project, returning family names and type ids; reloading is safe. |
274
+ | `revit_list_family_symbols` | Lists the family types loaded in the model, filtered by `category`, `family_name`, or both. |
275
+ | `revit_place_families` | Places one family instance per point; `z` is an absolute model elevation, read back per point. |
276
+ | `revit_place_openings` | Places doors and windows in walls; without `host_wall_id`, hosts in the nearest wall within 3 feet. |
277
+
278
+ </details>
279
+
280
+ <details>
281
+ <summary><b>Materials &amp; types</b> (8 tools)</summary>
282
+
283
+ | Tool | Description |
284
+ | --- | --- |
285
+ | `revit_list_materials` | Lists every material as `{ id, name, colorRgb, appearanceAssetId }`. |
286
+ | `revit_create_material` | Creates a material with colour, transparency, shininess and an optional `texture_path`. |
287
+ | `revit_set_material_texture` | Puts a bitmap on a material with `scale` (tile size in feet), `rotation` and `tint`. |
288
+ | `revit_assign_material` | Assigns an existing material through the type's compound structure or a material parameter. |
289
+ | `revit_create_wall_type` | Creates a wall type carrying a material and a thickness by duplicating an existing type. |
290
+ | `revit_create_floor_type` | Creates a floor type carrying a material and a thickness by duplicating an existing type. |
291
+ | `revit_get_material_appearance` | Reads the appearance asset behind a material — the rendered look, not the shading colour. |
292
+ | `revit_set_material_appearance` | Edits a material's appearance asset by naming the properties to write. |
293
+
294
+ </details>
295
+
296
+ <details>
297
+ <summary><b>Views</b> (10 tools)</summary>
298
+
299
+ | Tool | Description |
300
+ | --- | --- |
301
+ | `revit_list_views` | Lists every non-template view with its type, sheet placement and direction vectors. |
302
+ | `revit_create_plan_view` | Creates a plan on a level; `view_family_type` names the Site, Ceiling or Structural plan type. |
303
+ | `revit_create_drafting_view` | Creates a drafting view: linework and notes, no model geometry. |
304
+ | `revit_create_3d_view` | Creates an isometric or perspective 3D view from `eye`/`target`; returns `modelExtents`. |
305
+ | `revit_create_section_view` | Creates a section from an origin, a look direction, and width/height/depth in feet. |
306
+ | `revit_list_legends` | Lists the legend views as `{ id, name, scale }`; empty means the document has none. |
307
+ | `revit_create_legend` | Creates a legend by duplicating an existing one, else `NO_LEGEND_TO_DUPLICATE`. |
308
+ | `revit_duplicate_view` | Duplicates a view as Duplicate, WithDetailing or AsDependent. |
309
+ | `revit_set_view_scale` | Sets 1:X on one view or a batch; a perspective view returns `PERSPECTIVE_VIEW_HAS_NO_SCALE`. |
310
+ | `revit_scale_perspective_crop` | Resizes a perspective view on its sheet with proportions locked; the camera is untouched. |
311
+
312
+ </details>
313
+
314
+ <details>
315
+ <summary><b>View graphics &amp; templates</b> (14 tools)</summary>
316
+
317
+ | Tool | Description |
318
+ | --- | --- |
319
+ | `revit_set_view_style` | Sets a view's display style and detail level, both read back off the view. |
320
+ | `revit_set_view_background` | Sets a 3D view's background to `sky`, `gradient` or `image`; a non-3D view is a `BAD_REQUEST`. |
321
+ | `revit_hide_view_categories` | Hides categories in one view; `['annotation']` is shorthand for levels, grids, sections and more. |
322
+ | `revit_override_view_categories` | Overrides category line colour, weight and transparency in one view; `dry_run` defaults to true. |
323
+ | `revit_set_view_sun` | Moves the sun by `azimuth`/`altitude` in degrees or by `date`/`time`; settings can be shared. |
324
+ | `revit_get_view_graphics` | Returns style, detail level, template, shadow sliders, background and a cast-shadows probe. |
325
+ | `revit_set_view_graphics` | Sets style, detail level, shadow/sunlight intensity and cast shadows in one undo step. |
326
+ | `revit_get_view_graphics_command_status` | Reports what became of the last posted shadows command, with a fresh parameter probe. |
327
+ | `revit_capture_view_template` | Turns a view into a view template controlling `graphics`, `shadows` or `all`. |
328
+ | `revit_apply_view_template` | Applies or assigns one template to many views, validated first; `dry_run` defaults to true. |
329
+ | `revit_list_view_templates` | Lists view templates with id, name, view type, and the parameters each one controls. |
330
+ | `revit_get_view_crop` | Reads the crop of one view or a batch: whether it is on, drawn, and where it sits. |
331
+ | `revit_set_view_crop` | Crops one view or a batch to a region of the model, in feet. |
332
+ | `revit_hide_elements_in_view` | Hides elements permanently in one view — the 'Hide in View > Elements' override. |
333
+
334
+ </details>
335
+
336
+ <details>
337
+ <summary><b>Sheets &amp; title blocks</b> (11 tools)</summary>
338
+
339
+ | Tool | Description |
340
+ | --- | --- |
341
+ | `revit_list_titleblocks` | Lists the title block family types loaded in the model, the source of the title block id. |
342
+ | `revit_inspect_titleblock_family` | Reads what a title block prints: logo, placeholders, labels and their paper sizes. |
343
+ | `revit_edit_titleblock_family` | Edits a title block family: strip logos and placeholders, retext, resize labels, add notes. |
344
+ | `revit_list_sheets` | Lists every sheet as `{ id, number, name }`, ordered by sheet number. |
345
+ | `revit_create_sheets` | Creates sheets from `{ number, name }` pairs; numbers that exist are skipped and reported. |
346
+ | `revit_place_views_on_sheets` | Places views on sheets in one batch, handling schedules as schedule instances. |
347
+ | `revit_get_sheet_layout` | Measures where everything on a sheet actually sits, in feet on the paper. |
348
+ | `revit_set_viewport_position` | Moves one viewport on its sheet. |
349
+ | `revit_list_sheet_collections` | Lists the sheet collections — the collapsible groups under Sheets in the Project Browser. |
350
+ | `revit_set_sheet_collections` | Puts sheets into native sheet collections so the browser shows collapsible groups. |
351
+ | `revit_get_browser_organization` | Inspects how the Project Browser currently groups the Sheets section. |
352
+
353
+ </details>
354
+
355
+ <details>
356
+ <summary><b>Schedules &amp; annotation</b> (6 tools)</summary>
357
+
358
+ | Tool | Description |
359
+ | --- | --- |
360
+ | `revit_create_schedule` | Creates a schedule for a category with named fields; unknown names are skipped and listed. |
361
+ | `revit_configure_schedule` | Sets itemisation, grouping, sorting, totals, headings and widths; `dry_run` defaults to true. |
362
+ | `revit_set_schedule_position` | Moves a schedule instance on a sheet by its top-left corner; `dry_run` defaults to true. |
363
+ | `revit_draw_detail_lines` | Draws detail lines in a drafting view or plan; an unknown line style falls back and is reported. |
364
+ | `revit_add_text_notes` | Adds text notes to a view; `size` is paper feet and duplicates a text type when needed. |
365
+ | `revit_read_schedule` | Reads the text of a schedule, cell by cell. |
366
+
367
+ </details>
368
+
369
+ <details>
370
+ <summary><b>Export</b> (2 tools)</summary>
371
+
372
+ | Tool | Description |
373
+ | --- | --- |
374
+ | `revit_export_view_image` | Exports views to PNG/JPEG; Revit renames the file, so open the returned `path`. |
375
+ | `revit_export_pdf` | Exports named views and sheets through Revit's own PDF exporter — vector, at real sheet size. |
376
+
377
+ </details>
378
+
379
+ <details>
380
+ <summary><b>Bridge install &amp; reload</b> (3 tools)</summary>
381
+
382
+ | Tool | Description |
383
+ | --- | --- |
384
+ | `revit_install_bridge` | Installs the bundled add-in into Revit's Addins folder; works with Revit closed. |
385
+ | `revit_reload_bridge` | Reloads the add-in's endpoint logic from disk without restarting Revit. |
386
+ | `revit_uninstall_bridge` | Removes the add-in and its manifest from Revit's Addins folder. |
387
+
388
+ </details>
389
+
390
+ ## Configuration
391
+
392
+ | Variable | Default | Purpose |
393
+ | --- | --- | --- |
394
+ | `REVIT_MCP_URL` | `http://localhost:48884/revit-mcp` | Bridge base URL. Change it for a second Revit instance. |
395
+ | `REVIT_MCP_TIMEOUT` | `30000` | Per-request timeout, in milliseconds. |
396
+ | `REVIT_MCP_BRIDGE_TIMEOUT_MS` | `30000` | Read by the add-in: how long a request waits for Revit's main thread. Clamped to 1–600 s. |
397
+
398
+ ## Unattended operation
399
+
400
+ A modal dialog parks Revit's main thread, and the bridge can only run work when
401
+ that thread goes idle — so with nobody at the keyboard, one dialog would fail
402
+ every call from then on. The bridge therefore answers them itself:
403
+
404
+ - **Dialogs are auto-dismissed** (on by default). Ordinary dialogs get OK;
405
+ anything that sounds destructive — delete, remove, overwrite, discard, unload,
406
+ save, close — gets Cancel.
407
+ - **Transaction warnings are resolved** by a failure preprocessor: Revit's own
408
+ resolution where one exists, otherwise the warning is deleted. Genuine
409
+ **errors** are never suppressed — they fail the transaction and roll it back.
410
+
411
+ **`revit_diagnostics` is the other half of that trade.** A silently resolved
412
+ warning often means the model did something you did not ask for. Every
413
+ dismissed dialog and resolved warning goes to a 200-entry ring buffer, readable
414
+ only there. Check it after a batch of writes; `revit_set_auto_dismiss` with
415
+ `clear: true` empties it first so the next read covers only that batch.
416
+
417
+ Turn auto-dismiss **off** (`revit_set_auto_dismiss`, `enabled: false`) when
418
+ someone is working in Revit at the same time. Calls then fail with `REVIT_BUSY`
419
+ until the dialog is answered by hand.
420
+
421
+ ## Troubleshooting
422
+
423
+ | Symptom | Cause | Fix |
424
+ | --- | --- | --- |
425
+ | `connection refused` | Revit is not running, or is running without the add-in. | Start Revit. Check `%APPDATA%\Autodesk\Revit\Addins\<version>\` for `RevitMcpBridge.addin`. If the dialog was answered *Do Not Load*, reinstall and choose **Always Load**. |
426
+ | `HTTP 404 … add-in is not loaded` | Manifest present, assembly failed to load. | The DLL was blocked by Windows (file properties → **Unblock**), or a stale install. Reinstall, restart Revit. Log: `%LOCALAPPDATA%\RevitMcpBridge\bridge.log`. |
427
+ | `did not answer within Nms` | Revit is busy or showing a modal dialog. | Check `revit_diagnostics` — a dialog seen but unanswered is recorded with `answered: false`. Otherwise clear the screen in Revit and retry, or raise `REVIT_MCP_TIMEOUT`. A timed-out write may still have completed. |
428
+ | `no active document` | Revit is on the start page or between documents. | Open a model and retry. `revit_status` deliberately tolerates this, which distinguishes it from the others. |
429
+
430
+ ## Security
431
+
432
+ - The add-in runs **inside `Revit.exe` with full Revit API access**. It can
433
+ read, modify and delete anything in the open model, and open, save or close
434
+ documents. There is no read-only mode.
435
+ - It opens an HTTP listener bound to the `127.0.0.1` literal (not a wildcard) on
436
+ port `48884`. Not reachable from the network, but **unauthenticated**: any
437
+ local process can drive your Revit session.
438
+ - The add-in is **not code-signed**, hence Revit's unsigned add-in dialog.
439
+ - **Auto-dismiss answers Revit's dialogs for you.** Turn it off when you are at
440
+ the keyboard, and read `revit_diagnostics` after writes.
441
+ - **Work on a copy** of any model you care about.
442
+
443
+ Report a vulnerability privately through
444
+ [GitHub security advisories](https://github.com/rui-branco/revit-mcp/security/advisories/new).
445
+
446
+ ## Development
447
+
448
+ ```bash
449
+ git clone https://github.com/rui-branco/revit-mcp.git
450
+ cd revit-mcp
451
+ npm install
452
+ npm test
453
+ ```
454
+
455
+ `npm test` is `node --test` over `tests/` — 507 tests. It touches no Revit and
456
+ opens no socket: the HTTP layer is stubbed and the tools are exercised through a
457
+ real MCP client over an in-memory transport.
458
+
459
+ The C# side needs the **.NET SDK** (end users do not):
460
+
461
+ ```bash
462
+ npm run build:bridge # dotnet build -c Release, then refresh revit-bridge/dist/
463
+ cd revit-bridge && .\install.ps1 -Build # install that local build
464
+ ```
465
+
466
+ `revit-bridge/dist/` is the compiled add-in that ships with the package, and is
467
+ committed on purpose — that is what spares end users the SDK. `prepublishOnly`
468
+ re-runs `build:bridge` and the tests, so a stale binary cannot be published.
469
+
470
+ The add-in is **two assemblies**: `RevitMcpBridge.dll` is the loader Revit pins
471
+ for the session (HTTP listener, main-thread pump, dialog handling), and
472
+ `RevitMcpBridge.Handlers.dll` is the routing and endpoint logic, loaded into a
473
+ collectible `AssemblyLoadContext` from a shadow copy. One build produces both.
474
+
475
+ That split is what makes **hot reload** work: rebuild, call
476
+ `revit_reload_bridge`, and the new logic is live with the model still open. If
477
+ the response reports `unloadedPrevious: false` the new logic is still live, but
478
+ the old context is leaking and is worth a look.
479
+
480
+ ### Further reading
481
+
482
+ | Document | Covers |
483
+ | --- | --- |
484
+ | [`revit-bridge/README.md`](revit-bridge/README.md) | Add-in internals: the `ExternalEvent` pump, the transaction model, the full HTTP contract. |
485
+ | [`docs/documentation.md`](docs/documentation.md) | Sheets, schedules and drawing-set production. |
486
+ | [`docs/professional-graphics.md`](docs/professional-graphics.md) | View templates, category overrides, display styles. |
487
+ | [`docs/professional-qa.md`](docs/professional-qa.md) | Warnings, bounds and drawing-set QA. |
488
+
489
+ ## Contributing
490
+
491
+ Issues and pull requests are welcome. Please run `npm test` before opening a PR,
492
+ and `npm run build:bridge` as well if you changed the C# side.
493
+
494
+ ## License
495
+
496
+ MIT — see [LICENSE](LICENSE).
package/index.js ADDED
@@ -0,0 +1,124 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
+ import { pathToFileURL } from "url";
6
+ import { createBridge } from "./lib/bridge.js";
7
+ import { registerReadTools } from "./lib/tools/read.js";
8
+ import { registerWriteTools } from "./lib/tools/write.js";
9
+ import { registerParameterTools } from "./lib/tools/parameters.js";
10
+ import { registerModelTools } from "./lib/tools/model.js";
11
+ import { registerGeometryTools } from "./lib/tools/geometry.js";
12
+ import { registerMaterialTools } from "./lib/tools/materials.js";
13
+ import { registerMaterialAppearanceTools } from "./lib/tools/material-appearance.js";
14
+ import { registerDocumentTools } from "./lib/tools/document.js";
15
+ import { registerSheetTools } from "./lib/tools/sheets.js";
16
+ import { registerSheetCollectionTools } from "./lib/tools/sheet-collections.js";
17
+ import { registerTitleblockTools } from "./lib/tools/titleblocks.js";
18
+ import { registerViewTools } from "./lib/tools/views.js";
19
+ import { registerGraphicsTools } from "./lib/tools/graphics.js";
20
+ import { registerDetailTools } from "./lib/tools/detail.js";
21
+ import { registerDocumentationTools } from "./lib/tools/documentation.js";
22
+ import { registerDiagnosticsTools } from "./lib/tools/diagnostics.js";
23
+ import { registerQualityTools } from "./lib/tools/quality.js";
24
+ import { registerReloadTools } from "./lib/tools/reload.js";
25
+ import { registerInstallTools } from "./lib/tools/install.js";
26
+
27
+ const instructions = `# Revit MCP
28
+
29
+ This MCP drives the model that is open in Autodesk Revit on this machine. Node
30
+ talks to a bridge add-in running inside Revit over HTTP on localhost.
31
+
32
+ ## When to use these tools
33
+
34
+ Use these tools whenever the user wants to:
35
+ - Ask about the open model (e.g. "what levels are there", "how many walls", "what's selected")
36
+ - Read parameters off elements (e.g. "what's the Mark on these doors")
37
+ - Change the model (e.g. "add a level at 3m", "set Comments on the selection")
38
+ - Populate a project (e.g. "build the site surface", "pave the terrace", "plant trees along the path")
39
+ - Start a project or set up drawings (e.g. "new project from the metric template", "create sheets A101-A105")
40
+ - Fill sheets (e.g. "make a plan of each level and put it on its sheet", "schedule the planting")
41
+ - Draw details (e.g. "a drafting view of the paving build-up, with the layers labelled")
42
+ - Tidy a drawing set (e.g. "set every plan to 1:100", "add a Phase parameter to the sheets and stamp L.02 on them")
43
+ - Open, save or close a model (e.g. "open C:\\Projects\\House.rvt", "save the model", "close without saving")
44
+
45
+ ## IMPORTANT
46
+
47
+ - Run revit_status first if anything fails — it tells you whether Revit is even reachable.
48
+ - If nothing is listening, the bridge add-in is probably not installed: run revit_install_bridge, then tell the user to restart Revit.
49
+ - revit_query_elements caps at 500 rows. It always returns the total match count: if total is larger than what you got, say so instead of pretending the list is complete.
50
+ - revit_get_elements returns only the parameters you name in params. Ask for what you need, not everything.
51
+ - Every write tool is one undo step for the user. Do not split one logical change across several calls.
52
+ - revit_new_project and revit_open_project are the tools that work with no document open; everything else needs a model.
53
+ - The bridge answers Revit's modal dialogs and resolves its transaction warnings by itself, so an unattended run never stalls. Call revit_diagnostics after a batch of writes: a warning it resolved silently may mean the model did something you did not intend.
54
+ - revit_close_project discards unsaved changes unless save is true. Call revit_save first when the work matters.
55
+ - One project lives at one path and is rebuilt in place. When an attempt goes wrong, call revit_new_project again with the SAME save_path and overwrite: true — it closes that project in Revit, deletes the file and its backups, and builds it again. Never version a project into a new filename per iteration.
56
+ - Call revit_list_titleblocks before revit_create_sheets: a sheet needs a title block, and an empty list means one has to be loaded in Revit first.
57
+ - A sheet stays empty until something is on it: revit_place_views_on_sheets is what fills it, and it handles schedules as well as views.
58
+ - A view lives on exactly one sheet. To show the same view twice, revit_duplicate_view first.
59
+ - Revit's API cannot create the first legend in a document. revit_list_legends says what exists and revit_create_legend duplicates one; when there is none, ask the user to make one legend in the Revit UI (View tab > Legends > Legend) rather than passing off a drafting view as a legend.
60
+ - Collapsible sheet groups ARE settable: revit_set_sheet_collections builds Revit's own sheet collections, so the grouping is in the model when it opens, with nothing to set up in the UI. It is a dry run until you pass dry_run: false, it reuses a collection of the same name rather than duplicating it, and it never renames or renumbers a sheet. revit_list_sheet_collections reads what exists. That is a different thing from the browser organisation scheme below, which groups by a PARAMETER and still cannot be applied from the API.
61
+ - Grouping sheets into folders in the Project Browser is not settable from the API. revit_get_browser_organization shows the scheme in force, the scheme names the document has and the folders each sheet really sits in, and it reports canApplyFromApi false — that is the API's limit, not a bug to work around. Add the field with revit_create_project_parameter and stamp it with revit_set_sheet_parameters, then tell the user to point the browser organisation at that parameter once in the UI (right-click Sheets > Browser Organization). Stamping the parameter is NOT the grouping being applied; never report it as such.
62
+ - Load real families before falling back to primitives. revit_load_families takes .rfa paths from Autodesk's library — C:\\ProgramData\\Autodesk\\RVT <year>\\Libraries\\<language>\\, under Planting\\, Site\\Accessories\\, Lighting\\Architectural\\External\\, Furniture\\, Doors\\, Windows\\ — and a library one release behind the running Revit still loads, because Revit upgrades it. That is the difference between a model made of real content and one made of spheres on sticks. Only when the library is genuinely not installed do revit_place_planting and revit_create_directshape become the answer; there are no MEP families either, so revit_create_pipes and revit_place_sprinklers model irrigation that way regardless.
63
+ - revit_list_family_symbols is what the ids to place come from, and it filters by category, by family_name, or both. revit_place_families takes z as an absolute model elevation like everything else here and reports placedZ read back off each instance — check it rather than assuming.
64
+ - Doors and windows are NOT revit_place_families: they have to cut the wall they sit in, and only revit_place_openings hosts them. Unhosted, a door stands in front of an uncut wall and the building stays a sealed box. It finds the nearest wall to each point for you and reports which one it used.
65
+ - You can SEE the model: revit_create_3d_view (read modelExtents from it, then aim eye/target), revit_set_view_style with Realistic, then revit_export_view_image. Open the file reported under 'path', never the one you asked for — Revit renames it. This is not a photoreal render; the API cannot start Revit's raytracer. Cast shadows are revit_set_view_graphics, which probes whether the view's shadows parameter is writable and either writes it or posts Revit's own shadows command — read the method it reports rather than assuming it worked.
66
+ - Three more calls are what stop an export reading as a screenshot of Revit: revit_set_view_background (a 3D view is drawn on flat dark slate until you give it a sky), revit_hide_view_categories with ['annotation'] (level datums and section marks float through an otherwise finished isometric), and revit_set_view_sun (the direction everything is lit from). Sun settings can be shared between views — check sharesSettings in the response.
67
+ - Shadows and repeatable graphics: revit_get_view_graphics diagnoses what a view can actually take, revit_set_view_graphics sets style, detail, the lighting sliders and cast shadows, and revit_capture_view_template + revit_apply_view_template give a whole set of views the same graphics in one undo step. Be straight with the user about two things: a shadows write that reports method 'posted-command' is PENDING and unverified, not done; and a template captured from a view whose shadows were off turns shadows on nowhere.
68
+ - A PNG is for looking at the model; a PDF is the deliverable. revit_export_view_image captures the view as Revit draws it on screen, so a dark view background comes out as a black-paper negative of the drawing and no setting makes that printable. revit_export_pdf is Revit's own exporter: vectors, white paper, real sheet size. Use it for anything anyone will read or plot, name the views and sheets explicitly, and trust the manifest's path/bytes — they are read off the disk, and a missing file is an error rather than a success.
69
+ - A drawing that comes out tiny in the corner of its sheet is almost always an uncropped view: section marks and elevation markers sit far outside the building and the viewport is sized to all of it. The order that fixes it is revit_set_view_crop (model feet, dry run first), then revit_set_view_scale, then revit_get_sheet_layout to MEASURE where things landed, then revit_set_viewport_position (paper feet, centre of the box) if it still needs moving. Never guess a viewport coordinate — read the layout first. revit_hide_elements_in_view takes out what is merely in the way without deleting it, and revit_read_schedule is the only way to see what a schedule actually says. A PERSPECTIVE view breaks that order at one step: it has no view scale, so revit_set_view_scale refuses it with PERSPECTIVE_VIEW_HAS_NO_SCALE and revit_scale_perspective_crop re-sizes it instead — one multiplier, proportions locked, same shot, camera untouched. Reframing a perspective is still revit_set_view_crop; the two are not interchangeable.
70
+ - A schedule on a sheet is not a viewport: it is anchored by its top-left corner, so it moves with revit_set_schedule_position (the instance id from revit_get_sheet_layout, not the schedule id), never with revit_set_viewport_position. A quantities schedule that lists every instance on its own row is not broken data — that is "Itemize every instance", and revit_configure_schedule with itemized false plus group_by is what collapses it to one row per type with a count. Check bodyRows before and after: that is the proof it worked.
71
+ - The documentation write tools default dry_run TRUE, unlike everything else here. That is deliberate: they edit drawings a human has approved. Read the dry run's 'before', then call again with dry_run false. A crop refused with CROP_CONTROLLED_BY_TEMPLATE means the view's template owns it — say so and offer to change the template, rather than working around it.
72
+ - Elements built that way schedule as one lump unless you say what they are. Put the species, the type or the zone in each entry's comments and mark — that is what makes a quantities schedule a breakdown.
73
+ - Nothing has a material unless you give it one, and the whole model renders grey until you do. Materials go on at BUILD time for geometry (material_id on revit_create_directshape, revit_place_planting, revit_create_pipes, revit_place_sprinklers) because the solid carries it; walls and floors take theirs from their type, so make one with revit_create_wall_type / revit_create_floor_type first. revit_assign_material is only for elements that already exist and can take one — it reports the ones that cannot.
74
+ - Changing how an EXISTING material looks in a render is revit_get_material_appearance then revit_set_material_appearance: read the appearance asset's real property names and types, then patch the ones you name. Never guess a property name — the asset's schema decides them — and never read a null appearanceAssetId as "it needs a texture": create_generic gives that material a textureless Generic asset to patch. The asset is copied before it is patched, so one material's new finish can never appear on another's surfaces.
75
+ - Paving on graded terrain overlaps the surface unless you deal with it: revit_flatten_toposolid levels the region under it, and revit_create_floor takes an offset to lift the slab clear.
76
+ - All lengths, coordinates and elevations are Revit internal units: decimal feet. Sheet coordinates are feet on the paper, not model feet.
77
+ - Element ids are integers and only valid for the currently open document.
78
+ `;
79
+
80
+ export function createRevitServer(bridge = createBridge()) {
81
+ const server = new McpServer({ name: "revit", version: "1.0.0" }, { instructions });
82
+
83
+ registerReadTools(server, bridge);
84
+ registerWriteTools(server, bridge);
85
+ registerParameterTools(server, bridge);
86
+ registerModelTools(server, bridge);
87
+ registerGeometryTools(server, bridge);
88
+ registerMaterialTools(server, bridge);
89
+ registerMaterialAppearanceTools(server, bridge);
90
+ registerDocumentTools(server, bridge);
91
+ registerSheetTools(server, bridge);
92
+ registerSheetCollectionTools(server, bridge);
93
+ registerTitleblockTools(server, bridge);
94
+ registerViewTools(server, bridge);
95
+ registerGraphicsTools(server, bridge);
96
+ registerDetailTools(server, bridge);
97
+ registerDocumentationTools(server, bridge);
98
+ registerDiagnosticsTools(server, bridge);
99
+ registerQualityTools(server, bridge);
100
+ registerReloadTools(server, bridge);
101
+
102
+ // The install tools shell out to revit-bridge/install.ps1 and never touch the
103
+ // bridge, so they keep working when Revit is closed or the add-in is missing.
104
+ registerInstallTools(server);
105
+
106
+ return server;
107
+ }
108
+
109
+ async function main() {
110
+ const server = createRevitServer();
111
+ const transport = new StdioServerTransport();
112
+ await server.connect(transport);
113
+
114
+ process.stderr.write("Revit MCP server running on stdio\n");
115
+ }
116
+
117
+ // Only start the server when run as the entry point — the tests import
118
+ // createRevitServer() and must not have stdio hijacked from under them.
119
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
120
+ main().catch((err) => {
121
+ process.stderr.write(`Fatal: ${err.message}\n`);
122
+ process.exit(1);
123
+ });
124
+ }