How the Module Search and Usage System Works in revit-mcp

The module search and usage system in revit-mcp dynamically discovers available tools at runtime through search_modules and executes them via use_module, enabling AI clients to introspect and invoke Revit operations without hardcoded commands.

The revit-mcp repository implements a Model Context Protocol (MCP) server that exposes Revit functionality as callable tools for AI assistants. At the heart of this architecture lies the module search and usage system, which provides runtime introspection capabilities that allow clients to discover available operations and execute them dynamically.

Dynamic Tool Discovery Architecture

The Registration Pipeline in src/tools/register.ts

The foundation of the module search and usage system rests in src/tools/register.ts, which exports the registerTools function. This function performs a filesystem scan of the src/tools directory at runtime, dynamically importing each tool module and invoking its registration function.

// src/tools/register.ts
export async function registerTools(server: McpServer) {
  const __filename = fileURLToPath(import.meta.url);
  const __dirname = path.dirname(__filename);

  // All files in the same directory …
  const files = fs.readdirSync(__dirname);

  // … that are *.ts or *.js but **not** index/register itself
  const toolFiles = files.filter(
    (file) =>
      (file.endsWith(".ts") || file.endsWith(".js")) &&
      file !== "index.ts" &&
      file !== "index.js" &&
      file !== "register.ts" &&
      file !== "register.js"
  );

  // For each candidate…
  for (const file of toolFiles) {
    try {
      // Build the import path that points to the compiled .js file
      const importPath = `./${file.replace(/\.(ts|js)$/, ".js")}`;

      // Dynamically import the module (e.g. "./get_selected_elements.js")
      const module = await import(importPath);

      // Find the exported function whose name starts with “register”
      const registerFunctionName = Object.keys(module).find(
        (key) => key.startsWith("register") && typeof module[key] === "function"
      );

      // Call it – the function itself will call `server.tool(...)`
      if (registerFunctionName) {
        module[registerFunctionName](server);
        console.error(`已注册工具: ${file}`);
      } else {
        console.warn(`警告: 在文件 ${file} 中未找到注册函数`);
      }
    } catch (error) {
      console.error(`注册工具 ${file} 时出错:`, error);
    }
  }
}

Tool Registration Pattern

Each tool file follows a consistent pattern, exporting a registerXTool function that receives the McpServer instance. This function calls server.tool() to define the operation's name, description, Zod schema, and handler. For example, src/tools/get_selected_elements.ts exports registerGetSelectedElementsTool which registers the tool with the server.

The Introspection Tools

How search_modules Works

The search_modules tool is generated at build time in src/tools/search_modules.ts. When invoked, it returns a complete catalogue of all registered tools, including their names, descriptions, and argument schemas. This enables AI clients to understand available Revit operations without prior knowledge.

The tool handler calls an internal API equivalent to server.getRegisteredTools() to retrieve the list of all tools that have been registered by registerTools, then returns a JSON array containing each tool's metadata.

How use_module Works

Complementing the discovery mechanism, use_module (implemented in src/tools/use_module.ts) acts as a universal executor. It accepts a module name and an args object, looks up the corresponding registered tool, and forwards the arguments to that tool's handler.

The tool definition uses a Zod schema with module: z.string() and args: z.any(). When executed, the handler performs an internal lookup equivalent to server.getToolByName(module) and invokes the located tool's handler with the supplied arguments.

Runtime Execution Flow

Server Initialization in src/index.ts

The entry point at src/index.ts initializes the McpServer and triggers the tool registration process before connecting the transport layer.

// src/index.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { registerTools } from "./tools/register.js";

const server = new McpServer({ name: "revit-mcp", version: "1.0.0" });

async function main() {
  // ① Register *all* tools in src/tools
  await registerTools(server);

  // ② Connect the MCP transport (e.g. stdio for Claude Desktop)
  const transport = new StdioServerTransport();
  await server.connect(transport);
}

When main() runs, registerTools performs the filesystem scan and dynamic imports, registering every tool including search_modules and use_module.

Client Interaction Pattern

When an AI client connects to the revit-mcp server, it typically follows a two-step workflow:

  1. Discovery: Call search_modules to retrieve the complete tool catalogue with schemas
  2. Execution: Call use_module with the selected tool name and validated arguments

This pattern eliminates the need for clients to hardcode available operations, allowing the server to add new Revit tools simply by adding new files to the src/tools directory.

Practical Usage Examples

Discovering Available Modules

A client requests the tool catalogue:

{
  "command": "search_modules",
  "arguments": {}
}

Typical response (truncated):

{
  "content": [
    {
      "type": "text",
      "text": "[\n  {\n    \"name\": \"get_selected_elements\",\n    \"description\": \"Get elements currently selected in Revit…\",\n    \"schema\": {\"type\":\"object\",\"properties\":{\"limit\":{\"type\":\"number\"}},\"required\":[]}\n  },\n  {\n    \"name\": \"create_line_based_element\",\n    \"description\": \"Create line based element (wall, beam, pipe)\",\n    \"schema\": {…}\n  }\n]"
    }
  ]
}

Invoking a Discovered Module

To call get_selected_elements with a limit of 10:

{
  "command": "use_module",
  "arguments": {
    "module": "get_selected_elements",
    "args": { "limit": 10 }
  }
}

Result:

{
  "content": [
    {
      "type": "text",
      "text": "{\n  \"elements\": [\n    {\"id\": 123, \"type\": \"Wall\", \"name\": \"Wall A\"}\n  ]\n}"
    }
  ]
}

Internally, use_module fetched the registered handler for get_selected_elements (registered by registerGetSelectedElementsTool in src/tools/get_selected_elements.ts) and executed it with the supplied arguments.

Summary

  • The registerTools function in src/tools/register.ts dynamically discovers tool modules by scanning the filesystem and filtering for .ts and .js files
  • Each tool exports a registerXTool function that calls server.tool() to register with the MCP server, defining the tool's schema and handler
  • search_modules provides runtime introspection of all registered tools, returning names, descriptions, and Zod schemas
  • use_module enables dynamic invocation of any registered tool by name, acting as a universal executor that forwards arguments to the appropriate handler
  • This architecture allows AI assistants to discover and invoke Revit operations without prior knowledge of available commands

Frequently Asked Questions

What is the purpose of the module search and usage system in revit-mcp?

The system enables AI clients to dynamically discover available Revit operations through search_modules and execute them via use_module. This eliminates the need for clients to maintain hardcoded lists of available tools, allowing the server to add new functionality simply by adding files to the src/tools directory.

How does registerTools discover new tool modules?

The registerTools function scans the src/tools directory at runtime, filtering for files ending in .ts or .js while excluding index and register files. It dynamically imports each module using import(), then searches for exported functions starting with "register" and invokes them to register the tool with the MCP server.

Can I add custom tools to revit-mcp?

Yes. Create a new file in src/tools/ that exports a registerCustomTool(server) function calling server.tool() with your tool's name, Zod schema, and handler. The dynamic registration system in src/tools/register.ts will automatically discover and register your tool on the next server restart, making it available through both search_modules and use_module.

What happens if use_module is called with an invalid module name?

If use_module receives a module name that doesn't exist in the registry, the handler returns an error indicating that the requested tool was not found. This prevents execution of undefined operations and provides clear feedback to the client about available tool names.

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 →