Skip to content

About

A custom Model Context Protocol server that lets Claude (or any MCP client) drive Revit 2025- 2027 — read the model, query, create & edit elements, and run multi-step operations as a single undoable Transaction.

Resources

Stars

17 stars

Watchers

2 watching

Forks

Repository files navigation

Revit MCP Server

A custom Model Context Protocol server that lets Claude (Desktop, Code, or any MCP client) drive Autodesk Revit 2025, 2026 & 2027 — read the model, create & edit elements, and run multi-step operations as a single undoable Transaction.

Author: KenLP

Claude Desktop / Claude Code  ──stdio──▶  revit-mcp-server (Node)  ──HTTP──▶  RevitMCPAddin  ──ExternalEvent + Transaction──▶  Revit 2025/2026/2027
  • C# addin (.NET 8 for R2026, .NET 10 for R2027) runs inside Revit, owns transactions, marshals work onto the main UI thread via IExternalEventHandler. Auto-assigns a unique port per Revit version so multiple versions can run side-by-side.
  • TypeScript MCP server is a thin stdio bridge: every tool just forwards to a small HTTP API on the addin.
  • Curated, schema-validated tool surface — no eval-style escape hatch, every write is a named Transaction you can review and undo.
  • Batch transaction pattern — fold N steps into one atomic Revit undo entry; rollback on first failure.

For the design rationale and the answer to "why not just wrap every Revit API?", read docs/API_COVERAGE.md.

Status

v0.8.43 — 98 C# commands (97 exposed as MCP tools + 1 hidden) + 1 batch tool + 2 workflow recipes = 100 MCP tools. Hidden = create_spot_elevation, registered for HTTP /mcp use but off the MCP tool surface. More HTTP-only commands can be added through opt-in command packs. Supports Revit 2025 (.NET 8), Revit 2026 (.NET 8) and Revit 2027 (.NET 10) with auto-port assignment for side-by-side use. Features: dry-run mode, structured diffs, auth token, per-tool risk levels, Family & FamilySymbol rename, linked-file element reading, clash/clearance detection (host + linked-file raycast, multi-point centreline sampling), view image export, view manipulation (duplicate, section-box, isolate), MEP Space listing, element type swapping, view template application, parameter copy across elements, schedule filters/sort/CSV export, level elevation editing, PDF export, room containment (phase-aware FromRoom/ToRoom/Room batch lookup for doors, furniture, fixtures), annotation tagging (tag single element, tag-all-not-tagged by category, list tags in view), model health report (one-shot scorecard: warnings + warning/element ratio, file size, imported/linked CAD, PDF/raster, RVT links, point clouds, in-place families, groups, unused views, purgeable elements), workset audit (per-workset element counts, empty-workset detection). See docs/ROADMAP.md.

Layer Build target Status
Revit addin Revit 2025 / .NET 8 ✅ CI-tested
Revit addin Revit 2026 / .NET 8 ✅
Revit addin Revit 2027 / .NET 10 ✅
MCP server Node 22 / TypeScript 5 ✅

Tool surface (97 commands + 1 batch + 2 recipes = 100 MCP tools)

Diagnostics (3)

revit_ping | revit_get_version | revit_get_document_info

Inspection / Introspection (31 read-only)

revit_list_elements | revit_get_element_info | revit_find_element_by_unique_id | revit_query_where | revit_find_elements | revit_get_parameter | revit_list_levels | revit_list_wall_types | revit_list_floor_types | revit_list_categories | revit_list_families | revit_list_family_types | revit_list_sheets | revit_list_rooms | revit_list_spaces | revit_list_materials | revit_list_phases | revit_list_view_templates | revit_get_views | revit_get_active_view | revit_get_selected_elements | revit_get_linked_files | revit_get_element_geometry | revit_get_linked_elements | revit_get_view_image | revit_get_element_rooms | revit_export_view_pdf | revit_get_model_health | revit_get_worksets | revit_get_schedule_data | revit_get_doors

Coordination / Clash (1 read-only)

revit_check_clearance

Creation: Architecture (10 write)

revit_create_wall | revit_create_floor | revit_create_level | revit_create_grid | revit_create_room | revit_create_column | revit_create_beam | revit_create_ceiling | revit_create_opening_in_wall | revit_place_family_instance

Creation: Documentation (9 write)

revit_create_sheet | revit_place_view_on_sheet | revit_create_floor_plan_view | revit_create_section_view | revit_create_3d_view | revit_create_perspective_view | revit_create_schedule | revit_tag_element | revit_create_text_note

Annotation & Detailing (5 read/write)

revit_tag_all_in_view | revit_get_tags_in_view | revit_create_aligned_dimension | revit_create_detail_line | revit_create_filled_region

Edit: Parameters, Types & Naming (9 write)

revit_set_parameter | revit_set_parameter_batch | revit_update_where | revit_import_parameters | revit_rename_element | revit_change_element_type | revit_apply_view_template | revit_copy_parameters | revit_configure_schedule

Edit: Levels & Export (1 write)

revit_set_level_elevation

Edit: Transform (5 write)

revit_move_element | revit_rotate_element | revit_copy_element | revit_mirror_element | revit_array_linear

Edit: Delete & Group (3 write)

revit_delete_elements | revit_group_elements | revit_ungroup_elements

Family (2 write)

revit_load_family | revit_duplicate_family_type

View Manipulation (12 write / UI)

revit_open_document | revit_open_view | revit_set_view_detail_level | revit_hide_elements_in_view | revit_unhide_elements_in_view | revit_select_elements | revit_zoom_to_elements | revit_apply_view_filter | revit_color_override_by_param | revit_override_element_graphics | revit_duplicate_view | revit_set_section_box | revit_isolate_elements_in_view

Batch (1)

revit_batch — run multiple commands inside ONE Revit Transaction (single undo entry).

Workflow recipes (2)

revit_recipe_model_health_triage — read-only: health scan → prioritized, actionable triage list. revit_recipe_clash_review — read-only: coordination clash sweep across many element-set pairs (host vs host or host vs linked RVT) → consolidated, prioritized clash report. Recipes orchestrate verified kernel commands in the Node layer; they are the P4 workflow surface.

Full schemas and examples: docs/COMMANDS.md.

Ribbon panel (optional, and inert on its own)

Installing the add-in also adds one ribbon tab, AutoAudit, with a dockable panel. It is not a feature of this add-in by itself: the panel is a WebView2 browser pointed at a local service, so with nothing listening on the port it shows a connection error. That is the service being absent, not the add-in being broken.

Ribbon tab Loads by default Service Config file (per Revit version)
AutoAudit http://127.0.0.1:8601/ui/ KenLP/autoaudit-bim revit-mcp-panel.json

Override the URL by dropping the config file into %APPDATA%\Autodesk\Revit\Addins\<version>\:

{ "url": "http://127.0.0.1:9000/ui/" }

The installer never writes or deletes these files, so a URL you set survives every upgrade. A missing or malformed file falls back to the default above.

A blank panel or a connection error almost always means the service is not running, or its web UI was never built — check the service's own README for the build step before filing anything here.

A second panel of your own (opt-in)

The add-in can host one more dockable panel for any local web UI you run, but only if you ask for it: nothing appears unless revit-mcp-extra-panel.json exists in the same folder and names a URL.

{
  "url":   "http://127.0.0.1:9000/ui/",   // required — http(s) only
  "label": "My Tool",                     // pane + button name (default "Extra Panel")
  "tab":   "My Tool",                     // ribbon tab (default = label; "AutoAudit" shares that tab)
  "enabled": true                         // set false to hide it without deleting the file
}

No file, no url, or "enabled": false → no tab, no pane, nothing registered. The add-in ships nothing for this panel to show; it is only a browser onto the URL you give it.

Ignore the tab entirely if you only want the MCP tool surface. Panels are additive: nothing in the tool surface depends on them, and a panel that fails to load cannot take the MCP server down with it.

Write gates: audit log and read-back (server-side, on by default)

Every client — MCP over stdio, or any program calling the HTTP API directly — goes through the same two gates in the add-in. Both are additive: the envelope, ok and every existing field are unchanged.

Audit log. One JSON line per command in %APPDATA%\RevitMCP\audit\audit-YYYY-MM-DD.jsonl (one file per UTC day); a batch writes one line per step plus a summary line sharing batchId. Reads are not logged unless asked. By default params are stored only as a SHA-256 hash and the document only as a hash of its path, so the log carries no model content:

{"ts":"2026-10-04T12:00:00.123Z","id":"6f1c…","revit":"2027","docHash":"sha256:…","command":"set_parameter",
 "kind":"ModelWrite","batchId":null,"step":null,"dryRun":false,"ok":true,"errorCode":null,"durationMs":42,
 "paramsHash":"sha256:…","affected":{"created":[],"modified":[123456],"deleted":[]},
 "verify":{"status":"passed","detail":null},"client":null,"trace":null}

A write failure in the log never fails the command; it shows up on GET /health as audit.lastError.

Read-back. After a write commits, the add-in re-reads the model and reports what is actually stored in data.verify = { status, detail }: passed, failed, skipped (the check could not run, or a later step of the same batch changed that element again) or not_supported. Parameter writes are compared after the same unit conversion the write used; moves, rotations and mirrors on the element's location; created and deleted elements on whether they exist. Commands that report which elements they touched add data.affected = { created, modified, deleted }.

By default a failed read-back is only reported (ok stays true — the change is committed). Strict mode makes it an error: a single command returns verify_failed (the message says the model was committed), and a batch verifies before committing and rolls back whole.

Configure both with revit-mcp-audit.json next to the token file (%APPDATA%\Autodesk\Revit\Addins\<version>\); a missing file means these defaults:

{
  "enabled": true,          // audit log on/off
  "dir": null,              // null = %APPDATA%\RevitMCP\audit
  "includeReads": false,    // also log read-only commands
  "logParams": "hash",      // "hash" | "redacted" (keys + non-string values) | "full"
  "verifyFailure": "report", // "report" | "error" (strict)
  "mutationMode": "direct"  // "direct" | "preview_required" (see below)
}

Preview before write (optional, off by default). With "mutationMode": "preview_required" every model write — a write command, or a batch containing one — must first be sent as a dry-run. The dry-run response carries an approvalToken (single command: data.approvalToken; batch: top level, beside results) plus approvalExpiresAt. Send the same request again with that token at the top level of the body, next to params / steps:

{ "command": "set_parameter", "params": { "id": 123456, "parameterName": "Mark", "value": "D-101" },
  "approvalToken": "apv_…" }
  • The token is random, single-use and valid for 300 seconds. It is bound to the command, the exact params (for a batch: the steps, their order and stopOnError) and the active document.
  • No token → HTTP 428 approval_required. Unknown, already used or expired token, or a request that differs from the one previewed → HTTP 409 approval_mismatch (a mismatch does not use up the token). A failed dry-run issues no token.
  • Read-only and UI-action commands are never gated. Tokens live in memory, so restarting Revit invalidates them.
  • MCP tools that accept dryRun also accept approvalToken. GET /health reports mutationMode. The audit line records approval: {state, token} with state issued, consumed, required or mismatch; token is a short fingerprint, never the token itself.

Command packs can implement the same read-back (IVerifiableCommand in RevitMCP.Core); a pack command that reports data.affected gets the existence check without writing any code.

Warnings and errors at commit

Revit raises many warnings only when a transaction commits, and shows them in a modal box that stops the UI thread and every queued call until someone clicks. MCP writes never open that box.

A warning is dismissed, which is what clicking OK does: the change stays and the warning stays in Review Warnings. The caller gets it in data.revitWarnings (on the batch result for a batch):

{"message": "Multiple Rooms are in the same enclosed region. ...", "count": 1,
 "elementIds": [2772386, 2772388], "failureId": "83d4a67c-818c-4291-adaf-f2d33064fea8",
 "resolution": "Delete Room(s)"}

An error cannot be OK'd, so the transaction rolls back and the call fails with commit_failed (500), data.revitErrors in the same shape. Nothing changed in the model; a batch reports committed: false and all its steps are undone.

resolution is the fix Revit's dialog offers. It is reported, never applied, because it changes the model: the caller decides what to do from the message and elementIds. Only writes made through this add-in are covered. A warning from a person's own edit, or from another add-in's transaction, still shows Revit's box.

Opening models unattended (open_document, dialog policy, launcher)

Three pieces let a script start Revit, open a model and reach a ready add-in without anyone clicking.

open_document opens a .rvt path or a cloud model and makes it the active document. It needs no open document, so Revit can be launched bare. It passes a workset configuration (worksets: all by default, none, last_viewed), which is what keeps the "Opening Worksets" dialog from appearing at all. It is idempotent (alreadyOpen: true when the model already is the active document, wasOpen: true when it was open but not active), returns openMs, and lists every dialog Revit raised while opening. Cloud models take cloud: {region, projectGuid, modelGuid} — the ids get_document_info now reports for a cloud model. Opening takes tens of seconds (minutes for a first cloud download or an upgrade); the add-in imposes no timeout, the MCP tool waits up to REVIT_MCP_OPEN_TIMEOUT_MS (default 15 min). Errors: not_found 404, not_signed_in 403, open_failed 500 (with Revit's message).

Dialog policy (opt-in). revit-mcp-dialog-policy.json next to the token file makes the add-in subscribe to DialogBoxShowing at start-up, so it also sees dialogs raised while Revit opens a model from the command line, when no command can run. No file = no subscription: Revit's dialogs behave as they do without the add-in.

{
  "log": true,          // JSONL of every dialog seen: %APPDATA%\RevitMCP\dialogs\dialogs-YYYY-MM-DD.jsonl
  "dir": null,
  "rules": [            // first match wins; a dialog no rule matches is recorded, never answered
    { "dialogId": "Dialog_Revit_Partitions", "result": 1 },
    { "dialogId": "TaskDialog_Save_File", "result": 7 }
  ]
}

A rule needs dialogId and/or messageContains, and the integer result passed to OverrideResult (task dialogs: TaskDialogResult values such as 1 OK, 6 Yes, 7 No, or 1001+ for command links; Revit's own dialogs: 1 = OK). GET /dialogs[?since=seq] (auth) returns the recorded dialogs, answered on the listener thread — it works while Revit sits modal on the dialog in question.

Dialog ids measured on Revit 2027:

Dialog dialogId kind Fires DialogBoxShowing?
Opening Worksets (workshared model opened without a workset configuration) Dialog_Revit_Partitions dialog yes — OverrideResult(1) accepted, model opens with its default worksets
Warnings after open (e.g. "0 Errors, 23 Warnings" on an upgraded model) Dialog_Revit_DocWarnDialog dialog yes
Save changes on close ("Do you want to save changes to …?") TaskDialog_Save_File task_dialog yes
Security - Unsigned Add-In TaskDialog_Security_Unsigned_File_Loading (journal) task_dialog before any add-in loads — not catchable
Model Upgrade (progress window; only button: Cancel Upgrade) — — no

Launcher. scripts/launch-revit-dev.ps1 starts Revit, clicks "Load Once" for this add-in only, opens -Model through open_document (-OpenVia addin, default) or on the command line (-OpenVia commandline), and prints one JSON line with the phase times. It follows its own process by PID and reads that process's own journal, classifying each tick as starting, addin_up, waiting_dialog, stalled or ready. A silent journal alone is not a hang: Revit writes nothing while a dialog waits. Dialogs are handled by -DialogRules (click a button, ignore, or halt) — see scripts/launch-revit-dev.rules.example.json; one no rule covers is never clicked and ends the run with exit code 6. -StallSec / -Restart N kill a stalled Revit by PID (never by name) and relaunch. Its decision logic is tested without Revit: powershell -File scripts/tests/launch-revit-dev.tests.ps1.

Saving, closing and exporting without the UI

Four document commands complete the unattended loop, and export_ifc feeds tools that read IFC. All are UI actions: they run outside a transaction, a dry-run does nothing, and they cannot share a batch with model writes. Each returns the dialogs Revit raised, like open_document.

Command What it does
save_document Saves the active document to its own file
save_as Saves to a new .rvt; the open document then is that file. overwrite (default false), maxBackups (default 1)
close_document Closes an open document by path. save defaults to false. A path that is not open returns wasOpen: false
new_document Creates a project from a template, saves it to path and activates it
export_ifc Writes an IFC file: outputPath, ifcVersion (default IFC4), viewId, exportBaseQuantities, spaceBoundaryLevel, wallAndColumnSplitting, overwrite (default true)

Things the Revit API decides:

  • The active document cannot be closed. close_document returns active_document: open another model first, or end the session by stopping Revit.
  • save_document, save_as and close_document support local, non-workshared files only (not_supported otherwise). export_ifc has no such limit.
  • Document.Save() has no backup setting, so save_document follows the model's own backup count; save_as is the way to set it.
  • ifcVersion is checked against what the running Revit defines: Revit 2026 has no IFC4x3RV or IFC4x3DTV.
  • IFC export carries the rooms of one phase only: the view's phase, else the last phase. The result reports phase, roomsInPhase and roomsInOtherPhases; a non-zero roomsInOtherPhases means rooms are missing from the file. Areas and MEP Spaces also export as IfcSpace, so IfcSpace is not always a count of rooms.

export_ifc needs a transaction by Revit's rule, so it opens one and always rolls it back: the model is not modified. It writes to a temporary name first, so a failed export never replaces an earlier file. These commands are UI actions, so mutationMode: preview_required does not gate them.

Command packs (optional, opt-in)

The add-in can register extra commands from your own .NET class libraries ("packs"), next to its built-in ones. Nothing is loaded unless revit-mcp-packs.json exists in %APPDATA%\Autodesk\Revit\Addins\<version>\ and lists them:

{ "packs": [ "RevitMCP.Packs/MyPack.dll" ] }   // relative to that Addins folder; "enabled": false turns all off
  • Pack commands are HTTP-only (POST /mcp, POST /mcp/batch) and go through the same dispatcher: transactions, dry-run, batch, auth and error envelope all apply. They never appear in the MCP tool list.
  • A pack cannot replace a built-in command or another pack's command; a clashing command is skipped and reported, the rest of the pack still loads. A pack whose constructor throws is rejected whole.
  • A missing file, malformed config or incompatible pack is reported and skipped — it never stops the add-in. GET /commands shows each pack's commands, skipped and error; GET /health shows builtinCommandCount and packCommandCount.
  • Build a pack against the same RevitMCP version as the add-in, and ship only the pack DLL (never a copy of RevitMCP.Core.dll). The installer never writes or deletes the config or the packs.

Start from samples/HelloPack.

Tool profiles (token efficiency)

Exposing all 100 tools to every conversation costs tokens and degrades tool-selection accuracy. Set the REVIT_MCP_PROFILE env var to a comma-separated list to expose only the groups you need; core (ping, doc/element info, find, batch) is always included. Unset = all tools (default, backward compatible).

Profile Tools For
core 8 (always on) ping, document/element info, find, batch
inspection 23 list_* / get_* read-only queries (incl. schedule data, door swing)
model-health 2 get_model_health, get_worksets
recipes 2 workflow recipes (model-health triage, clash review)
coordination 1 check_clearance
architecture 10 wall/floor/level/grid/room/column/beam/ceiling/opening/family
documentation 16 sheets, views, schedules, tags, text, PDF, dimensions, detail lines, filled regions
editing 17 parameters, type swap, transform, delete, group, family load/duplicate
view 11 open/hide/isolate/filter/override/section-box
// e.g. a documentation + view client (a subset instead of all 98):
{ "mcpServers": { "revit": {
  "command": "node", "args": ["dist/index.js"],
  "env": { "REVIT_MCP_VERSION": "2027", "REVIT_MCP_PROFILE": "documentation,view" }
}}}

Repo layout

RevitMCPServer/
├── README.md                       ← you are here
├── LICENSE                         ← MIT
├── CHANGELOG.md
├── docs/
│   ├── ARCHITECTURE.md             ← three-layer design, threading, observability
│   ├── COMMANDS.md                 ← every command's schema + envelope
│   ├── API_COVERAGE.md             ← what we wrap, what we don't, why
│   ├── COMPATIBILITY.md            ← Revit/.NET/Node support + port matrix
│   ├── SMOKE_TESTING.md            ← live-Revit smoke suite + golden fixtures
│   ├── TROUBLESHOOTING.md          ← common failures and fixes
│   └── ROADMAP.md                  ← phase tracker
├── scripts/
│   ├── check-version.mjs           ← CI gate: version + tool-count consistency
│   └── smoke-test.ps1              ← live-Revit smoke test
├── samples/
│   └── HelloPack/                  ← minimal opt-in command pack
└── src/
    ├── RevitMCP.Core/              ← portable kernel: dispatcher + 98 commands
    │   ├── RevitMCPExternalEventHandler.cs
    │   └── Commands/               ← one IRevitCommand per tool
    ├── RevitAddin/                 ← C# addin host (in-Revit, .NET 8/10)
    │   ├── App.cs
    │   ├── Packs/                  ← opt-in command pack loader (revit-mcp-packs.json)
    │   ├── Server/McpHttpServer.cs ← /mcp, /mcp/batch, /commands, /health, /stats, /dialogs
    │   ├── Server/RequestLog.cs    ← structured request log
    │   ├── Server/ServerMetrics.cs ← counters behind /stats
    │   ├── RevitMCPAddin.csproj
    │   └── RevitMCPAddin.addin
    └── McpServer/                  ← TypeScript MCP stdio server
        ├── src/index.ts            ← tool declarations + profiles
        ├── src/revitClient.ts
        ├── package.json
        └── tsconfig.json

Install (recommended) — one-step installer

Everything is in a single download; no cloning or compiling.

  1. Download RevitMCPServer-v<latest>.zip from the latest release (the combined bundle — not the -R2025/-R2026/-R2027 per-version zips).
  2. Close Revit (the installer replaces the add-in DLL).
  3. Extract the zip, then right-click install.ps1 → Run with PowerShell (or, in a PowerShell window: .\install.ps1).
  4. Restart Revit, then restart Claude Desktop.

install.ps1 with no arguments auto-detects which Revit versions (2025 / 2026 / 2027) you have installed, deploys the matching add-in to each, installs the MCP server to %LOCALAPPDATA%\RevitMCPServer, and merges a revit-<ver> entry into your Claude Desktop config — backing it up first and leaving every other MCP server untouched.

Only prerequisite: Node.js 18+ on your PATH, for the Claude bridge. (The add-in itself works over its local HTTP API without Node — Node is only needed for Claude Desktop/Code to talk to it, and a missing Node is a warning, not a failure.)

Other MCP clients (Codex, Gemini, Cursor)

By default the installer configures Claude Desktop. Point it at a different client with -Client (one or more of claude, gemini, cursor, codex):

.\install.ps1 -Client codex                     # OpenAI Codex CLI
.\install.ps1 -Client claude,gemini,cursor,codex # all of them

Each client's config is merged in place (backed up first), leaving its other servers untouched:

Client Config file Format
claude %APPDATA%\Claude\claude_desktop_config.json JSON mcpServers
gemini %USERPROFILE%\.gemini\settings.json JSON mcpServers
cursor %USERPROFILE%\.cursor\mcp.json JSON mcpServers
codex %USERPROFILE%\.codex\config.toml TOML [mcp_servers.*]

The server is the same stdio program for every client — only where each keeps its config differs. Cloud-only clients that can't launch a local process (e.g. web ChatGPT) can't reach the loopback add-in; use a local client (Codex CLI, Claude, Cursor, Gemini CLI) on the same machine as Revit.

Options:

.\install.ps1 -RevitVersions 2027   # only one version
.\install.ps1 -NoClientConfig       # don't touch any client config, just print the snippet
.\uninstall.ps1                     # remove add-in, server, and the revit-<ver> config entries

Verify: open Revit, then run the health check (7890 = R2025, 7891 = R2026, 7892 = R2027):

Invoke-RestMethod http://127.0.0.1:7891/health
# → ok=True, service=revit-mcp-addin, version=0.8.43, authEnabled=True

Then restart Claude Desktop. The server shows up under Connectors (the + menu → Connectors) as revit-2026 / revit-2027, toggled on — open Tool access there to see the 89 revit_* tools per version. (Older Claude builds showed these under a 🔨 tools icon instead.)

Claude Code plugin

For Claude Code (and Cowork on the same computer), the bridge also comes as a plugin from this repository's marketplace. Install the add-in with .\install.ps1 -NoClientConfig, then:

/plugin marketplace add KenLP/RevitMCPServer
/plugin install revit-mcp-bimlynx@bimlynx

The plugin asks for your Revit version and adds a skill on how to work with the tools. See plugin/README.md. Every tool carries MCP annotations (a title, readOnlyHint, destructiveHint), so a client can run reads without asking and always ask before a change.


Build from source — advanced

Most users should use the one-step installer above. Build from source only to run an unreleased commit or to hack on the code. Every step is copy-paste into PowerShell on Windows.

Step 0 — What you'll install

Tool Why Download
Autodesk Revit 2025, 2026 or 2027 The app the addin plugs into Autodesk account (you already have this)
.NET 8 SDK (R2025/R2026) or .NET 10 SDK (R2027) Compiles the C# addin https://dotnet.microsoft.com/download/dotnet/8.0 (or /10.0)
Node.js 22 (LTS) Runs the MCP bridge https://nodejs.org/
Git Downloads this repo https://git-scm.com/download/win
Claude Desktop (or Claude Code) Your MCP client https://claude.ai/download

Install each of the above with the default options. Reboot is not required, but open a fresh PowerShell window after installing them so the PATH picks up dotnet, node, and git.

Sanity check — paste this in PowerShell:

dotnet --version   # should print 8.x or 10.x
node --version     # should print v22.x (or v18+)
git --version      # should print git version 2.x

If any command says "not recognized", re-install that tool and re-open PowerShell.

Step 1 — Download the repo

Pick a folder (e.g. C:\Dev\) and clone:

mkdir C:\Dev -Force
cd C:\Dev
git clone https://github.com/KenLP/RevitMCPServer.git
cd RevitMCPServer

If you downloaded a source ZIP instead, unzip it to C:\Dev\RevitMCPServer\.

Step 2 — Build the Revit addin (C#)

Close Revit first — if Revit is running it locks the DLL and the build will fail.

For Revit 2025/26 (default):

cd C:\Dev\RevitMCPServer\src\RevitAddin
dotnet build

For Revit 2027:

cd C:\Dev\RevitMCPServer\src\RevitAddin
dotnet build -p:RevitVersion=2027

For Revit 2025:

cd C:\Dev\RevitMCPServer\src\RevitAddin
dotnet build -p:RevitVersion=2025

The csproj auto-selects .NET 8 for R2025–2026 and .NET 10 for R2027+.

What this does:

  1. Compiles RevitMCPAddin.dll.
  2. Auto-copies the DLL and the .addin manifest file into %APPDATA%\Autodesk\Revit\Addins\<version>\ so Revit finds it next start.

If you use multiple Revit versions, build once per version — each deploys to its own Addins folder.

You should see Build succeeded at the end. If you see red errors, check:

  • Is Revit closed?
  • Is the target Revit version installed at C:\Program Files\Autodesk\Revit <version>\? If not, override: dotnet build -p:RevitInstallDir="D:\Your\Path".

Step 3 — Build the MCP bridge (TypeScript)

cd C:\Dev\RevitMCPServer\src\McpServer
npm install
npm run build

This produces dist/index.js — the small Node program Claude will launch.

Step 4 — Start Revit and verify the addin loaded

  1. Open Revit (2025, 2026 or 2027) → open any project (even a blank one).

  2. In PowerShell, run the health check for your version (port 7890 for R2025, 7891 for R2026, 7892 for R2027):

    Invoke-RestMethod http://127.0.0.1:7890/health   # R2025
    Invoke-RestMethod http://127.0.0.1:7891/health   # R2026
    Invoke-RestMethod http://127.0.0.1:7892/health   # R2027

    Expected output:

    ok        : True
    service   : revit-mcp-addin
    version   : 0.8.43
    authEnabled : True
    
  3. The first time Revit starts after the build, the addin generates a random token at:

    %APPDATA%\Autodesk\Revit\Addins\<version>\revit-mcp-token.txt
    

    You don't need to read it yourself — the MCP bridge does that automatically.

Step 5 — Tell Claude Desktop about the server

Open (or create) the Claude Desktop config file. The easy way:

notepad "$env:APPDATA\Claude\claude_desktop_config.json"

Paste this (adjust the path if you cloned somewhere other than C:\Dev).

Single Revit version (e.g. 2026 only):

{
  "mcpServers": {
    "revit-2026": {
      "command": "node",
      "args": [
        "C:\\Dev\\RevitMCPServer\\src\\McpServer\\dist\\index.js"
      ],
      "env": { "REVIT_MCP_VERSION": "2026" }
    }
  }
}

Two Revit versions side-by-side (port is auto-assigned per version):

{
  "mcpServers": {
    "revit-2026": {
      "command": "node",
      "args": [
        "C:\\Dev\\RevitMCPServer\\src\\McpServer\\dist\\index.js"
      ],
      "env": { "REVIT_MCP_VERSION": "2026" }
    },
    "revit-2027": {
      "command": "node",
      "args": [
        "C:\\Dev\\RevitMCPServer\\src\\McpServer\\dist\\index.js"
      ],
      "env": { "REVIT_MCP_VERSION": "2027" }
    }
  }
}

Important: the path uses double backslashes (\\) because it's JSON. Port is auto-assigned (R2025 = 7890, R2026 = 7891, R2027 = 7892) — no need to set REVIT_MCP_PORT unless you want a custom port. REVIT_MCP_VERSION tells the bridge which port + token file to use.

Save, then fully quit and restart Claude Desktop (right-click tray icon → Quit). When it relaunches, the server appears under Connectors (+ menu → Connectors) as revit-<ver>, toggled on; open Tool access to see the 89 revit_* tools (doubled if you configured two versions). (Older Claude builds showed these under a 🔨 tools icon instead.)

Step 6 — Try your first prompt

In Claude Desktop, with a Revit project open, send:

Ping Revit and tell me what version is running.

Claude will ask permission to run revit_ping — click Allow (or Always allow to skip future prompts for that tool). You should see a JSON response confirming Revit 2026.

Next try:

List 5 walls in the active document.

🎉 You're in.

Troubleshooting

Symptom Fix
dotnet build → CS0246: 'Autodesk' could not be found Revit 2026 not installed at default path. Use -p:RevitInstallDir="...".
dotnet build → MSB3027: Could not copy ... file is being used Revit is running. Close it first.
/health → "Unable to connect" Revit is not running, or the addin didn't load. Check Add-Ins → External Tools menu.
Claude Desktop shows 0 tools Wrong path in config, or Node not in PATH. Try running node "C:\Dev\RevitMCPServer\src\McpServer\dist\index.js" manually — it should wait on stdin without erroring.
Tools listed but every call says "401 unauthorized" Revit was restarted after Claude read the token. Quit + restart Claude Desktop.
Port 7891 already in use Another service is on that port. Set REVIT_MCP_PORT=8123 on both ends (env var in Revit launch + in claude_desktop_config.json).

Build (reference)

Quick-reference for developers.

1. C# Revit addin

Revit version .NET required Build command
2026 (default) .NET 8 SDK dotnet build
2027 .NET 10 SDK dotnet build -p:RevitVersion=2027
2025 .NET 8 SDK dotnet build -p:RevitVersion=2025
cd src/RevitAddin
dotnet build                          # Revit 2026 (default)
dotnet build -p:RevitVersion=2027     # Revit 2027
dotnet build -p:RevitVersion=2025     # Revit 2025

Post-build copies RevitMCPAddin.dll and the .addin manifest into %APPDATA%\Autodesk\Revit\Addins\<version>\. Skip auto-deploy with -p:DeployToRevit=false.

Heads up: close the target Revit before rebuilding — it holds the DLL open.

Revit 2025 uses .NET 8 (same as 2026). CI-tested via Nice3point reference assemblies (v2025.2.0, port 7890). Not smoke-tested against a live R2025 install — see docs/COMPATIBILITY.md.

2. TypeScript MCP server

Requires Node 18+ (Node 22 tested).

cd src/McpServer
npm install
npm run build

Outputs dist/index.js, runnable as node dist/index.js.

Run

1. Start Revit

Open any project. The addin auto-loads and starts the HTTP listener on http://127.0.0.1:7891/ (default port). On first start it generates a random auth token at %APPDATA%\Autodesk\Revit\Addins\<version>\revit-mcp-token.txt.

Running multiple Revit versions at once? Each version auto-assigns its own port (R2025 = 7890, R2026 = 7891, R2027 = 7892). No extra config needed — just open both.

Sanity check:

Invoke-RestMethod http://127.0.0.1:7890/health   # R2025
Invoke-RestMethod http://127.0.0.1:7891/health   # R2026
Invoke-RestMethod http://127.0.0.1:7892/health   # R2027
# → ok=True, service=revit-mcp-addin, version=0.8.43, authEnabled=True

# Authenticated request (read the token first):
$token = Get-Content "$env:APPDATA\Autodesk\Revit\Addins\2026\revit-mcp-token.txt"
Invoke-RestMethod http://127.0.0.1:7891/commands -Headers @{ Authorization = "Bearer $token" }
# → list of all registered commands + isReadOnly + riskLevel

💡 PowerShell quoting tip. Don't use curl.exe with -d "{\"...\"}" in PowerShell — Windows arg marshalling eats the quotes. Use Invoke-RestMethod -Body '{"command":"ping","params":{}}' instead.

2. Wire up your MCP client

Claude Desktop (%APPDATA%\Claude\claude_desktop_config.json) or Claude Code (~/.claude/settings.json mcpServers block):

Single version:

{
  "mcpServers": {
    "revit-2026": {
      "command": "node",
      "args": [ "C:\\path\\to\\RevitMCPServer\\src\\McpServer\\dist\\index.js" ],
      "env": { "REVIT_MCP_VERSION": "2026" }
    }
  }
}

Two versions side-by-side (port auto-assigned):

{
  "mcpServers": {
    "revit-2026": {
      "command": "node",
      "args": [ "C:\\path\\to\\RevitMCPServer\\src\\McpServer\\dist\\index.js" ],
      "env": { "REVIT_MCP_VERSION": "2026" }
    },
    "revit-2027": {
      "command": "node",
      "args": [ "C:\\path\\to\\RevitMCPServer\\src\\McpServer\\dist\\index.js" ],
      "env": { "REVIT_MCP_VERSION": "2027" }
    }
  }
}

Restart the client. You should see 100 revit_* tools per configured version.

3. Try it

Ping Revit, then list 5 walls in the active document.

Create level "L4" at 12 m, then a grid line "1" from (0,0) to (30,0), then 3 walls along the grid — all in one batch so I can undo it as one step.

Find all walls on level "Level 1" and set their Comments parameter to "Reviewed by AI".

The batch flow uses revit_batch, which runs every step in one Revit Transaction. Ctrl+Z in Revit reverts the whole batch.

How the threading + transaction model works

Revit's API can only be called on the main UI thread, and writes must be inside a Transaction. We hide both rules from command authors:

  1. McpHttpServer receives a request on a thread-pool thread.
  2. It enqueues a PendingRequest (with a TaskCompletionSource), raises an ExternalEvent, and awaits the TCS.
  3. Revit invokes Execute(UIApplication) on the main thread; the dispatcher drains the queue.
  4. For each request:
    • Read-only commands run with no transaction.
    • Single write commands are wrapped in Transaction(doc, "MCP: <cmd>").
    • Batches open one Transaction(doc, "MCP: Batch (n ops)") and run every sub-command inside it.
  5. The TCS completes; the HTTP handler resumes and writes the response.

Commands themselves are stateless IRevitCommand implementations — they just call the Revit API and return JSON. They never see a Transaction. Full details in docs/ARCHITECTURE.md.

Response envelope

Every command — single or batched — returns the same shape:

// success
{ "ok": true, "data": { /* command-specific */ } }

// failure
{ "ok": false, "error": { "code": "not_found", "message": "Level 'L99' not found." } }

Batches add committed, count, hadFailures, results[]. See docs/COMMANDS.md.

Configuration env vars

Variable Where Default Purpose
REVIT_MCP_PORT Revit + Node auto (see below) HTTP port — override the auto-assigned port
REVIT_MCP_HOST Node only 127.0.0.1 Loopback only (127.0.0.1/localhost/::1) — anything else refuses to start
REVIT_MCP_TIMEOUT_MS Node only 30000 Per-tool-call HTTP timeout
REVIT_MCP_AUTH_TOKEN Node only (auto-read) Override: use this token instead of file
REVIT_MCP_VERSION Node only 2026 Revit version (for token file + auto-port)
REVIT_MCP_CLIENT Node only (unset) Sent as X-MCP-Client (≤64 chars); recorded in the add-in's audit log
REVIT_MCP_TRACE_ID Node only (unset) Sent as X-MCP-Trace (≤128 chars); joins a harness run to the audit log
REVIT_MCP_TRACE_FILE Node only (unset) Append one JSON line per MCP tool call: ts, tool, durationMs, ok, errorCode, reqBytes, respBytes, trace

Auto-port: both the C# addin and the TypeScript bridge auto-assign a port based on the Revit version: R2026 = 7891, R2027 = 7892, R2028 = 7893, etc. REVIT_MCP_PORT overrides this if set.

Dry-run mode

Every write tool accepts "dryRun": true. The command executes normally but the transaction is rolled back — the model is unchanged.

// HTTP
POST /mcp?dryRun=true
{ "command": "create_wall", "params": { ... } }

// MCP tool
revit_create_wall({ ..., dryRun: true })

Response includes "dryRun": true, "committed": false alongside the normal data payload so the AI can preview the result and ask the user to confirm before running it for real.

Structured diffs

Write commands return a changeSummary one-liner and, where applicable, a changes object with before/after values:

{
  "ok": true,
  "data": {
    "id": 184239,
    "parameterName": "Comments",
    "changeSummary": "Set 'Comments' on element 184239: '' → 'Reviewed by AI'",
    "changes": { "before": "", "after": "Reviewed by AI" }
  }
}

The AI should show the concise changeSummary by default, and only expand the full changes diff when the user asks for details.

Auth token

On startup the addin generates a cryptographically random token and writes it to %APPDATA%\Autodesk\Revit\Addins\<version>\revit-mcp-token.txt. The TypeScript MCP server reads this file automatically.

  • GET /health is exempt from auth (so clients can detect the addin).
  • All other endpoints require Authorization: Bearer <token>. Token auth is unconditional — the old REVIT_MCP_AUTH=false switch was removed in 0.8.17 (an unauthenticated loopback port would let any local process drive Revit).
  • Pass REVIT_MCP_AUTH_TOKEN=<token> to the MCP server to use a fixed token.

Per-tool risk levels

GET /commands now returns a riskLevel for each command:

Level Meaning Examples
read Read-only, no model changes ping, list_elements
low Creates new elements (easily undoable) create_wall, create_level
medium Modifies existing elements set_parameter, move_element
high Deletes or hard-to-reverse delete_elements, ungroup

MCP clients can use this to decide which tools need per-call confirmation.

Security model

  • The HTTP listener binds only to 127.0.0.1, and the Node client refuses to start if REVIT_MCP_HOST points anywhere but loopback.
  • Auth token: a random Bearer token is generated each Revit session and required on all endpoints except /health. It cannot be disabled.
  • Dry-run mode: lets you preview AI actions without committing.
  • Risk levels: GET /commands exposes per-tool risk, so clients can auto-allow read tools and prompt on high tools.
  • Never run this addin on a machine where untrusted local processes might reach 127.0.0.1:7891 (e.g. a multi-tenant build agent).

Inspirations

The full comparison and the project's roadmap rationale live in docs/ROADMAP.md and docs/API_COVERAGE.md.

License

MIT.

About

A custom Model Context Protocol server that lets Claude (or any MCP client) drive Revit 2025- 2027 — read the model, query, create & edit elements, and run multi-step operations as a single undoable Transaction.

Resources

Stars

17 stars

Watchers

2 watching

Forks

Releases

Packages

Contributors

Languages