How the SocketClient Handles Command Responses in Revit MCP: Asynchronous TCP Communication Deep Dive
The SocketClient handle command responses mechanism in the Revit MCP architecture relies on a robust JSON‑RPC‑style protocol layered over raw TCP sockets. By coupling unique request identifiers with promise‑based callbacks, the RevitClientConnection class enables reliable, non‑blocking communication between the MCP server and Autodesk Revit, even when network fragmentation splits messages into multiple packets.
The Core Mechanism: Request IDs and Callback Registration
When invoking sendCommand, the SocketClient first generates a unique request ID via generateRequestId and immediately stores a resolver function in an internal responseCallbacks map keyed by that ID. This registration (lines 111‑130 in src/utils/SocketClient.ts) ensures that when the server eventually echoes the same ID back, the client can match the response to the original caller. The callback wraps a Promise, allowing sendCommand to return immediately while waiting asynchronously for Revit's reply.
Buffering and Parsing TCP Streams
TCP sockets do not guarantee intact JSON delivery; messages may arrive fragmented. The client addresses this with a buffering strategy outlined in lines 23‑27:
| Step | Implementation | Explanation |
|---|---|---|
| Buffering | socket.on("data", …) → this.buffer += dataString (lines 23‑27) |
Accumulates partial data fragments until a complete JSON object is available. |
| Parsing | processBuffer() (lines 42‑52) |
Attempts JSON.parse(this.buffer). Success means the buffer contains a full response; otherwise, the client waits for additional data. |
| Dispatching | handleResponse(this.buffer) (lines 46‑48) → const requestId = response.id || "default" (lines 80‑81) → this.responseCallbacks.get(requestId) (line 83) |
Maps the echoed id to the stored callback. |
| Callback Execution | Callback defined inside sendCommand (lines 111‑130) |
Parses the JSON and resolves the Promise with response.result or rejects on response.error. |
| Timeout | setTimeout (lines 135‑141) |
If no response arrives within 2 minutes, the callback is purged and the Promise rejects, preventing memory leaks. |
Real‑World Usage Examples
Sending a Command and Awaiting Results
The following pattern demonstrates how SocketClient handle command responses in practice:
import { withRevitConnection } from "./utils/ConnectionManager.js";
async function getProjectInfo() {
return await withRevitConnection(async (client) => {
// `sendCommand` returns a Promise that resolves when the callback runs
const result = await client.sendCommand("GetProjectInfo", { /* params */ });
console.log("Project info:", result);
return result;
});
}
Key points demonstrated
withRevitConnectionguarantees the socket is connected before invokingsendCommand.sendCommandinternally registers a callback (responseCallbacks.set) and resolves the promise when the server echoes the sameid.
Executing Multiple Commands Concurrently
Because each SocketClient handle command responses operation is independent, you can parallelize calls safely:
import { withRevitConnection } from "./utils/ConnectionManager.js";
async function parallelQueries() {
return await withRevitConnection(async (client) => {
const promises = [
client.sendCommand("GetElementIds", { viewId: 1 }),
client.sendCommand("GetFamilyTypes", { familyName: "Door" }),
client.sendCommand("GetCurrentViewInfo")
];
const [ids, types, viewInfo] = await Promise.all(promises);
console.log({ ids, types, viewInfo });
});
}
How it works
- Each call generates its own request ID and stores a distinct callback.
- Responses may arrive in any order; the matching callback resolves the appropriate Promise.
Key Source Files
| File | Role | Direct Link |
|---|---|---|
src/utils/SocketClient.ts |
Core socket wrapper that creates request IDs, buffers inbound data, parses JSON, and maps responses to callbacks. | SocketClient.ts |
src/utils/ConnectionManager.ts |
Helper that establishes the TCP connection, waits for the connect event, and ensures clean disconnection after the operation. |
ConnectionManager.ts |
src/tools/send_code_to_revit.ts |
Example tool script that invokes withRevitConnection and sendCommand to perform Revit operations. |
send_code_to_revit.ts |
Summary: Why This Matters for SocketClient Handle Command Responses
By combining unique request identifiers, TCP buffering, and promise‑based callbacks, the Revit MCP SocketClient achieves rock‑solid asynchronous communication. The explicit timeout handling (lines 135‑141) guards against orphaned promises, while the callback registry (responseCallbacks) permits concurrent command execution without blocking the event loop. Developers interacting with Revit via sendCommand or withRevitConnection benefit from a clean, async/await interface that abstracts away the underlying socket complexity, ensuring that every SocketClient handle command responses cycle completes reliably—even when network latency or message fragmentation occurs.
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 →