How revit-mcp Communicates with the Revit Plugin via TCP Sockets: JSON-RPC Architecture Explained

revit-mcp communicates with the Revit plugin via a lightweight JSON-RPC 2.0 protocol over TCP sockets, using request-ID mapping and buffer-based parsing to handle concurrent commands between the CLI and Revit.

The revit-mcp project enables programmatic control of Autodesk Revit through a Model Context Protocol (MCP) implementation. Understanding how revit-mcp communicates with the Revit plugin via TCP sockets reveals the architecture that allows external tools to drive Revit operations remotely through a robust JSON-RPC interface.

The Seven-Step TCP Communication Flow

The communication between revit-mcp and the Revit plugin follows a precise sequence implemented in src/utils/SocketClient.ts and src/utils/ConnectionManager.ts.

Step 1: Creating the TCP Client

The RevitClientConnection class initializes a Node.js net.Socket configured to connect to localhost:8080. This establishes the endpoint where the Revit plugin listens for incoming commands.

Step 2: Opening the Socket Connection

The connect() method initiates the TCP handshake. Upon successful connection, the socket emits a "connect" event, setting the internal isConnected flag to true and preparing the channel for data transmission.

Step 3: Framing Requests as JSON-RPC 2.0

For every command, the sendCommand() method constructs a JSON-RPC 2.0 envelope. The payload includes:

  • jsonrpc: "2.0"
  • method: The Revit command name
  • params: Command arguments
  • id: A unique request identifier generated for correlation

Step 4: Writing to the TCP Stream

The serialized JSON string is written directly to the socket using socket.write(). This transmits the command to the Revit plugin's TCP server without additional protocol overhead.

Step 5: Buffering Incoming Data

Because TCP is a streaming protocol, response data may arrive in fragments. The client maintains an internal buffer string that accumulates chunks from the "data" event. The system attempts JSON.parse() on the buffer; if parsing fails due to incomplete data, it waits for the next chunk.

Step 6: Dispatching Responses by Request ID

Once a complete JSON message is parsed, handleResponse() extracts the id field and looks up the corresponding callback in the responseCallbacks Map. The system resolves or rejects the original Promise based on the presence of an error field in the JSON-RPC response, enabling proper async/await patterns.

Step 7: Connection Cleanup

The withRevitConnection helper in src/utils/ConnectionManager.ts wraps the entire lifecycle. After the operation completes, it calls disconnect() to destroy the socket and release system resources, ensuring no lingering TCP connections remain.

Key Architectural Components

The socket communication relies on several sophisticated patterns that ensure reliability and concurrency.

JSON-RPC 2.0 Protocol Standardization

The system uses the JSON-RPC 2.0 specification to structure all messages. This standardization provides a clear separation between method invocation, parameter passing, and error handling, making the protocol extensible for future Revit commands.

Request-ID Mapping for Concurrency

To handle multiple simultaneous commands, the client maintains a Map<string, (response: string) => void> called responseCallbacks. Each outgoing request receives a unique ID, and the corresponding Promise resolver is stored in this Map. When the response arrives, the ID ensures the correct callback receives the data, enabling true concurrent request handling over a single TCP socket.

Buffer-Based Message Parsing

TCP streams do not respect message boundaries. The client implements a buffer-based parsing strategy where incoming data chunks are concatenated to an internal buffer. The system attempts to parse the buffer as JSON; if it receives a partial message, it simply waits for the next "data" event. This approach handles network fragmentation without complex framing protocols.

Promise-Based Async API

The sendCommand() method returns a native JavaScript Promise, allowing higher-level tools to use async/await syntax. This abstraction hides the underlying socket complexity, making tool implementations in src/tools/*.ts clean and maintainable.

Connection Manager Abstraction

The withRevitConnection function in src/utils/ConnectionManager.ts provides a connection manager pattern that handles the complete lifecycle: connection establishment, operation execution, and guaranteed cleanup. This prevents resource leaks and simplifies error handling across the codebase.

Implementation Examples

Using the Connection Manager Wrapper

The recommended approach uses the withRevitConnection helper to handle socket lifecycle automatically:

import { withRevitConnection } from "./utils/ConnectionManager.js";

async function getSelectedElements() {
  const result = await withRevitConnection(async (client) => {
    // The method name matches a command implemented in the Revit plug‑in
    return client.sendCommand("GetSelectedElements");
  });
  console.log("Selected element IDs:", result);
}

getSelectedElements();

The withRevitConnection wrapper ensures the socket is opened, the command is sent, and the socket is closed afterward.

Direct Client Implementation

For scenarios requiring manual connection control, instantiate RevitClientConnection directly:

import { RevitClientConnection } from "./utils/SocketClient.js";

const client = new RevitClientConnection("localhost", 8080);

client.connect();

client
  .sendCommand("CreateWall", { start: [0, 0, 0], end: [10, 0, 0] })
  .then((wallId) => console.log("Created wall:", wallId))
  .catch((err) => console.error("Error:", err))
  .finally(() => client.disconnect());

Here we manually manage the socket lifecycle and send a custom command with parameters.

Core Source Files

The TCP socket implementation spans several key files in the repository:

  • src/utils/SocketClient.ts – Implements the RevitClientConnection class, handling TCP socket creation, JSON-RPC framing, request-ID tracking via responseCallbacks, and buffer-based response parsing.

  • src/utils/ConnectionManager.ts – Provides the withRevitConnection helper that abstracts connection lifecycle management, ensuring proper cleanup after operations complete.

  • src/tools/*.ts (e.g., send_code_to_revit.ts, get_selected_elements.ts) – High-level tool modules that utilize withRevitConnection to execute specific Revit commands.

  • src/index.ts – Entry point that registers CLI commands and initializes the tool ecosystem.

Summary

  • revit-mcp establishes TCP communication with the Revit plugin through a JSON-RPC 2.0 protocol running over a plain TCP socket to localhost:8080.
  • The RevitClientConnection class in src/utils/SocketClient.ts manages the socket lifecycle, implements buffer-based parsing to handle TCP fragmentation, and uses a request-ID mapping system to correlate responses with pending Promises.
  • The withRevitConnection helper in src/utils/ConnectionManager.ts provides automatic resource management, ensuring sockets are properly closed after operations complete.
  • All commands follow the JSON-RPC 2.0 specification with unique request IDs, enabling concurrent command execution over a single persistent TCP connection.

Frequently Asked Questions

How does revit-mcp handle multiple concurrent commands over a single TCP socket?

revit-mcp handles concurrency through request-ID mapping. Each command sent via sendCommand() receives a unique identifier stored in the responseCallbacks Map. When the Revit plugin returns a response, the handleResponse() method uses the JSON-RPC id field to look up the correct Promise resolver, allowing multiple simultaneous operations without response mixing.

What happens if a TCP message arrives in multiple fragments?

The client implements buffer-based parsing to handle TCP fragmentation. Incoming data chunks from the "data" event are appended to an internal buffer string. The system attempts JSON.parse() on the accumulated buffer; if parsing fails due to incomplete data, the client waits for the next chunk. This approach ensures reliable message reconstruction regardless of network packet boundaries.

Why does revit-mcp use JSON-RPC 2.0 instead of raw JSON?

The JSON-RPC 2.0 protocol provides standardized request/response envelopes with required method, params, id, and error fields. This standardization enables the request-ID correlation system, structured error handling, and future extensibility. Raw JSON would require custom framing logic and lack the built-in error semantics that JSON-RPC provides for the Revit command interface.

How do I ensure the TCP connection closes properly after operations?

Use the withRevitConnection helper from src/utils/ConnectionManager.ts instead of manual socket management. This function automatically calls disconnect() to destroy the socket and release system resources after your operation completes, even if an error occurs. For manual control, always call client.disconnect() in a finally block as shown in the direct implementation examples.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →