KR

kenlp/revitmcpserver

Developer tools
15 stars 7 forks Quality 86 Trend 41MIT

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 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.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 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.

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 /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 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

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

  1. Download RevitMCPServer-v.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- 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:

  1. Compiles RevitMCPAddin.dll.
  2. Auto-copies the DLL and the .addin manifest 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

  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.40
    authEnabled : True
    
  3. The first time Revit starts after the build, the addin generates a random token at:

    %APPDATA%\Autodesk\Revit\Addins\\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-, 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.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 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 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: ").
    • 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\\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 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= 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.

View the full README on GitHub

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" } }}}