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...
Overview
A custom server that lets Claude (Desktop, Code, or any MCP client) drive — read the model, create & edit elements, and run multi-step operations as a single undoable Transaction. - (.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. - is a thin stdio bridge: every tool just forwards to a small HTTP API on the addin. - — no eval-style escape hatch, every write is a named Transaction you can review and undo. - — 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. — 92 C# commands (91 exposed as MCP tools + 1 hidden) + 1 batch tool + 2 workflow recipes = . Hidden = create_spot_elevation, registered for HTTP /mcp use but off the MCP tool surface.
README
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
Transactionyou 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.40 — 92 C# commands (91 exposed as MCP tools + 1 hidden) + 1 batch tool + 2 workflow recipes = 94 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 (91 commands + 1 batch + 2 recipes = 94 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_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\\:
{ "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\\); 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 409approval_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
dryRunalso acceptapprovalToken.GET /healthreportsmutationMode. The audit line recordsapproval: {state, token}with stateissued,consumed,requiredormismatch;tokenis 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.
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\\ 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 /commandsshows each pack’scommands,skippedanderror;GET /healthshowsbuiltinCommandCountandpackCommandCount. - 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 94 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 93):
{ "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 + 92 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
│ ├── 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.
- Download
RevitMCPServer-v.zipfrom the latest release (the combined bundle — not the-R2025/-R2026/-R2027per-version zips). - Close Revit (the installer replaces the add-in DLL).
- Extract the zip, then right-click
install.ps1→ Run with PowerShell (or, in a PowerShell window:.\install.ps1). - 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- 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- 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.40, 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.)
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 | (or /10.0) |
| Node.js 22 (LTS) | Runs the MCP bridge | |
| Git | Downloads this repo | |
| Claude Desktop (or Claude Code) | Your MCP client |
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:
- Compiles
RevitMCPAddin.dll. - Auto-copies the DLL and the
.addinmanifest file into%APPDATA%\Autodesk\Revit\Addins\\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 \? 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
-
Open Revit (2025, 2026 or 2027) → open any project (even a blank one).
-
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 # R2027Expected output:
ok : True service : revit-mcp-addin version : 0.8.40 authEnabled : True -
The first time Revit starts after the build, the addin generates a random token at:
%APPDATA%\Autodesk\Revit\Addins\\revit-mcp-token.txtYou 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 setREVIT_MCP_PORTunless you want a custom port.REVIT_MCP_VERSIONtells 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-, 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\\. 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\\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.40, 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.exewith-d "{\"...\"}"in PowerShell — Windows arg marshalling eats the quotes. UseInvoke-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 94 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
Commentsparameter 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:
McpHttpServerreceives a request on a thread-pool thread.- It enqueues a
PendingRequest(with aTaskCompletionSource), raises anExternalEvent, and awaits the TCS. - Revit invokes
Execute(UIApplication)on the main thread; the dispatcher drains the queue. - For each request:
- Read-only commands run with no transaction.
- Single write commands are wrapped in
Transaction(doc, "MCP: "). - Batches open one
Transaction(doc, "MCP: Batch (n ops)")and run every sub-command inside it.
- 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\\revit-mcp-token.txt.
The TypeScript MCP server reads this file automatically.
GET /healthis exempt from auth (so clients can detect the addin).- All other endpoints require
Authorization: Bearer. Token auth is unconditional — the oldREVIT_MCP_AUTH=falseswitch was removed in 0.8.17 (an unauthenticated loopback port would let any local process drive Revit). - Pass
REVIT_MCP_AUTH_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 ifREVIT_MCP_HOSTpoints 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 /commandsexposes per-tool risk, so clients can auto-allowreadtools and prompt onhightools. - 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
mcp-servers-for-revit/mcp-servers-for-revit— best-organised fork of the original PiggyYan project, multi-version CI.revit-mcp/revit-mcp— original TypeScript + C# project. We deliberately do not ship its arbitrary C# eval tool.revit-mcp/revit-mcp-python— pyRevit-based, Python-first.
The full comparison and the project’s roadmap rationale live in
docs/ROADMAP.md and docs/API_COVERAGE.md.
License
MIT.
Install
This server does not publish a one-line install command.
Open the repository installation guide New to MCP? Start with the mcpskill.org guide →Configuration
// e.g. a documentation + view client (a subset instead of all 93):
{ "mcpServers": { "revit": {
"command": "node", "args": ["dist/index.js"],
"env": { "REVIT_MCP_VERSION": "2027", "REVIT_MCP_PROFILE": "documentation,view" }
}}}