├── .gitignore ├── tsconfig.json ├── package.json ├── LICENSE ├── src ├── types.ts ├── revitService.ts ├── revitSocketClient.ts └── index.ts ├── README.zh-CN.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | *.log 4 | .env* -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "Node16", 5 | "moduleResolution": "Node16", 6 | "outDir": "./build", 7 | "rootDir": "./src", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true 12 | }, 13 | "include": ["src/**/*"], 14 | "exclude": ["node_modules"] 15 | } 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "revit-mcp-server", 3 | "version": "0.1.0", 4 | "description": "A Model Context Protocol server", 5 | "private": true, 6 | "type": "module", 7 | "bin": { 8 | "revit-mcp-server": "./build/index.js" 9 | }, 10 | "files": [ 11 | "build" 12 | ], 13 | "scripts": { 14 | "start": "node build/index.js", 15 | "dev": "ts-node src/index.ts", 16 | "build": "tsc", 17 | "watch": "tsc -w" 18 | }, 19 | "dependencies": { 20 | "@modelcontextprotocol/sdk": "0.6.0", 21 | "axios": "^1.8.4", 22 | "dotenv": "^16.4.7", 23 | "express": "^5.1.0", 24 | "ws": "^8.18.1" 25 | }, 26 | "devDependencies": { 27 | "@types/node": "^20.17.28", 28 | "@types/ws": "^8.18.0", 29 | "typescript": "^5.3.3" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 PiggyYan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | export interface RevitModelInfo { 2 | name: string; 3 | path: string; 4 | version: string; 5 | elements_count: number; 6 | last_modified: string; 7 | } 8 | 9 | export interface RevitElement { 10 | id: string; 11 | category: string; 12 | family: string; 13 | type: string; 14 | name: string; 15 | level: string; 16 | parameters: Record; 17 | } 18 | 19 | export interface GetElementsArgs { 20 | category?: string; 21 | family?: string; 22 | type?: string; 23 | level?: string; 24 | limit?: number; 25 | } 26 | 27 | export interface GetElementInfoArgs { 28 | elementId: string; 29 | getItemPropertyInfo?: boolean; 30 | getItemParameterInfo?: boolean; 31 | } 32 | 33 | // 类型守卫,用于验证查询参数 34 | export function isValidElementsArgs(args: any): args is GetElementsArgs { 35 | return ( 36 | typeof args === "object" && 37 | args !== null && 38 | (args.category === undefined || typeof args.category === "string") && 39 | (args.family === undefined || typeof args.family === "string") && 40 | (args.type === undefined || typeof args.type === "string") && 41 | (args.level === undefined || typeof args.level === "string") && 42 | (args.limit === undefined || typeof args.limit === "number") 43 | ); 44 | } -------------------------------------------------------------------------------- /README.zh-CN.md: -------------------------------------------------------------------------------- 1 | # 🏗️ Revit MCP 服务器 2 | 3 | Revit 集成的模型上下文协议服务器,实现 Claude AI 与 Autodesk Revit 之间的无缝通信。 4 | 5 | [English](./README.md) | [中文](./README.zh-CN.md) 6 | 7 | ## 🎯 概述 8 | 9 | 这是一个基于 TypeScript 的 MCP 服务器,通过 WebSocket 连接在 Claude AI 和 Revit 之间建立桥梁,实现与 Revit 模型的直接交互。它实现了模型上下文协议,提供以下功能: 10 | 11 | - ⚡ 实时访问 Revit 模型信息 12 | - 🔍 元素查询和过滤 13 | - 👀 视图和楼层管理 14 | - 🛡️ 具备模拟数据回退机制的错误处理 15 | 16 | ## ✨ 特性 17 | 18 | ### 🔌 Revit 集成 19 | - 📡 基于 WebSocket 的插件通信 20 | - 🔄 实时模型数据访问 21 | - 🔁 连接失败时优雅回退到模拟数据 22 | - ⚙️ 通过环境变量配置连接设置 23 | 24 | ### 🚀 核心功能 25 | - **📊 模型信息** 26 | - 访问基本模型元数据(名称、路径、版本) 27 | - 获取元素数量和最后修改日期 28 | - 实时模型状态同步 29 | 30 | - **🏗️ 元素管理** 31 | - 灵活的元素查询过滤 32 | - 访问元素属性和几何信息 33 | - 批量元素操作 34 | 35 | - **🎪 视图与楼层控制** 36 | - 列出所有可用视图 37 | - 访问楼层信息 38 | - 导航模型层次结构 39 | 40 | ## 💻 开发 41 | 42 | ### 📋 前置要求 43 | - Node.js (v14 或更高版本) 44 | - npm 45 | - Autodesk Revit (2023 或更高版本) 46 | - Revit WebSocket 插件(配套插件) 47 | 48 | ### 🔧 安装 49 | 50 | 安装依赖: 51 | ```bash 52 | npm install 53 | 54 | 构建服务器: 55 | 56 | ```bash 57 | npm run build 58 | ``` 59 | 60 | 用于开发的自动重构建: 61 | 62 | ```bash 63 | npm run watch 64 | ``` 65 | 66 | ### ⚙️ 配置 67 | 服务器可以通过环境变量进行配置: 68 | 69 | ```plaintext 70 | REVIT_HOST=127.0.0.1 # Revit 插件 WebSocket 主机 71 | REVIT_PORT=8080 # Revit 插件 WebSocket 端口 72 | ``` 73 | 74 | ## 🔗 Claude Desktop 集成 75 | 将服务器配置添加到 Claude Desktop: 76 | 77 | Windows: 78 | 79 | ```bash 80 | %APPDATA%/Claude/claude_desktop_config.json 81 | ``` 82 | 83 | 配置格式: 84 | 85 | ```json 86 | { 87 | "mcpServers": { 88 | "revit-mcp-server": { 89 | "command": "D:/path/to/revit-mcp-server/build/index.js" 90 | } 91 | } 92 | } 93 | ``` 94 | 95 | ### 🐛 调试 96 | MCP 通信调试: 97 | 98 | 1. 使用内置的 MCP 检查器: 99 | ```bash 100 | npm run inspector 101 | ``` 102 | 103 | 2. 监控与 Revit 插件的 WebSocket 通信 104 | 3. 检查服务器日志以了解连接和操作状态 105 | ## ⚠️ 错误处理 106 | 服务器实现了健壮的错误处理机制: 107 | 108 | - Revit 连接失败时自动回退到模拟数据 109 | - 详细的错误日志记录 110 | - 优雅的连接恢复机制 111 | ## 📄 许可证 112 | MIT 许可证 113 | 114 | ## 🤝 贡献 115 | 欢迎贡献!请随时提交拉取请求。 116 | 117 | ## 📬 联系方式 118 | 119 | 如果您有任何问题或建议,欢迎通过以下方式联系我们: 120 | 121 | - 📧 Email: 353554036@qq.com 122 | - 💬 微信号: modian4500 123 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🏗️ Revit MCP Server 2 | 3 | A Model Context Protocol server for Revit integration, enabling seamless communication between Claude AI and Autodesk Revit. 4 | 5 | [English](./README.md) | [中文](./README.zh-CN.md) 6 | 7 | ## 🎯 Overview 8 | 9 | This TypeScript-based MCP server provides a bridge between Claude AI and Revit, allowing direct interaction with Revit models through a WebSocket connection. It implements the Model Context Protocol to enable: 10 | 11 | - ⚡ Real-time access to Revit model information 12 | - 🔍 Element querying and filtering 13 | - 👀 View and level management 14 | - 🛡️ Robust error handling with fallback mock data 15 | 16 | ## ✨ Features 17 | 18 | ### 🔌 Revit Integration 19 | - 📡 WebSocket-based communication with Revit plugin 20 | - 🔄 Real-time model data access 21 | - 🔁 Graceful fallback to mock data when connection fails 22 | - ⚙️ Configurable connection settings via environment variables 23 | 24 | ### 🚀 Core Functionalities 25 | - **📊 Model Information** 26 | - Access basic model metadata (name, path, version) 27 | - Get element counts and last modification date 28 | - Real-time model state synchronization 29 | 30 | - **🏗️ Element Management** 31 | - Query elements with flexible filtering 32 | - Access element properties and geometry 33 | - Batch element operations 34 | 35 | - **🎪 View & Level Control** 36 | - List all available views 37 | - Access level information 38 | - Navigate through model hierarchy 39 | 40 | ## 💻 Development 41 | 42 | ### 📋 Prerequisites 43 | - Node.js (v14 or higher) 44 | - npm 45 | - Autodesk Revit (2023 or later) 46 | - Revit WebSocket Plugin (companion plugin) 47 | 48 | ### 🔧 Installation 49 | 50 | Install dependencies: 51 | ```bash 52 | npm install 53 | ``` 54 | Build the server: 55 | 56 | ```bash 57 | npm run build 58 | ``` 59 | 60 | For development with auto-rebuild: 61 | 62 | ```bash 63 | npm run watch 64 | ``` 65 | 66 | ### ⚙️ Configuration 67 | The server can be configured using environment variables: 68 | 69 | ```plaintext 70 | REVIT_HOST=127.0.0.1 # Revit plugin WebSocket host 71 | REVIT_PORT=8080 # Revit plugin WebSocket port 72 | ``` 73 | 74 | ## 🔗 Integration with Claude Desktop 75 | Add the server configuration to Claude Desktop: 76 | 77 | Windows: 78 | 79 | ```bash 80 | %APPDATA%/Claude/claude_desktop_config.json 81 | ``` 82 | 83 | Configuration format: 84 | 85 | ```json 86 | { 87 | "mcpServers": { 88 | "revit-mcp-server": { 89 | "command": "D:/path/to/revit-mcp-server/build/index.js" 90 | } 91 | } 92 | } 93 | ``` 94 | 95 | ### 🐛 Debugging 96 | 97 | 98 | For debugging the MCP communication: 99 | 100 | 1. Use the built-in MCP Inspector: 101 | ```bash 102 | npm run inspector 103 | ``` 104 | 105 | 2. Monitor WebSocket communication with Revit plugin 106 | 3. Check server logs for connection and operation status 107 | ## ⚠️ Error Handling 108 | The server implements robust error handling: 109 | 110 | - Automatic fallback to mock data when Revit connection fails 111 | - Detailed error logging 112 | - Graceful connection recovery 113 | ## 📄 License 114 | MIT License 115 | 116 | ## 🤝 Contributing 117 | Contributions are welcome! Please feel free to submit pull requests. 118 | 119 | ## 📬 Contact 120 | 121 | If you have any questions or suggestions, feel free to reach out: 122 | 123 | - 📧 Email: 353554036@qq.com 124 | - 💬 WeChat Account: modian4500 125 | 126 | -------------------------------------------------------------------------------- /src/revitService.ts: -------------------------------------------------------------------------------- 1 | import { RevitModelInfo, RevitElement, GetElementsArgs,GetElementInfoArgs } from './types.js'; 2 | import { RevitSocketClient } from './revitSocketClient.js'; 3 | import dotenv from 'dotenv'; 4 | 5 | // 加载环境变量 6 | dotenv.config(); 7 | 8 | // 从环境变量获取 Socket 连接信息,默认为 localhost:8080 9 | const REVIT_HOST = process.env.REVIT_HOST || '127.0.0.1'; 10 | const REVIT_PORT = parseInt(process.env.REVIT_PORT || '8080', 10); 11 | 12 | // 模拟 Revit 模型数据 (仅在无法连接到 Revit 插件时使用) 13 | const mockModelInfo: RevitModelInfo = { 14 | name: "Office Building.rvt", 15 | path: "C:\\Projects\\Office Building.rvt", 16 | version: "2023", 17 | elements_count: 5243, 18 | last_modified: new Date().toISOString() 19 | }; 20 | 21 | 22 | export class RevitService { 23 | private client: RevitSocketClient; 24 | private useMockData: boolean = false; 25 | 26 | constructor() { 27 | this.client = new RevitSocketClient(REVIT_HOST, REVIT_PORT); 28 | 29 | // 尝试连接到 Revit 插件 30 | this.client.connect().catch(error => { 31 | console.error('[RevitService] 无法连接到 Revit 插件,将使用模拟数据:', error); 32 | this.useMockData = true; 33 | }); 34 | } 35 | 36 | /** 37 | * 获取当前 Revit 模型信息 38 | */ 39 | async getModelInfo(): Promise { 40 | 41 | 42 | try { 43 | return await this.client.getModelInfo(); 44 | } catch (error) { 45 | console.error('[RevitService] 获取模型信息失败,使用模拟数据:', error); 46 | this.useMockData = true; 47 | return mockModelInfo; 48 | } 49 | } 50 | 51 | /** 52 | * 根据条件查询 Revit 元素 53 | */ 54 | async getElements(args: GetElementsArgs): Promise { 55 | 56 | 57 | try { 58 | return await this.client.getElements(args); 59 | } catch (error) { 60 | console.error('[RevitService] 获取元素失败,使用模拟数据:', error); 61 | throw error; // 不使用模拟数据,直接抛出错误 62 | } 63 | } 64 | 65 | async getElementInfo(args: GetElementInfoArgs): Promise { 66 | try { 67 | return await this.client.getElementInfo(args); 68 | } catch (error) { 69 | console.error('[RevitService] 获取元素信息失败:', error); 70 | throw error; 71 | } 72 | } 73 | 74 | 75 | 76 | /** 77 | * 关闭连接 78 | */ 79 | async close(): Promise { 80 | if (!this.useMockData) { 81 | await this.client.disconnect(); 82 | } 83 | } 84 | 85 | /** 86 | * 获取所有楼层 87 | */ 88 | async getLevels(): Promise { 89 | try { 90 | return await this.client.getLevels(); 91 | } catch (error) { 92 | console.error('[RevitService] 获取楼层失败,使用模拟数据:', error); 93 | throw error; // 不使用模拟数据,直接抛出错误 94 | } 95 | } 96 | 97 | /** 98 | * 获取所有视图 99 | */ 100 | async getViews(): Promise { 101 | try { 102 | return await this.client.getViews(); 103 | } catch (error) { 104 | console.error('[RevitService] 获取视图失败:', error); 105 | throw error; // 不使用模拟数据,直接抛出错误 106 | } 107 | } 108 | 109 | /** 110 | * 获取所有类别 111 | */ 112 | async getCategories(): Promise { 113 | try { 114 | return await this.client.getCategories(); 115 | } catch (error) { 116 | console.error('[RevitService] 获取类别失败:', error); 117 | throw error; 118 | } 119 | } 120 | 121 | /** 122 | * 获取所有族 123 | */ 124 | async getFamilies(): Promise { 125 | try { 126 | return await this.client.getFamilies(); 127 | } catch (error) { 128 | console.error('[RevitService] 获取族失败:', error); 129 | throw error; 130 | } 131 | } 132 | } -------------------------------------------------------------------------------- /src/revitSocketClient.ts: -------------------------------------------------------------------------------- 1 | import * as net from 'net'; 2 | import { GetElementsArgs, RevitModelInfo, RevitElement ,GetElementInfoArgs} from './types.js'; 3 | 4 | export class RevitSocketClient { 5 | private socket: net.Socket | null = null; 6 | private isConnected = false; 7 | private pendingRequests = new Map void, 9 | reject: (reason: any) => void 10 | }>(); 11 | private reconnectInterval: NodeJS.Timeout | null = null; 12 | private reconnectAttempts = 0; 13 | private readonly MAX_RECONNECT_ATTEMPTS = 5; 14 | private buffer = ''; 15 | 16 | constructor(private host: string = '127.0.0.1', private port: number = 8080) {} 17 | 18 | /** 19 | * 连接到 Revit Socket 服务器 20 | */ 21 | public async connect(): Promise { 22 | if (this.isConnected) { 23 | return; 24 | } 25 | 26 | return new Promise((resolve, reject) => { 27 | try { 28 | console.error(`[Revit Socket] 正在连接到 ${this.host}:${this.port}...`); 29 | this.socket = new net.Socket(); 30 | 31 | this.socket.on('connect', () => { 32 | console.error('[Revit Socket] 连接成功'); 33 | this.isConnected = true; 34 | this.reconnectAttempts = 0; 35 | if (this.reconnectInterval) { 36 | clearInterval(this.reconnectInterval); 37 | this.reconnectInterval = null; 38 | } 39 | resolve(); 40 | }); 41 | 42 | this.socket.on('data', (data: Buffer) => { 43 | try { 44 | // 将接收到的数据添加到缓冲区 45 | this.buffer += data.toString(); 46 | 47 | // 尝试解析完整的 JSON 响应 48 | try { 49 | const response = JSON.parse(this.buffer); 50 | this.buffer = ''; // 清空缓冲区 51 | 52 | // 处理响应 53 | if (response.error) { 54 | // 如果响应包含错误,找到对应的请求并拒绝 55 | const pendingRequestIds = Array.from(this.pendingRequests.keys()); 56 | if (pendingRequestIds.length > 0) { 57 | const id = pendingRequestIds[0]; // 取第一个待处理请求 58 | const { reject } = this.pendingRequests.get(id)!; 59 | this.pendingRequests.delete(id); 60 | reject(new Error(response.error)); 61 | } 62 | } else { 63 | // 如果响应成功,找到对应的请求并解析 64 | const pendingRequestIds = Array.from(this.pendingRequests.keys()); 65 | if (pendingRequestIds.length > 0) { 66 | const id = pendingRequestIds[0]; // 取第一个待处理请求 67 | const { resolve } = this.pendingRequests.get(id)!; 68 | this.pendingRequests.delete(id); 69 | resolve(response); 70 | } 71 | } 72 | } catch (e) { 73 | // 如果解析失败,说明数据不完整,继续等待更多数据 74 | } 75 | } catch (error) { 76 | console.error('[Revit Socket] 消息解析错误:', error); 77 | } 78 | }); 79 | 80 | this.socket.on('error', (error: Error) => { 81 | console.error('[Revit Socket] 连接错误:', error); 82 | if (!this.isConnected) { 83 | reject(error); 84 | } 85 | }); 86 | 87 | this.socket.on('close', () => { 88 | console.error('[Revit Socket] 连接关闭'); 89 | this.isConnected = false; 90 | this.attemptReconnect(); 91 | }); 92 | 93 | // 连接到服务器 94 | this.socket.connect(this.port, this.host); 95 | } catch (error) { 96 | console.error('[Revit Socket] 初始化错误:', error); 97 | reject(error); 98 | } 99 | }); 100 | } 101 | 102 | /** 103 | * 尝试重新连接 104 | */ 105 | private attemptReconnect(): void { 106 | if (this.reconnectInterval || this.reconnectAttempts >= this.MAX_RECONNECT_ATTEMPTS) { 107 | return; 108 | } 109 | 110 | this.reconnectInterval = setInterval(() => { 111 | if (this.isConnected) { 112 | clearInterval(this.reconnectInterval!); 113 | this.reconnectInterval = null; 114 | return; 115 | } 116 | 117 | this.reconnectAttempts++; 118 | console.error(`[Revit Socket] 尝试重新连接 (${this.reconnectAttempts}/${this.MAX_RECONNECT_ATTEMPTS})...`); 119 | 120 | this.connect().catch(() => { 121 | if (this.reconnectAttempts >= this.MAX_RECONNECT_ATTEMPTS) { 122 | console.error('[Revit Socket] 达到最大重连次数,停止重连'); 123 | clearInterval(this.reconnectInterval!); 124 | this.reconnectInterval = null; 125 | } 126 | }); 127 | }, 5000); // 每5秒尝试重连一次 128 | } 129 | 130 | /** 131 | * 关闭连接 132 | */ 133 | public async disconnect(): Promise { 134 | if (this.reconnectInterval) { 135 | clearInterval(this.reconnectInterval); 136 | this.reconnectInterval = null; 137 | } 138 | 139 | if (this.socket && this.isConnected) { 140 | this.socket.destroy(); 141 | this.isConnected = false; 142 | } 143 | } 144 | 145 | /** 146 | * 发送请求到 Revit 插件并等待响应 147 | */ 148 | private async sendRequest(command: string, args: any = {}): Promise { 149 | if (!this.isConnected || !this.socket) { 150 | await this.connect(); 151 | } 152 | 153 | return new Promise((resolve, reject) => { 154 | try { 155 | const id = Date.now().toString(); 156 | const request = { 157 | command, 158 | args 159 | }; 160 | 161 | this.pendingRequests.set(id, { resolve, reject }); 162 | 163 | // 设置超时 164 | setTimeout(() => { 165 | if (this.pendingRequests.has(id)) { 166 | this.pendingRequests.delete(id); 167 | reject(new Error(`请求超时: ${command}`)); 168 | } 169 | }, 30000); // 30秒超时 170 | 171 | this.socket!.write(JSON.stringify(request)); 172 | } catch (error) { 173 | reject(error); 174 | } 175 | }); 176 | } 177 | 178 | /** 179 | * 获取 Revit 模型信息 180 | */ 181 | public async getModelInfo(): Promise { 182 | const response = await this.sendRequest('getModelInfo', {}); 183 | return { 184 | name: response.name, 185 | path: response.path, 186 | version: response.version, 187 | elements_count: response.elements_count, 188 | last_modified: response.last_modified 189 | }; 190 | } 191 | 192 | /** 193 | * 获取所有类别 194 | */ 195 | public async getCategories(): Promise { 196 | const response = await this.sendRequest('get_categories', {}); 197 | return response; 198 | } 199 | 200 | /** 201 | * 根据条件查询 Revit 元素(更新后的方法) 202 | */ 203 | public async getElements(args: GetElementsArgs): Promise { 204 | const response = await this.sendRequest('get_elements', args); 205 | return response; 206 | } 207 | 208 | /** 209 | * 获取所有楼层 210 | */ 211 | public async getLevels(sortByElevation: boolean = true): Promise { 212 | const response = await this.sendRequest('get_levels', { }); 213 | return response; 214 | } 215 | 216 | /** 217 | * 获取所有视图 218 | */ 219 | public async getViews(): Promise { 220 | const response = await this.sendRequest('get_views', {}); 221 | return response; 222 | } 223 | 224 | /** 225 | * 获取所有视图 226 | */ 227 | public async getFamilies(): Promise { 228 | const response = await this.sendRequest('get_families', {}); 229 | return response; 230 | } 231 | 232 | public async getElementInfo(args: GetElementInfoArgs): Promise { 233 | return await this.sendRequest('get_element_info', args); 234 | } 235 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import { Server } from "@modelcontextprotocol/sdk/server/index.js"; 3 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; 4 | import { 5 | ListResourcesRequestSchema, 6 | ReadResourceRequestSchema, 7 | ListToolsRequestSchema, 8 | CallToolRequestSchema, 9 | ErrorCode, 10 | McpError 11 | } from "@modelcontextprotocol/sdk/types.js"; 12 | import dotenv from "dotenv"; 13 | import { RevitService } from "./revitService.js"; 14 | import { isValidElementsArgs } from "./types.js"; 15 | 16 | dotenv.config(); 17 | 18 | class RevitMcpServer { 19 | private server: Server; 20 | private revitService: RevitService; 21 | 22 | constructor() { 23 | this.server = new Server({ 24 | name: "revit-mcp-server", 25 | version: "0.1.0" 26 | }, { 27 | capabilities: { 28 | resources: {}, 29 | tools: {} 30 | } 31 | }); 32 | 33 | 34 | this.revitService = new RevitService(); 35 | 36 | this.setupHandlers(); 37 | this.setupErrorHandling(); 38 | } 39 | 40 | private setupErrorHandling(): void { 41 | this.server.onerror = (error) => { 42 | console.error("[MCP Error]", error); 43 | }; 44 | 45 | process.on('SIGINT', async () => { 46 | await this.cleanup(); 47 | process.exit(0); 48 | }); 49 | 50 | process.on('SIGTERM', async () => { 51 | await this.cleanup(); 52 | process.exit(0); 53 | }); 54 | } 55 | 56 | private async cleanup(): Promise { 57 | console.error("[RevitMcpServer] 正在关闭服务..."); 58 | //await this.revitService.close(); 59 | await this.server.close(); 60 | console.error("[RevitMcpServer] 服务已关闭"); 61 | } 62 | 63 | private setupHandlers(): void { 64 | this.setupResourceHandlers(); 65 | this.setupToolHandlers(); 66 | } 67 | 68 | private setupResourceHandlers(): void { 69 | // 保持原有的资源处理程序不变 70 | this.server.setRequestHandler( 71 | ListResourcesRequestSchema, 72 | async () => ({ 73 | resources: [{ 74 | uri: "revit://current/model-info", 75 | name: "当前 Revit 模型信息", 76 | mimeType: "application/json", 77 | description: "提供当前打开的 Revit 模型的基本信息,包括名称、路径、版本和元素数量" 78 | }] 79 | }) 80 | ); 81 | 82 | this.server.setRequestHandler( 83 | ReadResourceRequestSchema, 84 | async (request) => { 85 | if (request.params.uri !== "revit://current/model-info") { 86 | throw new McpError( 87 | ErrorCode.InvalidRequest, 88 | `未知资源: ${request.params.uri}` 89 | ); 90 | } 91 | 92 | try { 93 | const modelInfo = await this.revitService.getModelInfo(); 94 | //const modelInfo = ''; 95 | return { 96 | contents: [{ 97 | uri: request.params.uri, 98 | mimeType: "application/json", 99 | text: JSON.stringify(modelInfo, null, 2) 100 | }] 101 | }; 102 | } catch (error) { 103 | throw new McpError( 104 | ErrorCode.InternalError, 105 | `Revit API 错误: ${error instanceof Error ? error.message : String(error)}` 106 | ); 107 | } 108 | } 109 | ); 110 | } 111 | 112 | private setupToolHandlers(): void { 113 | this.server.setRequestHandler( 114 | ListToolsRequestSchema, 115 | async () => ({ 116 | tools: [{ 117 | name: "get_categories", 118 | description: "获取 Revit 模型中的所有类别", 119 | inputSchema: { 120 | type: "object", 121 | properties: {} 122 | } 123 | }, { 124 | name: "get_families", 125 | description: "获取 Revit 模型中的所有族", 126 | inputSchema: { 127 | type: "object", 128 | properties: { 129 | categoryId: { 130 | type: "string", 131 | description: "族类别 ID(可选)" 132 | }, 133 | name: { 134 | type: "string", 135 | description: "族名称过滤(可选)" 136 | } 137 | } 138 | } 139 | }, { 140 | name: "get_elements", 141 | description: "获取 Revit 模型中的元素", 142 | inputSchema: { 143 | type: "object", 144 | properties: { 145 | categoryIds: { 146 | type: "array", 147 | description: "需要获取的元素所属类别的Id集合" 148 | }, 149 | viewIds: { 150 | type: "array", 151 | description: "需要获取的元素所处的视图的Id集合" 152 | }, 153 | levelIds: { 154 | type: "array", 155 | description: "需要获取的元素所处的标高的Id集合" 156 | } 157 | } 158 | } 159 | }, 160 | { 161 | name: "get_levels", 162 | description: "获取 Revit 模型中的所有楼层", 163 | inputSchema: { 164 | type: "object", 165 | properties: {} 166 | } 167 | }, { 168 | name: "get_views", 169 | description: "获取 Revit 模型中的所有视图", 170 | inputSchema: { 171 | type: "object", 172 | properties: {} 173 | } 174 | }, { 175 | name: "get_element_info", 176 | description: "获取 Revit 元素的详细信息", 177 | inputSchema: { 178 | type: "object", 179 | properties: { 180 | elementId: { 181 | type: "string", 182 | description: "元素的 ID" 183 | }, 184 | getItemPropertyInfo: { 185 | type: "boolean", 186 | description: "是否获取元素属性信息", 187 | default: true 188 | }, 189 | getItemParameterInfo: { 190 | type: "boolean", 191 | description: "是否获取元素参数信息", 192 | default: false 193 | } 194 | }, 195 | required: ["elementId"] 196 | } 197 | }] 198 | }) 199 | ); 200 | 201 | this.server.setRequestHandler( 202 | CallToolRequestSchema, 203 | async (request) => { 204 | // 获取工具名称 205 | const toolName = request.params.name; 206 | 207 | // 将工具名称转换为 camelCase 方法名 208 | const methodName = toolName.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase()); 209 | 210 | // 检查 RevitService 是否有对应的方法 211 | if (typeof (this.revitService as any)[methodName] !== 'function') { 212 | throw new McpError( 213 | ErrorCode.MethodNotFound, 214 | `未知工具: ${toolName}` 215 | ); 216 | } 217 | 218 | try { 219 | // 动态调用对应的方法 220 | const result = await (this.revitService as any)[methodName](request.params.arguments || {}); 221 | //const result = ''; 222 | return { 223 | content: [{ 224 | type: "text", 225 | text: JSON.stringify(result, null, 2) 226 | }] 227 | }; 228 | } catch (error) { 229 | return { 230 | content: [{ 231 | type: "text", 232 | text: `Revit API 错误: ${error instanceof Error ? error.message : String(error)}` 233 | }], 234 | isError: true, 235 | } 236 | } 237 | } 238 | ); 239 | } 240 | 241 | async run(): Promise { 242 | const transport = new StdioServerTransport(); 243 | await this.server.connect(transport); 244 | 245 | // 以避免干扰在 stdout 上发生的 MCP 通信 246 | console.error("Revit MCP 服务器正在通过 stdio 运行"); 247 | } 248 | } 249 | 250 | const server = new RevitMcpServer(); 251 | server.run().catch(console.error); --------------------------------------------------------------------------------