revit-mcp-hardened

revit-mcp-hardened

Hardened MCP server for Autodesk Revit enabling LLMs to inspect and manipulate Revit models via pyRevit, with token authentication and capability profiles.

Category
Visit Server

README

MCP server for Revit - Python

A pyRevit-oriented implementation of the Model Context Protocol (MCP) for Autodesk Revit

Hardened fork. Derived from mcp-server-for-revit-python by Juan D. Rodriguez and Jean-Marc Couffin (MIT) — see NOTICE.md for what is inherited and what was added here. This fork adds token authentication, capability profiles, gating for arbitrary code execution, a strict MCP error contract, the nine tools left pending on the upstream roadmap, and CI.

Read SECURITY.md before connecting this to a real model. Works with any MCP client — Claude Code, Claude Desktop, GitHub Copilot, OpenAI Codex, Cursor. Setup for each is in OFFICE-SETUP.md.

How?

  • This minimal implementation leverages the Routes module inside pyRevit to create a bridge between Revit and Large Language Models (LLMs).
  • It provides a straightforward template to get started quickly, letting you prototype and iterate tools to give LLMs access to your Revit models.
  • These tools are designed to be expanded for your specific use cases. You're very welcome to fork the repo and make your own contributions.
  • Note: The pyRevit Routes API is currently in draft form and subject to change. It has no built-in authentication, and it binds every network interface unless you set its host explicitly. This fork adds an optional shared-secret token, capability profiles, and gating for arbitrary code execution on top of it — see SECURITY.md, which you should read before deploying to more than one machine.

Batteries Included

This repo is aimed at:

  • Beginners to the Revit API
  • Python specialists who aren't versed in C#
  • Anyone wanting to prototype and iterate quickly with LLMs and Revit

It contains:

  • A complete Routes implementation for pyRevit
  • A minimal MCP server script to connect to any MCP-compatible client
  • Several test commands to get you started right away

Key Architecture Components

The system runs as two separate servers working together in a chain:

Claude / LLM Client
       |
       |  MCP Protocol (stdio or HTTP)
       v
  main.py  (MCP Server)
       |
       |  HTTP requests (localhost:48884)
       v
  pyRevit Routes  (REST API running inside Revit)
       |
       |  Revit API calls
       v
  Revit Application

main.py is the MCP server. It speaks the MCP protocol so that Claude (or any MCP-compatible client) can call tools. When a tool is called, main.py translates it into an HTTP request and forwards it to Revit.

pyRevit Routes is a lightweight REST API that runs inside the Revit process. It receives those HTTP requests, executes Revit API code (since it has direct access to the running instance), and returns JSON responses.

They never conflict because they serve different roles, speak different protocols, and listen on different ports.

Note: The Launch & Document tools (launch_revit, list_revit_installations) are the exception — they run entirely on the MCP side, using subprocess to start Revit and then polling the pyRevit Routes health endpoint until the bridge is ready.

  1. MCP Server (main.py):
  • Built with FastMCP
  • Handles HTTP communication with Revit Routes API
  • Registers tools from modular tool system
  • Provides helper functions for GET/POST/Image requests
  1. pyRevit Extension (revit-mcp-python.extension/):
  • Contains the Routes API that runs inside Revit
  • Modular route registration in startup.py
  • Individual route modules in revit_mcp/ directory
  1. Tool Registration System (tools/):
  • Modular tool organization by functionality
  • Central registration through tools/__init__.py
  • Each module registers its own tools with the MCP server

Supported Tools

Current Implementation Status

Tool Name Status Category Description
get_revit_status ✅ Implemented Status & Connectivity Check if the Revit-MCP API is active and responding
get_revit_model_info ✅ Implemented Model Information Get comprehensive information about the current Revit model
list_levels ✅ Implemented Model Information Get all levels with elevation information
get_revit_view ✅ Implemented View & Image Export a specific Revit view as an image
list_revit_views ✅ Implemented View & Image Get a list of all exportable views organized by type
place_family ✅ Implemented Family & Placement Place a family instance at specified location with custom properties
list_families ✅ Implemented Family & Placement Get a flat list of available family types (with filtering)
list_family_categories ✅ Implemented Family & Placement Get a list of all family categories in the model
get_current_view_info ✅ Implemented View Information Get detailed information about the currently active view
get_current_view_elements ✅ Implemented View Information Get all elements visible in the current view
color_splash ✅ Implemented Visualization Color elements based on parameter values
clear_colors ✅ Implemented Visualization Remove color overrides from a category
list_category_parameters ✅ Implemented Visualization List parameters available on a category
execute_revit_code ✅ Implemented Code Execution Execute IronPython code directly in Revit context
list_revit_installations ✅ Implemented Launch & Document Discover all Revit versions installed on the system
launch_revit ✅ Implemented Launch & Document Launch Revit, optionally with a file, and poll for readiness
open_document ✅ Implemented Launch & Document Open a document in running Revit (supports detach and audit)
close_document ✅ Implemented Launch & Document Close the active document
save_document ✅ Implemented Launch & Document Save or Save As the active document
sync_with_central ✅ Implemented Launch & Document Synchronize a workshared document with central
get_selected_elements ✅ Implemented Selection Management Read the elements currently selected in the Revit UI
create_line_based_element ✅ Implemented Element Creation Create line-based elements (walls, beams, pipes)
create_surface_based_element ✅ Implemented Element Creation Create surface-based elements (floors, ceilings)
delete_elements ✅ Implemented Element Management Delete elements by id, with a dry-run mode
modify_elements ✅ Implemented Element Management Set instance parameters on one or more elements
reset_model ✅ Implemented Element Management Delete whole categories, dry-run by default and title-confirmed
tag_elements ✅ Implemented Annotation Tag every element of a category in the active view
search_modules ✅ Implemented Integration Discover Revit commands and installed pyRevit extensions
use_module ✅ Implemented Integration Post a built-in Revit command
get_revit_security_status ✅ Implemented Security Report the security posture of both halves of the bridge

Capability profiles

REVIT_MCP_PROFILE decides which of these tools are registered. A tool that is not registered is invisible to the client and cannot be called, so this is an enforced boundary rather than a suggestion.

Profile Tools Contents
read 12 Inspection only. Nothing here can modify a model.
standard (default) 26 read + creation, modification, deletion, tagging, colours, document operations
full 29 standard + reset_model and Revit command invocation
full + REVIT_MCP_ALLOW_CODE_EXEC=1 30 + execute_revit_code

execute_revit_code requires both the full profile and the explicit opt-in flag, on the MCP server and on the Revit side. Neither knob alone enables it.

Smaller profiles also select tools more reliably — routing accuracy degrades past roughly 18 tools on a single agent.

Units: every coordinate and length in the creation and modification tools is in decimal feet (the Revit API's internal unit), regardless of your project's display units. Divide millimetres by 304.8.

Set these in your MCP client's env block, not in your shell — the stdio transport passes the server only a minimal default environment. Full setup instructions are in OFFICE-SETUP.md.

Claude listing model elements in the Desktop interface

Claude getting a view in the Desktop interface

Getting Started

Installing uv:

Refer to ./README_UV.md

Installing the Extension on Revit

Activate pyRevit Routes

  1. In Revit, navigate to the pyRevit tab
  2. Open Settings
  3. Go to Routes > activate Routes Server pyRevit will start listening on port http://localhost:48884/

Install from pyRevit:

  1. In Revit, navigate to the pyRevit tab
  2. Open Extensions
  3. Select the MCP Server for Revit Python Extension > Install extension
  4. Select location, default is %APPDATA%\Roaming\pyRevit\Extensions
  5. Enable and wait for pyRevit to reload. Restart Revit if necessary.

Manual Installation on a custom directory:

  1. Clone the repo in a custom location:
    git clone https://github.com/mcp-servers-for-revit/mcp-server-for-revit-python
    
  2. Add .extension to the root folder name
  3. In Revit, navigate to the pyRevit tab
  4. Open Settings
  5. Under "Custom Extensions", add the path to the .extension folder
  6. Save settings and reload pyRevit (you might need to restart Revit entirely)

Testing Your Connection

Once installed, test that the Routes API is working:

  1. Open your web browser and go to:

    http://localhost:48884/revit_mcp/status/
    
  2. If successful, you should see a response like:

    {"status": "active",
     "health": "healthy",
     "revit_available": true,
     "document_title": "your_revit_filename",
     "api_name": "revit_mcp"}
    

The Routes Service will now load automatically whenever you start Revit. To disable it, simply remove the extension path from the pyRevit settings.

Using the MCP Client

Testing with the MCP Inspector

The MCP SDK includes a handy inspector tool for debugging:

mcp dev main.py

Then access http://127.0.0.1:6274 in your browser to test your MCP server interactively.

Transport Modes

The MCP server supports multiple transport modes for different use cases:

Flag Transport Endpoints Use Case
(none) stdio stdin/stdout Claude Desktop / Claude Code default
--sse SSE only /sse, /messages/ Legacy clients
--streamable-http HTTP only /mcp Modern HTTP clients
--combined Both All above Maximum compatibility

Running with combined transport (recommended for HTTP):

uv run --with "mcp[cli]" main.py --combined

This starts the server on http://127.0.0.1:8000 with both SSE and streamable-HTTP endpoints available.

Testing the endpoints:

# Test streamable-http
curl -X POST http://localhost:8000/mcp

# Test SSE
curl http://localhost:8000/sse

Connecting to Claude Desktop

The simplest way to install your MCP server in Claude Desktop:

mcp install main.py

Or for manual installation:

  1. Open Claude Desktop → Settings → Developer → Edit Config
  2. Add this to the mcpServers section:
{
  "mcpServers": {
    "Revit Connector": {
      "command": "uv",
      "args": [
        "run",
        "--with",
        "mcp[cli]",
        "mcp",
        "run",
        "/absolute/path/to/main.py"
      ]
    }
  }
}

For HTTP transport mode, configure Claude Desktop with:

{
  "mcpServers": {
    "Revit Connector": {
      "url": "http://localhost:8000/mcp"
    }
  }
}

Connecting to Claude Code

claude mcp add -s user "Revit-Connector" -- uv run --with "mcp[cli]" mcp run /absolute/path/to/main.py

Creating Your Own Tools

The modular architecture of this project makes adding functionalities relatively simple. The provided LLM.txt file also gives your language model the necessary context to get started right away.

The process involves three main parts:

Part 1: Create the Route Module in Revit

Create a new Python file within the revit-mcp-python.extension/revit_mcp/ directory (e.g., revit_mcp/your_module.py). This module will contain all the related functions you want to expose.

# In revit-mcp-python.extension/revit_mcp/your_module.py

# -*- coding: UTF-8 -*-
"""
Your Module for Revit MCP
Handles your specific functionality.
"""
from pyrevit import routes, revit, DB
import json
import logging

from . import auth

# Standard logger setup
logger = logging.getLogger(__name__)

def register_your_routes(api):
    """Register all your routes with the API."""

    # ---- Example 1: A GET request for reading data ----
    @api.route('/your_endpoint/', methods=["GET"])
    def get_project_title(doc, request):
        """Gets the project title from the Revit model."""
        # Every route must authorize. Take `request` in the signature even on a
        # GET - pyRevit binds it by name, and the token travels in the query
        # string there because HTTP headers never reach route handlers.
        denied = auth.check(request)
        if denied:
            return denied

        try:
            value = doc.Title
            return routes.make_response(data={"status": "success", "data": value})
        except Exception as e:
            logger.error("Get project title failed: {}".format(str(e)))
            return routes.make_response(
                data={
                    "status": "error",
                    "error": str(e),
                    "error_type": type(e).__name__,
                    # Tell the caller whether retrying could ever help.
                    "retryable": False,
                },
                status=500,
            )

    # ---- Example 2: A POST request for modifying the model ----
    @api.route('/modify_model/', methods=["POST"])
    def modify_model(doc, request):
        """Handles POST requests for modifying the Revit model."""
        # write=True so this route is refused in read-only mode.
        denied = auth.check(request, write=True)
        if denied:
            return denied

        try:
            data = json.loads(request.data) if isinstance(request.data, str) else request.data

            # Use a transaction for all model modifications
            t = DB.Transaction(doc, "Modify Model via MCP")
            t.Start()

            try:
                element_id = data.get("element_id")
                new_value = data.get("new_value")
                element = doc.GetElement(DB.ElementId(int(element_id)))
                param = element.LookupParameter("Comments")
                param.Set(new_value)

                t.Commit()
                return routes.make_response(data={"status": "success", "result": "Element modified."})

            except Exception as tx_error:
                if t.HasStarted() and not t.HasEnded():
                    t.RollBack()
                raise tx_error

        except Exception as e:
            logger.error("Modify model failed: {}".format(str(e)))
            return routes.make_response(
                data={
                    "status": "error",
                    "error": str(e),
                    "error_type": type(e).__name__,
                    "retryable": False,
                },
                status=500,
            )

    logger.info("Your custom routes were registered successfully.")

A route that omits auth.check fails CI: tests/unit/test_ironpython_compat.py counts route decorators against auth calls. If a route genuinely must answer without a token, give it an explicit # auth-exempt: <reason> comment — that keeps the exemption a reviewable decision instead of an oversight.

Remember this file runs on IronPython 2.7. No f-strings, no type annotations; use .format(). CI enforces that too.

Part 2: Create the MCP Tool Module

Create the corresponding tools for the MCP server in the tools/ directory (e.g., tools/your_tools.py). This module will use the revit_get and revit_post helpers from main.py.

# In tools/your_tools.py
# -*- coding: utf-8 -*-
"""Your tools for the MCP server."""

from mcp.server.fastmcp import Context
from .utils import format_response

def register_your_tools(mcp, revit_get, revit_post, revit_image=None):
    """Register your tools with the MCP server."""

    # ---- Tool for the GET request ----
    @mcp.tool()
    async def get_revit_project_title(ctx: Context) -> str:
        """Return the title of the currently open Revit project.

        Use this to confirm which model is open before acting on it - the
        title is also what reset_model requires as confirmation.

        Does NOT return the file path or whether the model is workshared;
        get_revit_model_info covers those.
        """
        # No try/except: a bridge failure must raise so MCP reports isError.
        response = await revit_get("/your_endpoint/", ctx)
        return format_response(response)

    # ---- Tool for the POST request ----
    @mcp.tool()
    async def modify_revit_element_comment(
        element_id: int,
        new_value: str,
        ctx: Context = None
    ) -> str:
        """Set the 'Comments' parameter on one element.

        Get element ids from get_selected_elements or
        get_current_view_elements. Returns confirmation of what changed.

        Does NOT create elements or edit type parameters.

        Args:
            element_id: The id of the element to modify.
            new_value: The new comment to apply.
        """
        payload = {"element_id": element_id, "new_value": new_value}
        response = await revit_post("/modify_model/", payload, ctx)
        return format_response(response)

Two conventions worth copying from the examples above:

  • Do not wrap the bridge call in try/except. format_response and the transport layer raise ToolError on failure, which is what sets isError on the MCP result. Catching it and returning the message hands the model a successful result that merely describes a failure.
  • Write the description for a reader who cannot see the code. State what the tool does, when to reach for it, what it returns, and — most usefully — what it does not do. That text is the only signal the model has when choosing between tools.

Then register it in tools/__init__.py under the right profile: read-only tools in _register_read_tools, anything that can change a model in _register_write_tools, destructive or administrative tools in _register_full_tools.

Part 3: Register Your New Modules

1. Register the Route Module

Open revit-mcp-python.extension/startup.py and add your new route registration function.

# In revit-mcp-python.extension/startup.py

# ... (other imports)
# Import the registration function from your new module
from revit_mcp.your_module import register_your_routes

def register_routes():
    """Register all MCP route modules"""
    api = routes.API('revit_mcp')
    try:
        # ... (existing route registrations)

        # Register your new routes (this registers all functions inside)
        register_your_routes(api)

        logger.info("All MCP routes registered successfully")
    except Exception as e:
        logger.error("Failed to register MCP routes: {}".format(str(e)))
        raise

2. Register the Tool Module

Open tools/__init__.py and add your new tool registration function.

# In tools/__init__.py

# ... (other tool imports)
# Import the registration function from your new tool module
from .your_tools import register_your_tools

def register_tools(mcp_server, revit_get_func, revit_post_func, revit_image_func):
    """Register all tools with the MCP server"""

    # ... (existing tool registrations)
    # Register your new tools (this registers all tools inside)
    register_your_tools(mcp_server, revit_get_func, revit_post_func, revit_image_func)

    return mcp_server

Roadmap

Everything on the original roadmap table above is now implemented. Delivered in this fork:

  • Authentication and security enhancements — shared-secret token on every route, capability profiles, read-only mode, code-execution gating with an audit log, and a get_revit_security_status tool. See SECURITY.md.
  • More advanced Revit tools — selection, modification, deletion, line- and surface-based creation, tagging, command invocation, model reset.
  • Better error handling — failures raise ToolError so MCP reports isError: true, each carrying a retry verdict; empty results stay successes.
  • CI — ruff and pytest on Python 3.11/3.12/3.13, plus static guards that fail the build if a route ships without an auth check or if Python-3-only syntax reaches the IronPython half.

Still open:

  • Creating a Client inside Revit
  • Benchmarking with local models
  • Openings, sloped floors, and curved geometry in the creation tools (currently straight lines and planar boundaries only — use execute_revit_code for the rest)
  • MEP beyond pipes — ducts, cable trays, fittings
  • Integration test coverage for the newer routes (they need a live Revit)

Contributing

Contributions are welcome! Feel free to submit pull requests or open issues for any bugs or feature requests. Feel free to reach out to me if you have any questions, ideas

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured
E2B

E2B

Using MCP to run code via e2b.

Official
Featured