Transport¶
confirm_token is a two-step handshake with the AI app. The AI app is expected to show the action to you; Model MCP does not show its own dialog. The token does not prove human approval.
REVIT_MCP_HOST selects local, ssh:<alias>, http://host:port or https://host:port.
--host overrides it.
local, the default, talks to the add-in over a per-process named pipe and falls back to the file channel.
For a remote client, run the whole server on the workstation over SSH stdio; see remote setups.
HTTP connects directly to the add-in and requires no SSH server or remote file transfer.
The MCP client still communicates with the Python server over stdio.
Transport errors¶
Unavailable SSH, named pipe, and HTTP transports return a normal MCP tool result with success:false, command, errorCode:"transport_unavailable", and retryable:true.
The error message names the transport and reason and directs the client to retry when the workstation or tunnel is back.
Transport failures do not close the stdio server.
The tool error codes also define read_only, policy_blocked, and action_failed.
SSH connection quota exhaustion before a command starts, connection failures, missing or inaccessible transport executables, pipe disconnects, unreachable HTTP endpoints, and HTTP 503 use transport_unavailable.
An execution timeout after a command starts remains a tool error.
An HTTP request timeout also remains a tool error: a submitted job may still execute.
The model and job result require inspection before retrying an action after a timeout.
An invalid REVIT_MCP_HOST produces a startup configuration message on stderr and exit code 2.
--help and --version remain available without starting a transport.
An unexpected stdio run exception produces a diagnostic line on stderr and propagates to the caller.
A keyboard interrupt exits successfully.
Channel permissions¶
The file channel trusts Windows folder permissions to identify its participants. A foreign writer can create requests or forge heartbeats and responses. The add-in refuses a channel whose owner or write grants fall outside the existing ACL policy. Current user, SYSTEM, and Administrators are trusted owners and writers; the policy also permits the Windows creator-owner and owner-rights writer entries. A new channel has a protected private DACL with Full Control for the current user, SYSTEM, and Administrators only.
An observed refused folder had Administrators as owner, inheritance enabled, and inherited write grants for the Hyper-V vmWorkerProcess capability and S-1-5-83-0 (Virtual Machines).
The capability SID is S-1-15-3-1024-2268835264-3721307629-241982045-173645152-1490879176-104643441-2915960892-1612460704.
Administrators ownership is allowed; the foreign write grants cause the refusal.
These entries alone do not establish who changed the parent ACL or whether any access occurred.
Without a fresh heartbeat, local and SSH discovery report:
Revit process
is running, but the Model MCP channel is not confirmed.
When available, the message quotes the newest File channel disabled: ... line from the tail of the newest add-in log.
The reader uses the Windows Documents folder, including redirected Documents, under RevitModelMcp\Logs; a machine LogDir policy takes precedence, with registry policy winning over policy.json.
The temporary log folder is the fallback when the configured folder is unavailable.
The read is limited to the last 64 KiB of one file, and REVIT_MCP_REDACT_PATHS applies to the quoted reason.
A missing log does not suppress the diagnostic.
The message ends with: "Use Repair channel permissions on the Model MCP ribbon, or see docs/transport.md#channel-permissions."
A heartbeat carrying an unsupported fileChannelVersion still reports a version mismatch.
A missing heartbeat does not establish a version mismatch or prove a permissions failure.
At startup, refusal adds one Activity notice and changes the connection button to AI connection off, with a permission-repair tooltip. The named pipe and configured HTTP listener can still run; heartbeat discovery remains unavailable until the file channel starts. Model MCP → Repair channel permissions is enabled only after an ACL refusal. The confirmation lists the channel folder, current owners, and every foreign SID removed from the folder and existing children, with account names where Windows can resolve them. After confirmation, the command turns inheritance off and replaces the DACL with Full Control for the current user, SYSTEM, and Administrators. It attempts to set the owner to the current user; an allowed existing owner can remain if Windows refuses the owner change. The command refuses reparse points and changes to the reviewed ACL before applying the repair. Successful repair reinitializes the channel, starts its watcher and heartbeat, and restores the connection button without restarting Revit. Repair changes workstation files, records notices in Activity and the add-in log, and creates no model undo entry. A repair failure can leave some children already repaired.
Manual repair¶
Windows requires WRITE_DAC to replace permissions and may require WRITE_OWNER to change ownership.
A denied repair reports these requirements and displays manual icacls commands.
Manual repair requires closing Revit and MCP clients, reviewing the affected folder and children, and consent to remove the reported access.
An administrator can run the following in elevated PowerShell under the same Windows account.
$channel must name the refused folder; the example uses the default channel.
The commands must not target the profile root or virtualization storage.
Stop after any nonzero exit code.
$channel = Join-Path $env:LOCALAPPDATA 'RevitModelMcp'
$sid = [Security.Principal.WindowsIdentity]::GetCurrent().User.Value
icacls $channel
icacls $channel /setowner "*$sid" /T
icacls $channel /inheritance:r /grant:r "*${sid}:(OI)(CI)F" "*S-1-5-18:(OI)(CI)F" "*S-1-5-32-544:(OI)(CI)F" /T
# Repeat /remove for every remaining foreign SID shown by the review.
icacls $channel /remove '*S-1-5-83-0' /T
icacls $channel /remove '*S-1-15-3-1024-2268835264-3721307629-241982045-173645152-1490879176-104643441-2915960892-1612460704' /T
icacls $channel
The removal examples apply only when those SIDs appear in the review.
Explicit grants for other foreign SIDs also require removal; /grant:r replaces grants only for the named principals.
Microsoft documents the icacls options.
After manual repair, restart Revit and confirm that discovery and revit_ping succeed.
REVIT_MCP_CHANNEL_DIR only moves the channel; it must not hide a refusal or replace permission repair.
Named pipe¶
Each add-in listens on \\.\pipe\RevitModelMcp.<pid> from startup.
The pipe needs no port, URL reservation or settings, and it coexists with other MCP servers in the same Revit.
Its ACL admits only the Windows user running Revit, and the add-in drops clients that connect from another computer.
The heartbeat advertises pipe/1 only after the pipe listens.
REVIT_MCP_HOST=local reads the heartbeats in the channel directory directly.
It uses the pipe when the selected instance lists pipe/1 in protocols.
Otherwise, or when the pipe cannot be opened, it uses the local file channel.
Instance selection follows the file channel rules below.
The server keeps one connection per Revit process and checks that hello returns the heartbeat's pid and instanceId.
On Windows, it also checks the pipe server process ID when opening each connection and uses identification-only client credentials.
Protocol pipe/1¶
Messages are UTF-8 JSON objects, one per line.
Requests are limited to 1 MiB; a longer line gets a message_rejected error and closes the connection.
Replies carry command results and have no size limit.
A request may carry id; its reply echoes it.
hello must come first on every connection.
| Request | Reply |
|---|---|
{"type":"hello","protocol":"pipe/1","clientId":"…","clientName":"…"} |
{"type":"hello","instanceId","pid","revitVersion","addinVersion","protocolVersion","commands","documents"} |
{"type":"submit","job":{…}} |
{"type":"submitted","jobId","state","position"} |
{"type":"status","jobId":"…"} |
{"type":"status","jobId","state","position","result"}; result only when finished |
{"type":"cancel","jobId":"…"} |
{"type":"cancel","jobId","cancelled","state","message"} |
job is the same JSON object as a file job, and its clientId must equal the hello client ID.
For every job it submitted, a connection receives {"type":"job","jobId","state","position"} when the state changes.
It then receives one final {"type":"job","jobId","state","result"} with state done, failed or cancelled and the command response in result.
Failures return {"type":"error","id","error","message"}.
Codes include client_id_changed, client_id_required, client_mismatch, duplicate_job_id, hello_required, internal_error, invalid_job, invalid_message, job_id_required, job_required, message_rejected, not_found, queue_full, read_only, submission_failed, unknown_type and unsupported_protocol. queue_full includes retryAfterMs.
Pipe, HTTP and file jobs share one per-Revit scheduler.
A disconnect cancels that connection's queued jobs; a running job, and above all a running action, always finishes.
A client that reconnects with the same clientId can send status for its unfinished job to get the result and resume its pushes.
Completed read results expire ten minutes after completion, action results after 24 hours; see result retention.
capture-elements downloads one PNG through the same data.fileName artifact mechanism as export-view over local, pipe and SSH transports.
It is unavailable over HTTP and returns element snapshots need the local or SSH transport; /views/{name}/image is unchanged.
revit_export_view over the pipe moves the PNG from ROOT\instances\<pid>\ to output_path or a new temporary directory.
HTTP configuration¶
HTTP is opt-in and off by default; a default deployment uses only the named pipe and the local file channel.
On first startup the add-in creates %LOCALAPPDATA%\RevitModelMcp\settings.json with these values:
{
"httpEnabled": false,
"httpBind": "127.0.0.1",
"httpPort": 53110,
"token": "<generated 32-byte base64url token>",
"showActivityPaneOnAction": true,
"trustedNetworkRoots": [],
"updateCheck": true,
"maxStoredResponses": 256,
"maxStoredBytes": 67108864,
"requestReadTimeoutSeconds": 30,
"shutdownDrainSeconds": 5
}
The token is per Windows user and persists across restarts.
Settings and the read-only file remain in this default directory even when REVIT_MCP_CHANNEL_DIR overrides the file channel.
The add-in creates and restricts the file with a protected NTFS ACL granting full control only to the current Windows user.
Keep the token private and transfer it to the client's secret store through a trusted channel.
The add-in never logs it.
Do not put it in a URL, repository or shared shell history.
Revit environment variables override settings at startup: REVIT_MCP_HTTP_ENABLED=0|1, REVIT_MCP_HTTP_BIND, REVIT_MCP_HTTP_PORT and REVIT_MCP_TOKEN.
Overrides are not written back to the settings file.
Set httpEnabled=true in settings or REVIT_MCP_HTTP_ENABLED=1 before launching Revit to enable HTTP.
Assign each HTTP-enabled Revit process its own REVIT_MCP_HTTP_PORT; the settings file is shared by the Windows user.
For example, launch one process from a PowerShell session with $env:REVIT_MCP_HTTP_ENABLED = '1' and $env:REVIT_MCP_HTTP_PORT = '53111', and another with port 53112.
Each port requires its own URL reservation.
Set httpEnabled=false or REVIT_MCP_HTTP_ENABLED=0 to turn the listener off entirely.
Upgrade note: an existing settings.json with httpEnabled:true remains enabled after this default change.
The stored value is preserved.
To stop the listener, set it to false or set REVIT_MCP_HTTP_ENABLED=0 before upgrading, then enable HTTP deliberately if wanted.
Restart Revit after changing listener settings.
Invalid settings disable HTTP and leave the file channel available.
Limits¶
These optional settings in settings.json bound memory and stalled clients.
A missing key uses the default.
A value outside its range disables HTTP, like any other invalid setting.
The settings reference lists every key.
| Setting | Default | Range | Meaning |
|---|---|---|---|
maxStoredResponses |
256 | 1-10000 | Most completed results kept at once |
maxStoredBytes |
67108864 | 1048576-1073741824 | Most bytes of completed results kept at once |
requestReadTimeoutSeconds |
30 | 1-300 | Total time allowed to receive one request body |
shutdownDrainSeconds |
5 | 0-60 | Time to let in-flight requests finish when Revit shuts down |
When a limit is exceeded, the add-in evicts the oldest completed results first.
Queued and running jobs are never evicted, and the newest completed result is always kept, even if it alone exceeds maxStoredBytes.
The limits apply to results of every transport, because all transports share one scheduler.
Result retention¶
Two different stores hold results, so two different times apply.
The scheduler keeps completed read results in memory for ten minutes after completion; they also disappear when Revit exits.
Action results stay in memory for 24 hours and are also written to jobs/<jobId>.json in the instance directory, so revit_jobs can fetch them after an MCP server restart.
The limits above can evict a result earlier.
Over HTTP, a result that is no longer available returns status 410 with "error":"expired"; status 404 means the ID was never known.
Settings reference¶
%LOCALAPPDATA%\RevitModelMcp\settings.json accepts these keys.
An unknown key is ignored.
Restart Revit after a change.
| Key | Type | Default | Meaning |
|---|---|---|---|
httpEnabled |
boolean | false |
Turns the HTTP listener on. REVIT_MCP_HTTP_ENABLED overrides it. |
httpBind |
string | "127.0.0.1" |
IPv4 interface address to bind; 0.0.0.0 must be explicit. REVIT_MCP_HTTP_BIND overrides it. |
httpPort |
integer | 53110 |
Listener port, 1-65535. REVIT_MCP_HTTP_PORT overrides it. |
token |
string | generated | Bearer token: nonempty, no control characters. The add-in writes a random 32-byte token on first start. REVIT_MCP_TOKEN overrides it. |
showActivityPaneOnAction |
boolean | true |
Shows the MCP activity pane when an action runs. |
trustedNetworkRoots |
array of strings | [] |
UNC share roots such as \\server\share that file actions may use. See security. |
updateMode |
string | channel default | off, notify or auto. See automatic updates. |
updateCheck |
boolean | true |
false stops the add-in from checking for updates. A false in %ProgramData%\RevitModelMcp\settings.json applies to every user. See automatic updates. |
maxStoredResponses |
integer | 256 |
See Limits. |
maxStoredBytes |
integer | 67108864 |
See Limits. |
requestReadTimeoutSeconds |
integer | 30 |
See Limits. |
shutdownDrainSeconds |
integer | 5 |
See Limits. |
The workstation read-only lock is a file next to settings.json.
It applies to all Revit versions and instances of the same Windows user.
The Model MCP ribbon or revit_set_read_only controls it.
Chat unlock requires a five-minute, single-use token handshake with the AI app. The AI app is expected to show the action; Model MCP does not show its own dialog. The token does not prove human approval.
Machine policy overrides the user lock.
See read-only mode.
Listener status¶
The heartbeat and revit_ping report the listener state: httpState is disabled, listening or failed, and httpReason explains disabled and failed.
revit_ping returns them as httpListener with state and reason; revit_list_instances returns httpState and httpReason over the local and SSH transports.
After the listener starts, the add-in probes its own /health endpoint. An HTTP.sys 503 or another failed probe sets the state to failed.
A reservation or port problem shows failed with the reason, and an invalid settings.json shows failed with a configuration reason.
When the state is failed, or a client gets HTTP 503, check the reservations and the HTTP service from an elevated prompt on the Revit machine:
netsh http show urlacl
netsh http show servicestate
show urlacl must list the exact prefix, for example http://127.0.0.1:53110/, for the Windows user that runs Revit.
show servicestate lists the request queues and URL groups that already own a prefix; another process on the same URL causes a 503.
A leftover wildcard reservation for the same port, such as http://+:53110/, also causes a 503 self-probe failure.
Remove that reservation from an elevated prompt with netsh http delete urlacl url=http://+:53110/, substituting the configured HTTP port.
Windows URL reservation¶
Default script and MSI installs neither register a URL ACL nor require a netsh command.
URL reservation is a separate opt-in installation step; it does not change the listener settings.
An elevated script install reserves the selected prefix only with -EnableHttp:
.\install.ps1 -Year 2026 -EnableHttp -HttpBind 127.0.0.1 -HttpPort 53111
For a per-machine MSI, pass HTTP_ENABLED=1 HTTP_URL_PREFIX="http://127.0.0.1:53111/" to msiexec /i <package.msi> from an elevated prompt.
The prefix must match the explicitly enabled Revit process.
The SingleUser MSI installs without elevation and does not manage URL reservations. For HTTP with a per-user installation, use the exact netsh http add urlacl command printed by install.ps1 -EnableHttp when it is not elevated. Alternatively, use the MultiUser MSI from an elevated prompt with the HTTP properties above.
An opted-in MultiUser MSI reserves the prefix for the built-in Users group, allowing any local interactive user to bind that prefix.
Bearer-token authentication remains required.
Without elevation, an opted-in script install prints the exact reservation command for an elevated command prompt.
The script preserves existing reservations without claiming ownership.
An opted-in MultiUser MSI install fails if its prefix already exists; omit HTTP_ENABLED=1 to use a reservation managed outside that MSI.
Changing httpPort or httpBind requires a matching URL reservation.
Use + in the reservation prefix for httpBind=0.0.0.0.
An access-denied warning in the add-in log includes the exact configured prefix and repair command.
The listener remains stopped until the reservation exists and Revit restarts.
MultiUser MSI uninstall removes only the prefix recorded for that installed product after successful registration.
Major upgrades remove that owned reservation; pass the HTTP installation properties again to reserve a prefix for the new version.
The script records a successfully created prefix under %APPDATA%\Autodesk\Revit\Addins\RevitModelMcp-http-urlacl.txt.
install.ps1 -Uninstall removes that recorded prefix after the last installed Revit year for the current user, or prints its removal command when not elevated.
Reservations from older installers without an ownership record, and manually managed reservations, require manual removal.
After explicitly enabling HTTP, start Revit with a model open and check from PowerShell:
Test-NetConnection 127.0.0.1 -Port 53110
curl.exe -i http://127.0.0.1:53110/health
The TCP check reports TcpTestSucceeded: True; /health returns HTTP 200.
Client connection¶
The client reads REVIT_MCP_TOKEN; --token overrides it.
Prefer the environment populated by a secret store.
With the token already available in the client environment:
export REVIT_MCP_HOST=http://127.0.0.1:53110
uv run --directory server revit-model-mcp
HTTP has no built-in TLS.
Use an SSH tunnel, Tailscale or a TLS reverse proxy; the client validates HTTPS certificates.
Redirects are rejected to prevent forwarding the bearer token to another endpoint.
/health is unauthenticated and reveals the Revit and add-in versions, supported commands, protocol version, active document name, process ID, startup identity (startedUtc) and readOnly: { enabled, source, setBy, setAtUtc }.
The Python client sends a fresh 32-byte random base64url nonce in X-RevitMcp-Nonce on every health request.
The add-in answers with X-RevitMcp-Proof, the unpadded base64url HMAC-SHA256 of revit-model-mcp/health/v1\n followed by the decoded nonce.
The HMAC key is the UTF-8 bytes of the token text sent after Bearer, including for an overridden REVIT_MCP_TOKEN.
The client compares each proof before sending the token for a job.
Health requests without a valid nonce keep the same response body and do not return a proof.
An older add-in cannot supply the proof, so a newer Python client refuses jobs and asks for an add-in update; an older client works with a newer add-in.
All other routes require Authorization: Bearer <token>.
| Request | Result |
|---|---|
GET /health |
ok, revitVersion, addinVersion, protocolVersion, commands, documentName, processId, startedUtc, readOnly |
POST /jobs?timeout=120 |
Enqueue a job; return its state, position and ID |
GET /jobs/{id} |
State and position with HTTP 202 while pending; state and result with HTTP 200; HTTP 410 after expiry or eviction; HTTP 404 for an unknown ID |
POST /jobs/{id}/cancel |
JSON body {"clientId":"<server GUID>"} cancels that client's queued job; running actions finish |
GET /views/{name}/image?pixel=1600 |
PNG bytes from the same view exporter used by revit_export_view |
POST waits default to 120 seconds and accept 0-600 seconds.
HTTP 202 contains jobId, state and position; it means the accepted job is still queued or executing.
A timeout or client disconnect does not cancel a job.
Completed read results expire ten minutes after completion, action results after 24 hours; see result retention.
Do not resubmit an action after a timeout without checking its result and the model.
The Python client submits once with timeout=0, then polls within timeout_seconds.
Proof verification uses the existing health requests for each job and adds no HTTP request per job or poll.
REVIT_MCP_PICKUP_TIMEOUT_SECONDS in the server environment configures file pickup; HTTP ignores this timeout.
HTTP 401 means the token is missing or invalid.
HTTP 429 with error:queue_full means this client already has 16 queued jobs; retryAfterMs gives a retry hint.
HTTP 408 means the request body was not fully received within requestReadTimeoutSeconds; the add-in closes that connection.
HTTP 410 with "error":"expired" and "retryable":false means the result expired or was evicted by the limits above.
It is final: do not poll it again, and inspect the model before resubmitting an action.
HTTP 404 means the ID was never known to this add-in, so check the ID.
HTTP 503 with error:shutting_down means Revit is closing.
HTTP 400 with error:invalid_request means the job body is not valid UTF-8.
Direct HTTP POST /jobs validates a supplied jobId for action commands, whose results are persisted: the ID must be a 32-character hexadecimal GUID without dashes.
An invalid value returns HTTP 400 with error:invalid_job_id and message:"jobId must be a 32-character hexadecimal GUID without dashes.".
An absent or blank ID gets a generated ID.
Read submissions do not enforce this format; GET /jobs/{id}, POST /jobs/{id}/cancel, and image requests with jobId look up the supplied ID and return HTTP 404 for an unknown ID.
The Python HTTP transport URL-encodes lookup IDs and checks pending responses for a non-empty string ID, without GUID validation.
HTTP 403 rejects action jobs while the workstation read-only gate file is present.
MCP action tools return success:false and errorCode:"read_only" while the workstation lock is on.
The message names its source, time, and removal options.
Policy refusal returns errorCode:"policy_blocked" and asks for administrator help.
The pipe and HTTP transports preserve these messages.
The authenticated set-read-only command can run while locked.
HTTP and file jobs share one per-Revit scheduler. The add-in executes one job at a time and rotates between clients.
Jobs are limited to 1 MiB. The add-in returns HTTP 413 before reading a body
whose Content-Length exceeds that limit, and also enforces the limit while
streaming bodies without a declared length.
The same limits and the read deadline apply to the cancel request body.
When Revit shuts down, the add-in stops accepting connections, then finishes in-flight requests for up to shutdownDrainSeconds.
Queued jobs end as cancelled with the error Revit is closing., and requests waiting on them receive that job state before the listener stops.
A job that was already running ends as abandoned, not failed, because Revit cannot interrupt it and the change may still have been applied.
The result of an abandoned job has "abandoned":true; inspect the model before retrying it.
View names must be URL-encoded; pixel accepts 32-4000.
Image requests can return HTTP 202 with jobId after 120 seconds.
Poll /jobs/{id}, then append jobId={id} to the image URL to fetch that export without executing it again.
The optional document query must match the job's targetDocument.
The Python exporter follows this path and preserves response metadata and the local PNG path.
HTTP image artifacts fetched this way expire with the result.
Each HTTP endpoint belongs to one Revit process.
The server checks PID and startedUtc through /health before submission and uses only the configured endpoint; it does not scan ports.
The heartbeat advertises httpPort only after the listener binds successfully; disabled or failed listeners advertise null.
For several instances, configure a distinct port in each process environment before launch.
An occupied port disables HTTP for the later instance and produces a log message; its file channel remains available.
revit_list_instances reports the connected endpoint in HTTP mode.
targetDocument and targetProcessId are checked in the Revit API context before execution.
Remote setups¶
The recommended remote setup runs the whole server on the workstation and carries MCP stdio through SSH. For a corporate PC without administrator rights and without sshd, use option 2 with IT-provisioned Tailscale. Never expose the endpoint on the office LAN. If neither service is available, IT must provision a route first; this add-in cannot bypass that requirement.
Recommended: server over SSH stdio¶
Install the server once on the Windows workstation, under the account that runs Revit:
uv tool install revit-model-mcp
Register the client with ssh as the command, replacing revit-pc with the host alias from the client's SSH configuration:
{
"mcpServers": {
"revit-model-mcp": {
"command": "ssh",
"args": ["revit-pc", "revit-model-mcp", "--redact-paths"]
}
}
}
The remote server runs with its default REVIT_MCP_HOST=local, so it reaches Revit through the named pipe.
MCP messages flow through the SSH session; no PowerShell process starts per job and no port opens.
Set other server variables in the workstation user's environment, because the client's env block does not cross SSH.
The SSH account must be the Windows user running Revit: the pipe ACL admits only that user.
Key-based authentication avoids a password prompt that would block stdio.
ssh:<alias> in option 4 remains the fallback when the server cannot run on the workstation.
1. Same LAN¶
Use this only on a trusted network where the workstation owner permits direct access.
Set httpBind to 0.0.0.0 explicitly, or set REVIT_MCP_HTTP_BIND=0.0.0.0 before starting Revit.
Run once in an elevated Windows command prompt, replacing <user> with the account running Revit, such as DOMAIN\name:
netsh http add urlacl url=http://+:53110/ user=<user>
netsh advfirewall firewall add rule name="Model MCP" dir=in action=allow protocol=TCP localport=53110
On the Mac at the clone root, replace revit-host with the workstation hostname and supply REVIT_MCP_TOKEN through the secret store:
export REVIT_MCP_HOST=http://revit-host:53110
uv run --directory server revit-model-mcp
The bearer token and model data are plaintext on this route.
Prefer a tunnel whenever possible.
The listener logs the required URL reservation command on access denied.
Windows may also require an explicit loopback reservation under a restricted account.
For a specific bind address, use that address in the URL ACL instead of +.
See Microsoft HttpListener prefix guidance.
2. Different networks: Tailscale¶
Install Tailscale on both machines and join the same permitted tailnet.
Windows installation requires local administrator access once; IT must also provision the URL ACL and any required firewall permission for the Tailscale interface.
Routine use then runs under the Revit user's account.
Bind to the workstation's Tailscale IPv4 address instead of 0.0.0.0 when possible.
Use that same address in the URL ACL and restrict firewall access to the permitted Tailscale peer.
See Tailscale installation for Windows.
export REVIT_MCP_HOST=http://100.101.102.103:53110
uv run --directory server revit-model-mcp
For a Mac without administrator rights, standalone tailscaled and tailscale binaries can run in userspace mode with an HTTP proxy and user-owned state.
With those binaries available in PATH:
mkdir -p "$HOME/.local/state/tailscale-revit"
tailscaled --tun=userspace-networking \
--state="$HOME/.local/state/tailscale-revit/state" \
--socket="$HOME/.local/state/tailscale-revit/socket" \
--outbound-http-proxy-listen=127.0.0.1:1055
In another terminal:
tailscale --socket="$HOME/.local/state/tailscale-revit/socket" up
export http_proxy=http://127.0.0.1:1055
export https_proxy=http://127.0.0.1:1055
export REVIT_MCP_HOST=http://100.101.102.103:53110
uv run --directory server revit-model-mcp
The Python HTTP transport honors these standard proxy environment variables. Userspace mode does not create a system VPN interface; the proxy supplies the route. See Tailscale userspace networking.
3. Existing SSH: loopback port forward¶
This option keeps the workstation listener on 127.0.0.1 and is the safest setup when sshd is already available.
It opens no new office-LAN port.
ssh -N -L 53110:127.0.0.1:53110 user@host
In another terminal, with the bearer token in the environment:
export REVIT_MCP_HOST=http://127.0.0.1:53110
uv run --directory server revit-model-mcp
4. SSH file channel¶
The existing transport remains available without HTTP:
export REVIT_MCP_HOST=ssh:revit-host
uv run --directory server revit-model-mcp
The file channel needs no HTTP opt-in or URL reservation.
File channel¶
The default directory is %LOCALAPPDATA%\RevitModelMcp on the Windows account running Revit.
Set REVIT_MCP_CHANNEL_DIR to an absolute Windows path to override it.
The server and Revit must use the same directory.
The Revit environment must contain the override before Revit starts.
The add-in keeps new channel directories private and restricts existing default directories owned by the current user.
If another account can write to the override directory, the add-in ignores the override and uses the private default directory for heartbeats, jobs, responses and exports.
Set REVIT_MCP_CHANNEL_DIR on the server to that private default directory or remove the server override so discovery uses the same directory.
Discovery reads ROOT\instance_<pid>.json only, where ROOT is the configured directory.
Each v2 add-in owns ROOT\instances\<pid>\:
| Location | Role |
|---|---|
ROOT\instance_<pid>.json |
Shared discovery heartbeat |
ROOT\instances\<pid>\mcp_<uuid>.tmp |
Job before atomic publication |
ROOT\instances\<pid>\job_<jobId>.json |
Published job awaiting pickup |
ROOT\instances\<pid>\response_<timestamp>_<command>_<correlationId>.json |
Atomic correlated response |
ROOT\instances\<pid>\view_*.png |
Exported view before download |
Response temporary files also remain in the selected instance directory.
The server resolves a target before publishing any job.
With several running Revit processes, pass document (a unique title or file name) or process_id to choose one.
A document must match exactly one active document by case-insensitive title or file-name substring.
Zero or multiple matches fail before publication.
Actions retain their open-document resolution inside the selected process.
After pickup, a directed read resolves the target among open documents and can read an inactive document.
If the target closed, the error names the target and active documents and suggests passing document or calling revit_activate_document.
A fresh heartbeat and an existing process are pre-checks.
For v2 the server performs a bounded ping handshake with a fresh correlationId in the selected directory.
It checks the response correlation, responder.processId and unchanged heartbeat startedUtc before submitting the requested job.
The handshake has a 60-second budget, including the SSH connection limiter.
pluginResponding=true for v2 means this handshake succeeded.
Timed-out instances remain in discovery with pluginResponding=false.
Processes without a fresh heartbeat remain visible with empty document fields when no document filter is supplied.
The server rejects execution on an unconfirmed channel.
The selected PID, startup identity and directory remain fixed through polling, JSON/PNG reads and cleanup.
Publication checks that the selected process still exists and that its v2 heartbeat identity is current.
Atomic publication does not overwrite another job file.
Cleanup affects only the selected directory and the current job's files.
Two MCP clients targeting the same PID enqueue independently. Each server process supplies its own clientId.
Reading inactive documents remains outside this protocol. Queued actions can be cancelled; running actions finish.
Discovery heartbeat¶
The add-in rewrites ROOT\instance_<pid>.json atomically every five seconds.
Discovery version 3 keeps the v2 fields and adds the pipe and the open documents:
| Field | Meaning |
|---|---|
processId, revitVersion, startedUtc, updatedUtc |
Process identity and heartbeat time |
documentTitle, documentPath |
Active document, empty when none is active |
fileChannelVersion |
2 |
httpPort |
Bound HTTP port, or null |
discoveryVersion |
3 |
instanceId |
GUID generated once per Revit process lifetime |
pipeName |
RevitModelMcp.<pid>; absent when the pipe failed to start |
protocols |
pipe/1 when listening, always file/2, and http/1 when HTTP is bound |
documents |
Every open non-linked document: title, path, isActive, isFamilyDocument |
addinVersion, protocolVersion, commands |
Assembly informational version, protocol integer 1, and supported command names |
The server reads these fields from the selected heartbeat, pipe hello or HTTP health response.
The server accepts an add-in only when addinVersion has the same major version as the server; see Versions.
revit_list_instances returns these fields in local pipe mode; path redaction also covers documents[].path.
File protocol compatibility¶
The file channel version is 2.
The server refuses a heartbeat without fileChannelVersion, or with any other value, before publishing a job, and asks for an add-in of the same major version as the server.
There is no fallback to a shared ROOT layout and no single-process exception.
Update the server and the add-in together; see Versions.
The file transport operates independently of the optional HTTP listener. Revit API work runs through ExternalEvent. Pending files remain on disk if the server stops. There is no automatic cancellation of a published job. The channel relies on Windows file permissions.
Local host¶
REVIT_MCP_HOST=local is the default.
The server reads heartbeats from the channel directory and prefers the named pipe.
For add-ins without pipe/1, it invokes powershell.exe -NoProfile -NonInteractive -EncodedCommand on Windows.
That process checks Revit, publishes jobs and reads responses under the current Windows account.
Local mode requires a running Revit instance with the add-in loaded, and PowerShell for the file channel.
macOS and Linux clients run the server on Windows over SSH stdio, or use HTTP or ssh:<alias>.
SSH host¶
REVIT_MCP_HOST=ssh:<alias> selects a host in the client's SSH configuration.
--host ssh:<alias> provides the same setting on the command line.
The client invokes ssh with batch mode and a 45-second connection timeout.
Keepalive defaults are -o ServerAliveInterval=15 -o ServerAliveCountMax=3.
SSH checks the connection every 15 seconds and disconnects after three unanswered keepalive messages.
It disables terminal allocation, agent forwarding, X11 forwarding, configured port forwards and local commands before applying REVIT_MCP_SSH_OPTIONS.
The remote command runs Windows PowerShell with a UTF-16LE base64-encoded script.
Host aliases are validated. PowerShell string literals escape ASCII and Unicode single quotes, and response file names and startup timestamps are validated before use.
SSH credentials and routing belong to the user's SSH configuration.
Responses and exported PNG files are transferred as base64 in the PowerShell result.
The server checks the PNG signature and decodes the image into output_path or a new temporary directory.
output_path must end in .png.
An existing destination file produces an error.
The MCP result contains the image path and metadata without base64.
Each SSH invocation includes -o ControlMaster=auto -o ControlPath=<dir>/mux-%C -o ControlPersist=600 by default.
Commands for the same connection reuse the master instead of opening a new TCP connection for each PowerShell call.
The master persists for 600 idle seconds.
<dir> is $XDG_RUNTIME_DIR when nonempty. Otherwise the server uses a per-user temporary directory outside shared /tmp, or ~/.cache/revit-model-mcp.
On macOS and Linux, the server verifies the directory is owned by the current user and is not a symlink before setting mode 0700.
If the socket path is too long, the server tries ~/.cache/rmm. If the directory cannot be verified or the path is still too long, it disables multiplexing and logs a warning.
%C provides a hashed connection identifier within the Unix socket path limit.
REVIT_MCP_SSH_MUX=0 disables these built-in multiplexing options.
Use it on clients without multiplexing support, such as native Windows OpenSSH.
REVIT_MCP_SSH_OPTIONS appends shell-quoted arguments after built-in options and before the host, for example -o ServerAliveInterval=30 -p 2222.
OpenSSH uses the first value for an option.
Explicit ServerAliveInterval and ServerAliveCountMax options in REVIT_MCP_SSH_OPTIONS replace their built-in defaults.
A custom socket path or lifetime requires REVIT_MCP_SSH_MUX=0 plus all three Control* options in REVIT_MCP_SSH_OPTIONS.
Local mode ignores both variables and creates no multiplexing directory.
New SSH connections retain a limit of five per rolling 30 seconds within one host object. With built-in multiplexing, the first start and starts after 540 seconds without a successful SSH command count against the limit. Other multiplexed starts do not count. Without multiplexing, every SSH start counts. Polling waits ten seconds between attempts. Job preparation and final response retrieval each use one command. Transient failures during polling can be retried within the remaining timeout. Local mode uses the same file operations and polling without the SSH connection limiter.
Optional activation¶
Set REVIT_MCP_ACTIVATE_TASK to the name of an existing Windows scheduled task that activates Revit.
After 60 seconds without pickup the next check can invoke that task once.
Activation only occurs if the current job file still exists.
The server checks the task result and reports activation failure separately.
The task is optional and is never created by the server.
The default pickup timeout is 300 seconds, followed by a separate 120-second response timeout.
REVIT_MCP_PICKUP_TIMEOUT_SECONDS in the server environment overrides the file pickup timeout for all tools.
It accepts a positive integer; an invalid value raises RevitChannelError on first use.
Path redaction¶
REVIT_MCP_REDACT_PATHS=1 or --redact-paths strips directories from response documentPath and all nested path fields, including RVT/CAD/image link paths.
This applies to responder metadata and instance listings.
Image localPath remains available to the MCP client.
The option does not sanitize channel files or arbitrary strings in model data and errors.
See server configuration.
Client registration¶
For a remote workstation, register the server over SSH stdio. With the loopback SSH tunnel above running, register the HTTP endpoint from the clone root:
claude mcp add revit-model-mcp -e REVIT_MCP_HOST=http://127.0.0.1:53110 -e REVIT_MCP_REDACT_PATHS=1 -- uv run --directory "$PWD/server" revit-model-mcp
The server process must inherit REVIT_MCP_TOKEN from the client's environment or secret configuration.
For the SSH file channel, use -e REVIT_MCP_HOST=ssh:revit-host instead; no HTTP token is needed.
Request architecture¶
flowchart LR
Client[MCP client] <-->|stdio, optionally through SSH| Server[Python server]
Server <-->|named pipe pipe/1| Pipe[Add-in pipe listener]
Server <-->|local PowerShell or SSH| Channel[Windows file channel]
Server <-->|HTTP + bearer token| Endpoint[Add-in HTTP listener]
Pipe --> Scheduler[Job scheduler]
Channel --> Scheduler
Endpoint --> Scheduler
Scheduler <-->|ExternalEvent| Revit[Revit API]
The server submits jobs over the named pipe or HTTP, or writes them to the Windows file channel. All three feed one scheduler; the add-in runs one job at a time through ExternalEvent. The pipe and HTTP return JSON directly; the file channel keeps its file-based responses. A heartbeat identifies each Revit instance, its open documents and its protocols. The default tools read model data and export images. Actions use the same channel and execute in the Revit API context. See how it works, architecture and the feed format.
Installation from a clone¶
The add-in requires Windows and Revit 2022-2027.
Build with the .NET SDK selected by global.json.
The server requires Python 3.11 or later, uv and an MCP client.
Clone on each machine that will build or run a component:
git clone https://github.com/sharafutdinovdi/revit-model-mcp.git
cd revit-model-mcp
The commands below start from the repository root.
For a downloaded script, use Unblock-File .\install.ps1 to remove its downloaded-file block or Set-ExecutionPolicy -Scope Process Bypass for the current PowerShell session.
On Windows, close Revit and build and install for Revit 2026:
.\install.ps1 -Year 2026 -Source Build
Or install the latest GitHub release for every detected Revit year (2022-2027):
.\install.ps1 -Source Release
The inline build, copy and manifest-patching commands live in install.ps1.
Installation uses RevitModelMcp\ and RevitModelMcp.addin under %APPDATA%\Autodesk\Revit\Addins\<year>.
Use -Year 2024,2026 to select years and -Version 0.2.0 to pin a release.
-Source Release requires a release with an asset for each requested year: v0.1.0 ships R22-R26; v0.2.0 adds R27.
Add -SignThumbprint <thumbprint> to sign installed DLLs with a local code-signing certificate on workstations where Revit shows the unsigned add-in dialog on every rebuild.
Add -RegisterClaude to register the local server with Claude Code; both claude and uv must be on PATH.
Use -Uninstall -Year 2026 to remove that year's add-in; local settings remain intact.
The script refuses to run while Revit is open unless -Force is supplied.
Start Revit and open a model after installation, or restart it if it was already running.
The add-in creates %LOCALAPPDATA%\RevitModelMcp\instance_<processId>.json and updates it every five seconds.
It adds an "Activity" button (opens the MCP activity pane) and a "Connect AI app" button (registers the server in installed AI apps) in the MCP panel on the Add-Ins tab.
Batch transport¶
Batch collection writes durable run state under the workstation channel root. Its supervisor uses only the existing v2 per-PID file channel and validates the worker heartbeat identity before publication and termination. The Python tools use the existing local or SSH workstation host for state and artifact transfer. Direct HTTP batch tools are unavailable.
Progress notifications¶
The server sends MCP progress notifications during channel waits and revit_jobs waits.
Notifications report queue state, Revit API availability, and model progress.
The add-in publishes batch counters at data.progress.steps: completed counts attempted steps, total counts requested steps, and current names the last attempted action kind.
A failed step increments completed before the batch rolls back.
The existing data.steps array retains per-step results, and the final committed and rolledBack fields report the transaction outcome.
Pipe watchers send a nullable progress object when the stored progress changes, at most once every 500 ms.
State changes and terminal responses bypass that interval.
A batch push has the form {"type":"job","jobId":"j1","state":"running","position":0,"progress":{"steps":{"completed":17,"total":50,"current":"move"}}}.
The file channel exposes the same counters through status polling.
Running jobs emit a liveness message every two seconds.
Intermediate notifications are limited to one every 500 ms.
The final notification reports Done or Still running, poll revit_jobs.
A completed wait does not guarantee that an action succeeded; the tool response remains authoritative.
Messages include Queued in Revit (position 2), Waiting for the Revit API context, Running in Revit, 12 s, Model 3 of 12: Building.rvt, and Step 17 of 50.
Path redaction also applies to progress messages.
Clients receive notifications when their MCP request includes a progress token. Client support determines whether notifications appear in the interface. The absence of notifications is not a failure. The final tool response and job polling remain available.