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 nameparams: Command argumentsid: 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 theRevitClientConnectionclass, handling TCP socket creation, JSON-RPC framing, request-ID tracking viaresponseCallbacks, and buffer-based response parsing. -
src/utils/ConnectionManager.ts– Provides thewithRevitConnectionhelper 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 utilizewithRevitConnectionto 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
RevitClientConnectionclass insrc/utils/SocketClient.tsmanages 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
withRevitConnectionhelper insrc/utils/ConnectionManager.tsprovides 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →