# Bài 5: Dựng Core MCP cho Revit (C#)

> **Khóa học:** Revit API x MCP x AI — Từ Zero đến Plugin hoàn chỉnh
>
> **Mục tiêu bài học:** Xây dựng phần core của MCP Server chạy bên trong Revit plugin bằng C#, hiểu cách kết nối, xử lý thread-safe và định tuyến tool call từ AI Agent đến Revit API.
>
> **Thời lượng dự kiến:** 3 – 4 giờ
>
> **Mã nguồn tham khảo:** [deepbim-revit-mcp-plugin](https://github.com/nguyenngocdue/deepbim-revit-mcp-plugin)
>
> **Nguồn code phần Core (Bài 5):** [plugin/Core](https://github.com/nguyenngocdue/deepbim-revit-mcp-plugin/tree/master/plugin/Core)
>
> **Nguồn code phần CommandSet (Bài 5):** [commandset](https://github.com/nguyenngocdue/deepbim-revit-mcp-plugin/tree/master/commandset)
>
> **Tham chiếu triển khai Tool/MCP Server:** [revit-mcp-server](https://github.com/nguyenngocdue/revit-mcp-server)

***

## Mục lục

1. [Giới thiệu bài học](#1-giới-thiệu-bài-học)
2. [Các kiểu kết nối MCP — stdio, SSE, HTTP Streamable](#2-các-kiểu-kết-nối-mcp)
3. [Kiến trúc Revit Plugin — IExternalApplication](#3-kiến-trúc-revit-plugin--iexternalapplication)
4. [HTTP Listener trong Revit](#4-http-listener-trong-revit)
5. [Xử lý External Event — Thread Safety](#5-xử-lý-external-event--thread-safety)
6. [Router Pattern — Định tuyến Tool Call](#6-router-pattern--định-tuyến-tool-call)
7. [Triển khai RevitMcpListener — Full C# Code](#7-triển-khai-revitmcplistener--full-c-code)
8. [Triển khai CreateWallTool](#8-triển-khai-createwalltool)
9. [Test với Postman / curl](#9-test-với-postman--curl)
10. [Câu hỏi tự suy nghĩ](#10-câu-hỏi-tự-suy-nghĩ)
11. [Tổng kết](#11-tổng-kết)
12. [Tài liệu tham khảo](#12-tài-liệu-tham-khảo)

***

## 1. Giới thiệu bài học

Trong bài trước, chúng ta đã tìm hiểu cách xây dựng MCP Server bằng Node.js để đóng vai trò **cầu nối trung gian** giữa AI Agent và Revit. Bài này sẽ đi vào **thực chiến**: xây dựng phần core của MCP Server **ngay bên trong Revit** bằng C#.

### Tại sao phải nhúng MCP Server vào Revit?

Revit API có một đặc điểm rất quan trọng: **mọi thao tác với mô hình BIM phải chạy trên Main Thread của Revit** (còn gọi là Revit API context). Điều này có nghĩa là bạn không thể đơn giản tạo một web server riêng biệt rồi gọi Revit API từ đó — bạn sẽ gặp lỗi ngay lập tức hoặc tệ hơn là làm crash Revit.

Giải pháp là **nhúng HTTP Listener vào trong Revit plugin**, sau đó sử dụng cơ chế **ExternalEvent** để chuyển các request từ worker thread sang Revit API thread một cách an toàn.

### Những gì bạn sẽ học trong bài này

* Hiểu 3 kiểu kết nối MCP: `stdio`, `SSE`, `HTTP Streamable` và lý do chọn HTTP Streamable cho Revit
* Triển khai `IExternalApplication` để khởi tạo plugin
* Dùng `HttpListener` (.NET built-in) để lắng nghe request từ AI Agent
* Sử dụng `ExternalEvent` + `IExternalEventHandler` để đảm bảo thread-safe
* Xây dựng Router pattern để định tuyến tool call
* Viết tool cụ thể: `CreateWallTool` với Transaction đầy đủ

### Kiến trúc Core + CommandSet thực tế (addin runtime)

Nguồn chính:

* [`plugin/Core`](https://github.com/nguyenngocdue/deepbim-revit-mcp-plugin/tree/master/plugin/Core)
* [`commandset`](https://github.com/nguyenngocdue/deepbim-revit-mcp-plugin/tree/master/commandset)
* [`plugin/revit-mcp-plugin.addin`](https://github.com/nguyenngocdue/deepbim-revit-mcp-plugin/blob/master/plugin/revit-mcp-plugin.addin)

```mermaid
flowchart TB
    subgraph Addin["Revit Addin Registration"]
        AddinFile["plugin/revit-mcp-plugin.addin<br/>FullClassName: revit_mcp_plugin.Core.Application"]
    end

    subgraph Revit["Revit Process - plugin/Core"]
        App["Application.cs<br/>(IExternalApplication)"]
        MCPCmd["MCPServiceConnection.cs<br/>(IExternalCommand)"]
        Settings["Settings.cs"]
        Export["ExportSheetsToExcel.cs"]
        Icon["RibbonIconHelper.cs"]

        Socket["SocketService.cs<br/>TCP JSON-RPC Server<br/>port 8080-8099"]
        ConfigMgr["ConfigurationManager.cs<br/>Load commandRegistry.json<br/>or fallback commandset/command.json"]
        CmdMgr["CommandManager.cs<br/>Load commands from config/assembly"]
        Registry["RevitCommandRegistry.cs<br/>Dictionary commandName -> IRevitCommand"]
        Exec["CommandExecutor.cs<br/>Execute + JSON-RPC response"]
        EventMgr["ExternalEventManager.cs<br/>Manage ExternalEvent by key"]
    end

    subgraph CommandSet["CommandSet (commandset)"]
        CmdJson["command.json<br/>method -> assemblyPath"]
        CmdDll["RevitMCPCommandSet.dll<br/>Commands/* + Services/* + Models/*"]
    end

    subgraph External["External dependencies"]
        AI["MCP Host/Client<br/>(Claude, VS Code, Cursor)"]
        RevitAPI["Autodesk Revit API"]
    end

    AddinFile --> App
    App --> MCPCmd
    App --> Settings
    App --> Export
    App --> Icon
    App --> Socket

    Socket --> ConfigMgr
    ConfigMgr --> CmdJson
    Socket --> CmdMgr
    CmdJson --> CmdMgr
    CmdDll --> CmdMgr
    CmdMgr --> Registry
    Socket --> Registry
    Socket -.-> Exec
    Socket --> EventMgr
    Registry --> RevitAPI
    EventMgr --> RevitAPI

    AI -->|"TCP JSON-RPC request"| Socket
    Socket -->|"JSON-RPC response"| AI
```

```mermaid
sequenceDiagram
    participant Addin as revit-mcp-plugin.addin
    participant App as Core.Application
    participant AI as MCP Client
    participant Socket as SocketService
    participant Config as ConfigurationManager
    participant CmdMgr as CommandManager
    participant Registry as RevitCommandRegistry
    participant CmdDll as RevitMCPCommandSet.dll
    participant Cmd as Command (IRevitCommand)
    participant EventMgr as ExternalEventManager
    participant Revit as Revit API

    Addin->>App: Revit load AddIn
    App->>Socket: Initialize()/Start()
    Socket->>Config: LoadConfiguration()
    Config-->>Socket: command list (commandRegistry/command.json)
    Socket->>CmdMgr: LoadCommands()
    CmdMgr->>CmdDll: Load assembly + create command instances
    CmdMgr->>Registry: RegisterCommand(commandName)

    AI->>Socket: JSON-RPC request (method, params, id)
    Socket->>Registry: TryGetCommand(method)
    Registry-->>Socket: command instance
    Socket->>Cmd: Execute(params, id)
    Cmd->>EventMgr: GetOrCreateEvent(...) / Raise(...)
    EventMgr->>Revit: Execute on main thread
    Revit-->>Cmd: result/error
    Cmd-->>Socket: object result
    Socket-->>AI: JSON-RPC success/error
```

Bản đồ file (core + commandset):

* `plugin/revit-mcp-plugin.addin`: entry add-in, trỏ vào `revit_mcp_plugin.Core.Application`.
* `Application.cs`: điểm vào plugin, dựng ribbon, khởi tạo/dừng service.
* `SocketService.cs`: TCP listener, nhận JSON-RPC, route method đến command đã đăng ký.
* `ConfigurationManager.cs`: đọc `commandRegistry.json`, fallback sang `Commands/RevitMCPCommandSet/command.json`.
* `CommandManager.cs`: nạp command từ assembly theo cấu hình và đăng ký vào registry.
* `RevitCommandRegistry.cs`: lưu/tra cứu command theo `CommandName`.
* `ExternalEventManager.cs`: quản lý `ExternalEvent` để chạy an toàn trên Revit main thread.
* `CommandExecutor.cs`: lớp utility cho execute/format JSON-RPC response (đã khởi tạo trong `SocketService`, có thể dùng khi refactor tách riêng execution pipeline).
* `MCPServiceConnection.cs`: command mở UI điều khiển trạng thái kết nối/service.
* `Settings.cs`, `ExportSheetsToExcel.cs`, `RibbonIconHelper.cs`: lớp UI và tiện ích ribbon.
* `commandset/Commands/*`: tập các command implement `IRevitCommand` (ví dụ `say_hello`, `create_room`, `create_level`...).
* `commandset/Services/*`: các `IExternalEventHandler` thực thi trên Revit main thread.
* `commandset/command.json` (root `command.json` được copy/deploy): registry method -> assembly path để Core nạp command.

### Giao diện vận hành trong Revit

**1) DeepBim-MCP Server (mở cổng socket cho MCP gọi vào)**

![DeepBim-MCP Server Control](/files/ZZAASIwfR682bWOKLxlc)

Cửa sổ này thể hiện trạng thái server khi plugin đã cài vào Revit. Khi bật server, plugin mở một cổng TCP (trong dải `8080-8099`) để MCP client/server bên ngoài gọi vào qua JSON-RPC.

**2) MCP Settings (quản trị các tool được phép sử dụng trong Revit)**

![MCP Settings - Quản trị công cụ](/files/GUSZxDMMyKRZSDo00WqM)

Màn hình này dùng để quản trị danh sách command/tool được phép chạy trong Revit (bật/tắt theo nhu cầu), tương ứng với cơ chế nạp command từ `commandset` + `command.json` trong kiến trúc ở trên.

***

## 2. Các kiểu kết nối MCP

MCP (Model Context Protocol) hỗ trợ nhiều kiểu **transport** (phương thức truyền tin) giữa Client (AI Agent) và Server (plugin của bạn).

### 2.1. stdio (Standard Input/Output)

```
AI Agent <──stdin/stdout──> MCP Server (process riêng)
```

* Giao tiếp qua `stdin` và `stdout` của process
* AI Host (Claude Desktop, Cursor) tự khởi động MCP Server như một process con
* Đơn giản, phù hợp cho CLI tool
* **Không phù hợp cho Revit** vì Revit plugin không phải process độc lập — nó chạy *bên trong* process `Revit.exe`

### 2.2. SSE (Server-Sent Events)

```
AI Agent ──HTTP POST──> MCP Server
AI Agent <──SSE stream── MCP Server
```

* Client gửi request bằng HTTP POST
* Server trả kết quả qua SSE stream (một chiều, server → client)
* Phù hợp cho ứng dụng cần streaming dữ liệu dài
* Có thể dùng cho Revit, nhưng phức tạp hơn cần thiết

### 2.3. HTTP Streamable (lựa chọn của chúng ta)

```
AI Agent ──HTTP POST (JSON-RPC)──> MCP Server (HttpListener trong Revit)
AI Agent <──HTTP Response (JSON)── MCP Server
```

* Giao tiếp bằng HTTP request/response đơn giản
* Sử dụng JSON-RPC 2.0 format
* **Phù hợp nhất cho Revit plugin** vì:
  * Dễ triển khai với `System.Net.HttpListener` (built-in .NET)
  * Không cần process riêng
  * Tương thích với cơ chế `ExternalEvent` của Revit
  * Dễ debug bằng curl hoặc Postman

### So sánh các kiểu kết nối

| Đặc điểm          | stdio       | SSE           | HTTP Streamable       |
| ----------------- | ----------- | ------------- | --------------------- |
| Kiểu giao tiếp    | Process I/O | HTTP + Stream | HTTP Request/Response |
| Độ phức tạp       | Thấp        | Trung bình    | Trung bình            |
| Phù hợp Revit     | Không       | Có thể        | **Rất phù hợp**       |
| Streaming         | Có          | Có            | Có thể mở rộng        |
| Firewall-friendly | Không       | Có            | Có                    |
| Debug dễ không    | Khó         | Trung bình    | **Rất dễ**            |

```mermaid
flowchart LR
    subgraph "Các kiểu kết nối MCP"
        A["AI Agent\n(Claude, GPT)"]

        subgraph stdio["stdio"]
            B1["stdin/stdout\n(process con)"]
        end

        subgraph SSE["SSE"]
            B2["HTTP POST\n+ SSE Stream"]
        end

        subgraph HTTP["HTTP Streamable"]
            B3["HTTP POST / Response\nJSON-RPC 2.0"]
        end

        A -->|"Không phù hợp Revit"| B1
        A -->|"Có thể dùng"| B2
        A -->|"Lựa chọn tốt nhất"| B3
    end
```

***

## 3. Kiến trúc Revit Plugin — IExternalApplication

### 3.1. IExternalApplication là gì?

`IExternalApplication` là interface chính của Revit API cho phép bạn tạo một **Add-in chạy suốt vòng đời của Revit**. Nó có 2 method quan trọng:

* `OnStartup(UIControlledApplication)`: Gọi khi Revit khởi động — nơi bạn khởi tạo mọi thứ
* `OnShutdown(UIControlledApplication)`: Gọi khi Revit đóng — nơi bạn dọn dẹp tài nguyên

### 3.2. Tại sao dùng IExternalApplication mà không dùng IExternalCommand?

|              | IExternalApplication                | IExternalCommand       |
| ------------ | ----------------------------------- | ---------------------- |
| Khi nào chạy | Khi Revit khởi động                 | Khi user bấm nút       |
| Vòng đời     | Suốt phiên làm việc                 | Chỉ khi được gọi       |
| Phù hợp MCP  | **Có** — cần listener chạy liên tục | Không — chỉ chạy 1 lần |

Vì MCP Server cần **lắng nghe request liên tục**, nên `IExternalApplication` là lựa chọn duy nhất hợp lý.

### 3.3. Class diagram tổng thể

```mermaid
classDiagram
    class IExternalApplication {
        <<interface>>
        +OnStartup(UIControlledApplication) Result
        +OnShutdown(UIControlledApplication) Result
    }

    class RevitMcpApp {
        -RevitMcpListener _listener
        +OnStartup(UIControlledApplication) Result
        +OnShutdown(UIControlledApplication) Result
    }

    class RevitMcpListener {
        -HttpListener _httpListener
        -Thread _listenerThread
        -McpRouter _router
        -McpEventHandler _eventHandler
        -ExternalEvent _externalEvent
        -bool _isRunning
        -object _requestLock
        +Start() void
        +Stop() void
        -ListenLoop() void
        -ProcessRequest(HttpListenerContext) void
        -HandleInitialize() JsonObject
        -HandleToolsList() JsonObject
        -HandleToolCall(JsonObject) JsonObject
    }

    class McpRouter {
        -Dictionary~string, IMcpTool~ _tools
        +RegisterTool(IMcpTool) void
        +Route(string, JsonObject, Document) JsonObject
        +GetToolList() JsonArray
    }

    class IMcpTool {
        <<interface>>
        +Name string
        +Description string
        +Execute(JsonObject, Document) JsonObject
    }

    class CreateWallTool {
        +Name string
        +Description string
        +Execute(JsonObject, Document) JsonObject
        -FindLevelByName(Document, string) Level
    }

    class GetElementsTool {
        +Name string
        +Description string
        +Execute(JsonObject, Document) JsonObject
    }

    class McpEventHandler {
        +PendingAction Action~UIApplication~
        +Result object
        +Signal ManualResetEvent
        +Execute(UIApplication) void
        +GetName() string
    }

    IExternalApplication <|.. RevitMcpApp
    RevitMcpApp *-- RevitMcpListener
    RevitMcpListener *-- McpRouter
    RevitMcpListener *-- McpEventHandler
    McpRouter o-- IMcpTool
    IMcpTool <|.. CreateWallTool
    IMcpTool <|.. GetElementsTool
    McpEventHandler ..|> IExternalEventHandler
```

***

## 4. HTTP Listener trong Revit

### 4.1. HttpListener là gì?

`System.Net.HttpListener` là class built-in của .NET cho phép tạo một HTTP server đơn giản mà không cần IIS hay Kestrel. Nó lắng nghe trên một port cụ thể và xử lý các HTTP request.

### 4.2. Tại sao dùng HttpListener?

* **Nhẹ**: Không cần thêm dependency nào
* **Built-in**: Có sẵn trong .NET Framework (Revit dùng .NET Framework / .NET 8 tùy phiên bản)
* **Đơn giản**: Chỉ cần vài dòng code để bắt đầu lắng nghe
* **Đủ mạnh**: Hỗ trợ GET, POST, headers, body — đủ cho JSON-RPC

### 4.3. Lifecycle của HTTP Listener

```mermaid
stateDiagram-v2
    [*] --> Created : new HttpListener()
    Created --> Configured : AddPrefix("http://localhost:8080/")
    Configured --> Listening : Start()
    Listening --> Processing : GetContext() — nhận request
    Processing --> Listening : Gửi response, đợi request tiếp
    Listening --> Stopped : Stop()
    Processing --> Stopped : Stop() được gọi
    Stopped --> [*] : Close()

    note right of Listening
        Chạy trên Worker Thread
        Không block Revit UI
    end note

    note right of Processing
        Request được chuyển sang
        Revit API thread qua ExternalEvent
    end note
```

### 4.4. Lưu ý về quyền Admin trên Windows

> **Quan trọng:** Trên Windows, `HttpListener` cần quyền để lắng nghe trên một URL prefix. Chạy lệnh sau một lần với quyền Administrator để cấp phép cho tất cả user:
>
> ```cmd
> netsh http add urlacl url=http://localhost:8080/ user=Everyone
> ```
>
> Hoặc chạy Revit với quyền Administrator (không khuyến khích dùng lâu dài).

***

## 5. Xử lý External Event — Thread Safety

### 5.1. Vấn đề Thread Safety trong Revit

Đây là phần **quan trọng nhất** của bài học. Revit API có một quy tắc bất di bất dịch:

> **Mọi thao tác với Revit API (đọc/ghi model, tạo element, mở Transaction...) phải chạy trên Revit API thread (Main Thread).**

Nhưng `HttpListener` của chúng ta chạy trên **Worker Thread** (để không block giao diện Revit). Vậy làm sao để từ Worker Thread gọi được Revit API?

### 5.2. Giải pháp: ExternalEvent + IExternalEventHandler

Revit cung cấp cơ chế `ExternalEvent` cho phép bạn **đặt lịch** (schedule) một hành động sẽ được Revit gọi trên Main Thread khi Revit sẵn sàng (thời điểm idle).

**Luồng hoạt động:**

1. Worker Thread nhận HTTP request từ AI Agent
2. Worker Thread gán hành động cần thực thi vào `McpEventHandler.PendingAction`
3. Worker Thread gọi `ExternalEvent.Raise()` để yêu cầu Revit chạy
4. Worker Thread chờ đợi bằng `ManualResetEvent.WaitOne()`
5. Revit Main Thread (khi idle) gọi `McpEventHandler.Execute()`
6. Execute() chạy PendingAction — an toàn trên Main Thread
7. Execute() gọi `Signal.Set()` để báo hiệu hoàn thành
8. Worker Thread thức dậy, đọc kết quả và trả HTTP response về AI Agent

### 5.3. Sequence diagram chi tiết

```mermaid
sequenceDiagram
    participant AI as AI Agent (Claude)
    participant HTTP as HttpListener<br/>(Worker Thread)
    participant Router as McpRouter
    participant Event as ExternalEvent
    participant Handler as McpEventHandler
    participant Revit as Revit API<br/>(Main Thread)

    AI->>HTTP: POST http://localhost:8080/<br/>JSON-RPC: {"method":"tools/call",<br/>"params":{"name":"create_wall",...}}

    HTTP->>HTTP: Parse JSON-RPC body
    HTTP->>HTTP: Xác định method = "tools/call"
    HTTP->>HTTP: Gán PendingAction vào Handler

    HTTP->>Event: Raise() — yêu cầu Revit xử lý
    Note over HTTP: WaitOne(30s) — CHỜ ĐỢI...

    Note over Revit: Revit idle, kiểm tra hàng đợi sự kiện

    Event->>Handler: Execute(UIApplication app)
    Handler->>Router: Route("create_wall", args, doc)
    Router->>Revit: Transaction.Start()<br/>Wall.Create(...)<br/>Transaction.Commit()
    Revit-->>Router: Wall (ElementId = 12345)
    Router-->>Handler: JsonObject {success: true, wall_id: 12345}
    Handler->>Handler: Signal.Set() — BÁO HIỆU XONG

    Note over HTTP: WaitOne() trả về true

    HTTP->>HTTP: Đọc kết quả từ Handler
    HTTP-->>AI: HTTP 200 OK<br/>{"jsonrpc":"2.0","result":{"wall_id":12345}}
```

### 5.4. Threading diagram — Timeline hai thread

```mermaid
gantt
    title Thread Timeline khi xử lý một MCP request
    dateFormat X
    axisFormat %ss

    section Worker Thread
    Nhận HTTP Request           :a1, 0, 1
    Parse JSON-RPC              :a2, 1, 2
    Gán PendingAction           :a3, 2, 3
    Raise ExternalEvent         :a4, 3, 4
    WaitOne() — CHỜ             :crit, a5, 4, 8
    Đọc kết quả                 :a6, 8, 9
    Gửi HTTP Response           :a7, 9, 10

    section Revit Main Thread
    Đang xử lý UI               :b1, 0, 5
    Idle — gọi Execute()        :active, b2, 5, 6
    Transaction + Wall.Create() :active, b3, 6, 8
    Signal.Set()                :b4, 8, 8
    Tiếp tục UI                 :b5, 8, 10
```

### 5.5. Flowchart xử lý chi tiết

```mermaid
flowchart TB
    subgraph "Worker Thread (HttpListener)"
        A[Nhận HTTP Request] --> B[Parse JSON-RPC]
        B --> C{method?}
        C -->|initialize| D1[Trả server info]
        C -->|tools/list| D2[Trả danh sách tool]
        C -->|tools/call| D3[Gán PendingAction vào Handler]
        D3 --> E["ExternalEvent.Raise()"]
        E --> F["ManualResetEvent.WaitOne(30s)\n— CHỜ ĐỢI —"]
        F --> G{Timeout?}
        G -->|Có| H1[Trả lỗi timeout]
        G -->|Không| H2[Đọc kết quả từ Handler]
        H2 --> I[Gửi HTTP Response]
    end

    subgraph "Revit Main Thread"
        J["Revit idle → gọi Execute()"] --> K[Lấy PendingAction]
        K --> L[Chạy tool với Document]
        L --> M["ManualResetEvent.Set()\n— BÁO HIỆU XONG —"]
    end

    E -.->|"Schedule qua ExternalEvent"| J
    M -.->|"Kết quả"| F
```

***

## 6. Router Pattern — Định tuyến Tool Call

### 6.1. Router là gì?

Router là một pattern đơn giản: nhận tên tool từ request, tìm tool tương ứng trong registry, và gọi nó. Tương tự cách một web framework (Express, ASP.NET) định tuyến URL đến controller.

### 6.2. Tại sao cần Router?

* **Mở rộng dễ dàng**: Thêm tool mới chỉ cần tạo class mới và đăng ký — không sửa code khác
* **Tách biệt trách nhiệm**: Mỗi tool là một class riêng, dễ test và bảo trì
* **Tra cứu O(1)**: Dùng `Dictionary` để tìm tool theo tên

### 6.3. Cách hoạt động của Router

```mermaid
flowchart LR
    REQ["Request JSON-RPC\n{name: 'create_wall',\narguments: {...}}"]

    REQ --> ROUTER["McpRouter\n.Route(name, args, doc)"]

    ROUTER -->|"'create_wall'"| T1["CreateWallTool\n.Execute(args, doc)"]
    ROUTER -->|"'get_elements'"| T2["GetElementsTool\n.Execute(args, doc)"]
    ROUTER -->|"'delete_element'"| T3["DeleteElementTool\n.Execute(args, doc)"]
    ROUTER -->|"unknown"| ERR["Error:\n'Unknown tool'"]

    T1 --> RES["JsonObject\nkết quả"]
    T2 --> RES
    T3 --> RES
```

***

## 7. Triển khai RevitMcpListener — Full C# Code

Dưới đây là toàn bộ mã nguồn C# cho phần core MCP Server trong Revit. Code được chia thành nhiều file/class để rõ ràng.

> **Nguồn trích cho phần phân tích core:** các đoạn code trong Mục 7 được lấy từ thư mục [`plugin/Core`](https://github.com/nguyenngocdue/deepbim-revit-mcp-plugin/tree/master/plugin/Core) của dự án `deepbim-revit-mcp-plugin`.

### 7.1. IMcpTool — Interface chung cho mọi tool

```csharp
// File: IMcpTool.cs
using System.Text.Json.Nodes;
using Autodesk.Revit.DB;

namespace RevitMcp.Core
{
    /// <summary>
    /// Interface chung cho tất cả MCP tool.
    /// Mọi tool phải khai báo Name, Description và method Execute.
    ///
    /// Thiết kế theo Open/Closed Principle:
    /// - Mở để thêm tool mới (tạo class mới implement interface này)
    /// - Đóng để sửa Router (Router không cần thay đổi)
    /// </summary>
    public interface IMcpTool
    {
        /// <summary>
        /// Tên của tool — trùng với "name" trong JSON-RPC request.
        /// Ví dụ: "create_wall", "get_elements", "delete_element"
        /// Quy ước: dùng snake_case, chữ thường
        /// </summary>
        string Name { get; }

        /// <summary>
        /// Mô tả tool — AI Agent sẽ đọc mô tả này để biết khi nào nên gọi tool.
        /// Viết rõ ràng, cụ thể để AI chọn đúng tool.
        /// </summary>
        string Description { get; }

        /// <summary>
        /// Thực thi tool với các tham số từ AI Agent.
        /// Phương thức này được gọi trên Revit Main Thread — an toàn để gọi Revit API.
        /// </summary>
        /// <param name="parameters">Tham số đầu vào (JSON object từ AI Agent)</param>
        /// <param name="doc">Revit Document hiện tại</param>
        /// <returns>Kết quả trả về (JSON object)</returns>
        JsonObject Execute(JsonObject parameters, Document doc);
    }
}
```

### 7.2. McpEventHandler — Cầu nối giữa Worker Thread và Revit Main Thread

```csharp
// File: McpEventHandler.cs
using System;
using System.Threading;
using Autodesk.Revit.UI;

namespace RevitMcp.Core
{
    /// <summary>
    /// Handler cho ExternalEvent của Revit.
    ///
    /// Vai trò: nhận hành động từ Worker Thread và thực thi trên Revit Main Thread.
    ///
    /// Luồng hoạt động:
    ///   1. Worker Thread gán PendingAction và gọi ExternalEvent.Raise()
    ///   2. Revit gọi Execute() trên Main Thread khi idle
    ///   3. Execute() chạy PendingAction với UIApplication
    ///   4. Sau khi xong, gọi Signal.Set() để báo Worker Thread
    ///   5. Worker Thread thức dậy và đọc kết quả
    /// </summary>
    public class McpEventHandler : IExternalEventHandler
    {
        /// <summary>
        /// Hành động cần thực thi trên Revit Main Thread.
        /// Worker Thread sẽ gán giá trị này trước khi gọi Raise().
        /// </summary>
        public Action<UIApplication> PendingAction { get; set; }

        /// <summary>
        /// Tín hiệu để đồng bộ giữa Worker Thread và Main Thread.
        /// Worker Thread gọi WaitOne() để chờ.
        /// Main Thread gọi Set() để báo hiệu hoàn thành.
        /// </summary>
        public ManualResetEvent Signal { get; } = new ManualResetEvent(false);

        /// <summary>
        /// Revit gọi method này trên Main Thread khi có Raise() và Revit đang idle.
        /// </summary>
        public void Execute(UIApplication app)
        {
            try
            {
                // Chạy hành động đã được Worker Thread gán vào
                PendingAction?.Invoke(app);
            }
            catch (Exception ex)
            {
                // Lỗi được xử lý trong PendingAction — log ra đây để debug
                System.Diagnostics.Debug.WriteLine(
                    $"[MCP Handler] Lỗi khi thực thi PendingAction: {ex.Message}");
            }
            finally
            {
                // LUÔN báo hiệu cho Worker Thread biết đã xong,
                // dù có lỗi hay không — tránh Worker Thread chờ mãi mãi
                Signal.Set();
            }
        }

        /// <summary>
        /// Tên của handler — dùng cho debug/logging trong Revit journal.
        /// </summary>
        public string GetName() => "McpEventHandler";
    }
}
```

### 7.3. McpRouter — Định tuyến tool call

```csharp
// File: McpRouter.cs
using System;
using System.Collections.Generic;
using System.Text.Json.Nodes;
using Autodesk.Revit.DB;

namespace RevitMcp.Core
{
    /// <summary>
    /// Router chịu trách nhiệm:
    ///   1. Lưu trữ danh sách các tool đã đăng ký
    ///   2. Tìm tool theo tên (tra cứu O(1) bằng Dictionary)
    ///   3. Gọi tool và trả kết quả
    ///
    /// Pattern: Registry + Strategy
    /// </summary>
    public class McpRouter
    {
        // Dictionary lưu tool theo tên — tra cứu O(1)
        private readonly Dictionary<string, IMcpTool> _tools
            = new Dictionary<string, IMcpTool>();

        /// <summary>
        /// Đăng ký một tool mới vào router.
        /// Gọi method này trong OnStartup() để thêm tool trước khi server bắt đầu.
        /// </summary>
        public void RegisterTool(IMcpTool tool)
        {
            if (tool == null)
                throw new ArgumentNullException(nameof(tool));

            // Lưu tool với key là tên tool (lowercase để tránh case-sensitivity)
            _tools[tool.Name.ToLowerInvariant()] = tool;

            System.Diagnostics.Debug.WriteLine(
                $"[MCP Router] Đã đăng ký tool: {tool.Name}");
        }

        /// <summary>
        /// Tìm và gọi tool theo tên.
        /// </summary>
        /// <param name="toolName">Tên tool từ JSON-RPC request</param>
        /// <param name="parameters">Tham số đầu vào</param>
        /// <param name="doc">Revit Document</param>
        /// <returns>Kết quả JSON</returns>
        public JsonObject Route(string toolName, JsonObject parameters, Document doc)
        {
            var key = toolName?.ToLowerInvariant() ?? string.Empty;

            // Tìm tool trong registry
            if (_tools.TryGetValue(key, out var tool))
            {
                System.Diagnostics.Debug.WriteLine(
                    $"[MCP Router] Gọi tool: {toolName}");
                return tool.Execute(parameters, doc);
            }

            // Không tìm thấy tool — trả về lỗi kèm danh sách tool khả dụng
            return new JsonObject
            {
                ["error"] = $"Unknown tool: '{toolName}'",
                ["available_tools"] = GetToolList()
            };
        }

        /// <summary>
        /// Lấy danh sách tất cả tool đã đăng ký.
        /// Dùng cho phần "tools/list" của MCP protocol.
        /// </summary>
        public JsonArray GetToolList()
        {
            var list = new JsonArray();
            foreach (var tool in _tools.Values)
            {
                list.Add(new JsonObject
                {
                    ["name"] = tool.Name,
                    ["description"] = tool.Description
                });
            }
            return list;
        }

        /// <summary>
        /// Kiểm tra xem một tool có được đăng ký hay không.
        /// </summary>
        public bool HasTool(string toolName)
            => _tools.ContainsKey(toolName?.ToLowerInvariant() ?? string.Empty);
    }
}
```

### 7.4. RevitMcpListener — HTTP Server chính

```csharp
// File: RevitMcpListener.cs
using System;
using System.IO;
using System.Net;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Threading;
using Autodesk.Revit.UI;

namespace RevitMcp.Core
{
    /// <summary>
    /// HTTP Listener chạy trong Revit plugin.
    /// Lắng nghe các JSON-RPC request từ AI Agent và định tuyến đến tool tương ứng.
    ///
    /// Thiết kế:
    ///   - Chạy trên Worker Thread (Background) để không block Revit UI
    ///   - Sử dụng ExternalEvent để chuyển việc sang Main Thread khi cần gọi Revit API
    ///   - Lock để đảm bảo chỉ xử lý 1 request tại 1 thời điểm
    ///     (Revit chỉ cho phép 1 Transaction tại 1 thời điểm)
    /// </summary>
    public class RevitMcpListener
    {
        private HttpListener _httpListener;
        private Thread _listenerThread;
        private volatile bool _isRunning;

        private readonly McpRouter _router;
        private readonly McpEventHandler _eventHandler;
        private readonly ExternalEvent _externalEvent;
        private readonly int _port;

        // Lock object để đảm bảo chỉ xử lý 1 request tại 1 thời điểm
        private readonly object _requestLock = new object();

        public RevitMcpListener(
            McpRouter router,
            McpEventHandler eventHandler,
            ExternalEvent externalEvent,
            int port = 8080)
        {
            _router = router
                ?? throw new ArgumentNullException(nameof(router));
            _eventHandler = eventHandler
                ?? throw new ArgumentNullException(nameof(eventHandler));
            _externalEvent = externalEvent
                ?? throw new ArgumentNullException(nameof(externalEvent));
            _port = port;
        }

        /// <summary>
        /// Bắt đầu lắng nghe HTTP request.
        /// Tạo một background thread để không block Revit UI.
        /// </summary>
        public void Start()
        {
            _httpListener = new HttpListener();
            _httpListener.Prefixes.Add($"http://localhost:{_port}/");

            try
            {
                _httpListener.Start();
                _isRunning = true;

                // Tạo background thread — sẽ tự động dừng khi Revit đóng
                _listenerThread = new Thread(ListenLoop)
                {
                    IsBackground = true,
                    Name = "MCP-HttpListener"
                };
                _listenerThread.Start();

                System.Diagnostics.Debug.WriteLine(
                    $"[MCP] Listener started trên http://localhost:{_port}/");
            }
            catch (HttpListenerException ex)
            {
                System.Diagnostics.Debug.WriteLine(
                    $"[MCP] Không thể khởi động listener: {ex.Message}");
                System.Diagnostics.Debug.WriteLine(
                    $"[MCP] Thử chạy: " +
                    $"netsh http add urlacl url=http://localhost:{_port}/ user=Everyone");
                throw;
            }
        }

        /// <summary>
        /// Dừng lắng nghe và dọn dẹp tài nguyên.
        /// </summary>
        public void Stop()
        {
            _isRunning = false;

            try
            {
                _httpListener?.Stop();
                _httpListener?.Close();
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(
                    $"[MCP] Lỗi khi dừng listener: {ex.Message}");
            }

            System.Diagnostics.Debug.WriteLine("[MCP] Listener đã dừng.");
        }

        /// <summary>
        /// Vòng lặp chính: lắng nghe và xử lý request liên tục.
        /// Chạy trên Worker Thread.
        /// </summary>
        private void ListenLoop()
        {
            while (_isRunning)
            {
                try
                {
                    // Blocking call — đợi request mới
                    var context = _httpListener.GetContext();

                    // Xử lý request (đồng bộ, 1 request/lần)
                    ProcessRequest(context);
                }
                catch (HttpListenerException)
                {
                    // Xảy ra khi Stop() được gọi — bình thường
                    break;
                }
                catch (Exception ex)
                {
                    System.Diagnostics.Debug.WriteLine(
                        $"[MCP] Lỗi trong listen loop: {ex.Message}");
                }
            }
        }

        /// <summary>
        /// Xử lý một HTTP request.
        /// Parse JSON-RPC, định tuyến đến tool, trả kết quả.
        /// </summary>
        private void ProcessRequest(HttpListenerContext context)
        {
            var request = context.Request;
            var response = context.Response;

            // Thêm CORS headers để cho phép gọi từ browser/tool khác
            response.Headers.Add("Access-Control-Allow-Origin", "*");
            response.Headers.Add("Access-Control-Allow-Methods", "POST, OPTIONS");
            response.Headers.Add("Access-Control-Allow-Headers", "Content-Type");

            try
            {
                // Xử lý CORS preflight
                if (request.HttpMethod == "OPTIONS")
                {
                    response.StatusCode = 204;
                    response.Close();
                    return;
                }

                // Chỉ chấp nhận POST
                if (request.HttpMethod != "POST")
                {
                    SendError(response, 405, "Method Not Allowed. Use POST.");
                    return;
                }

                // Đọc body
                string body;
                using (var reader = new StreamReader(request.InputStream, Encoding.UTF8))
                {
                    body = reader.ReadToEnd();
                }

                System.Diagnostics.Debug.WriteLine($"[MCP] Nhận: {body}");

                // Parse JSON-RPC request
                var jsonRpc = JsonNode.Parse(body)?.AsObject();
                if (jsonRpc == null)
                {
                    SendJsonRpcError(response, null, -32700, "Parse error");
                    return;
                }

                var id = jsonRpc["id"];
                var method = jsonRpc["method"]?.GetValue<string>();

                // Xử lý các method của MCP protocol
                JsonObject result;
                switch (method)
                {
                    case "initialize":
                        result = HandleInitialize();
                        break;
                    case "tools/list":
                        result = HandleToolsList();
                        break;
                    case "tools/call":
                        result = HandleToolCall(jsonRpc);
                        break;
                    default:
                        result = new JsonObject
                        {
                            ["error"] = $"Unknown method: {method}"
                        };
                        break;
                }

                // Gửi response theo JSON-RPC format
                var rpcResponse = new JsonObject
                {
                    ["jsonrpc"] = "2.0",
                    ["id"] = id?.DeepClone(),
                    ["result"] = result
                };

                SendJson(response, 200, rpcResponse);
            }
            catch (Exception ex)
            {
                SendJsonRpcError(response, null, -32603,
                    $"Internal error: {ex.Message}");
            }
        }

        /// <summary>
        /// Xử lý "initialize" — trả về thông tin server và capabilities.
        /// Đây là handshake đầu tiên trong MCP protocol.
        /// </summary>
        private JsonObject HandleInitialize()
        {
            return new JsonObject
            {
                ["protocolVersion"] = "2024-11-05",
                ["capabilities"] = new JsonObject
                {
                    ["tools"] = new JsonObject
                    {
                        ["listChanged"] = false
                    }
                },
                ["serverInfo"] = new JsonObject
                {
                    ["name"] = "revit-mcp-server",
                    ["version"] = "1.0.0",
                    ["description"] = "MCP Server nhúng trong Revit plugin"
                }
            };
        }

        /// <summary>
        /// Xử lý "tools/list" — trả về danh sách tool khả dụng.
        /// AI Agent đọc danh sách này để biết có thể làm gì.
        /// </summary>
        private JsonObject HandleToolsList()
        {
            return new JsonObject
            {
                ["tools"] = _router.GetToolList()
            };
        }

        /// <summary>
        /// Xử lý "tools/call" — gọi tool cụ thể qua ExternalEvent.
        ///
        /// Đây là phần PHỨC TẠP NHẤT vì cần chuyển từ Worker Thread sang Main Thread.
        ///
        /// Cơ chế:
        ///   1. Gán lambda (PendingAction) chứa logic cần chạy trên Main Thread
        ///   2. Gọi ExternalEvent.Raise() để lên lịch
        ///   3. WaitOne() để chờ kết quả
        ///   4. Trả về kết quả hoặc lỗi timeout
        /// </summary>
        private JsonObject HandleToolCall(JsonObject request)
        {
            var toolParams = request["params"]?.AsObject();
            var toolName = toolParams?["name"]?.GetValue<string>();
            var toolArgs = toolParams?["arguments"]?.AsObject() ?? new JsonObject();

            if (string.IsNullOrEmpty(toolName))
            {
                return new JsonObject
                {
                    ["error"] = "Missing tool name in params"
                };
            }

            // Lock để đảm bảo chỉ 1 request tại 1 thời điểm
            lock (_requestLock)
            {
                JsonObject toolResult = null;

                // Reset signal trước khi bắt đầu
                _eventHandler.Signal.Reset();

                // Gán hành động sẽ chạy trên Revit Main Thread
                _eventHandler.PendingAction = (uiApp) =>
                {
                    var doc = uiApp.ActiveUIDocument?.Document;
                    if (doc == null)
                    {
                        toolResult = new JsonObject
                        {
                            ["error"] = "No active document in Revit. " +
                                        "Hãy mở một file .rvt trước khi gọi tool."
                        };
                        return;
                    }

                    // GỌI TOOL — chạy trên Main Thread, an toàn với Revit API
                    toolResult = _router.Route(toolName, toolArgs, doc);
                };

                // Yêu cầu Revit gọi Execute() trên Main Thread
                var raiseStatus = _externalEvent.Raise();
                System.Diagnostics.Debug.WriteLine(
                    $"[MCP] ExternalEvent.Raise() status: {raiseStatus}");

                // Chờ đợi kết quả (timeout 30 giây)
                bool completed = _eventHandler.Signal.WaitOne(
                    TimeSpan.FromSeconds(30));

                if (!completed)
                {
                    return new JsonObject
                    {
                        ["error"] = "Timeout: Revit không phản hồi trong 30 giây. " +
                                    "Revit có thể đang bận hoặc hiển thị dialog."
                    };
                }

                return toolResult ?? new JsonObject
                {
                    ["error"] = "Tool không trả về kết quả"
                };
            }
        }

        // ─── Helper methods ────────────────────────────────────────────────────

        private void SendJson(HttpListenerResponse response, int statusCode,
            JsonObject json)
        {
            var jsonString = json.ToJsonString(
                new JsonSerializerOptions { WriteIndented = true });
            var buffer = Encoding.UTF8.GetBytes(jsonString);

            response.StatusCode = statusCode;
            response.ContentType = "application/json; charset=utf-8";
            response.ContentLength64 = buffer.Length;
            response.OutputStream.Write(buffer, 0, buffer.Length);
            response.Close();
        }

        private void SendError(HttpListenerResponse response, int statusCode,
            string message)
        {
            SendJson(response, statusCode, new JsonObject
            {
                ["error"] = message
            });
        }

        private void SendJsonRpcError(HttpListenerResponse response,
            JsonNode id, int code, string message)
        {
            var errorResponse = new JsonObject
            {
                ["jsonrpc"] = "2.0",
                ["id"] = id?.DeepClone(),
                ["error"] = new JsonObject
                {
                    ["code"] = code,
                    ["message"] = message
                }
            };
            SendJson(response, 200, errorResponse);
        }
    }
}
```

### 7.5. RevitMcpApp — Entry point (IExternalApplication)

```csharp
// File: RevitMcpApp.cs
using System;
using Autodesk.Revit.UI;
using RevitMcp.Core;
using RevitMcp.Tools;

namespace RevitMcp
{
    /// <summary>
    /// Entry point chính của Revit Add-in.
    ///
    /// Khi Revit khởi động, OnStartup() sẽ:
    ///   1. Tạo McpEventHandler và ExternalEvent
    ///   2. Đăng ký các tool vào Router
    ///   3. Khởi động HTTP Listener
    ///
    /// Khi Revit đóng, OnShutdown() sẽ:
    ///   1. Dừng HTTP Listener
    ///   2. Giải phóng tài nguyên
    /// </summary>
    public class RevitMcpApp : IExternalApplication
    {
        private RevitMcpListener _listener;

        public Result OnStartup(UIControlledApplication application)
        {
            try
            {
                // Bước 1: Tạo event handler và external event
                var handler = new McpEventHandler();
                var externalEvent = ExternalEvent.Create(handler);

                // Bước 2: Tạo router và đăng ký các tool
                var router = new McpRouter();
                router.RegisterTool(new CreateWallTool());
                router.RegisterTool(new GetElementsTool());
                // Thêm các tool khác ở đây khi mở rộng...
                // router.RegisterTool(new DeleteElementTool());
                // router.RegisterTool(new GetLevelsTool());

                // Bước 3: Tạo và khởi động listener
                _listener = new RevitMcpListener(
                    router,
                    handler,
                    externalEvent,
                    port: 8080);
                _listener.Start();

                // Thông báo thành công
                TaskDialog.Show(
                    "MCP Server",
                    "Revit MCP Server đã khởi động thành công!\n" +
                    "Đang lắng nghe tại: http://localhost:8080/\n\n" +
                    "AI Agent có thể kết nối và gọi các tool Revit.");

                return Result.Succeeded;
            }
            catch (Exception ex)
            {
                TaskDialog.Show(
                    "MCP Error",
                    $"Không thể khởi động MCP Server:\n{ex.Message}\n\n" +
                    "Kiểm tra:\n" +
                    "1. Port 8080 có đang bị chiếm không?\n" +
                    "2. Có quyền Admin hoặc đã chạy netsh urlacl chưa?");
                return Result.Failed;
            }
        }

        public Result OnShutdown(UIControlledApplication application)
        {
            // Dọn dẹp: dừng listener khi Revit đóng
            _listener?.Stop();
            System.Diagnostics.Debug.WriteLine("[MCP] Plugin đã tắt.");
            return Result.Succeeded;
        }
    }
}
```

### 7.6. File .addin — Đăng ký với Revit

```xml
<!-- File: RevitMcp.addin -->
<!-- Đặt vào thư mục: %AppData%\Autodesk\Revit\Addins\2024\ -->
<?xml version="1.0" encoding="utf-8"?>
<RevitAddIns>
  <AddIn Type="Application">
    <Name>Revit MCP Server</Name>

    <!-- Full qualified class name: Namespace.ClassName -->
    <FullClassName>RevitMcp.RevitMcpApp</FullClassName>

    <!-- Đường dẫn tuyệt đối đến file DLL -->
    <Assembly>C:\RevitPlugins\RevitMcp\RevitMcp.dll</Assembly>

    <!-- GUID duy nhất — tạo bằng: [guid]::NewGuid() trong PowerShell -->
    <AddInId>A1B2C3D4-E5F6-7890-ABCD-EF1234567890</AddInId>

    <VendorId>DeepBIM</VendorId>
    <VendorDescription>DeepBIM — Revit MCP Plugin</VendorDescription>
    <VendorEmail>contact@deepbim.com</VendorEmail>
  </AddIn>
</RevitAddIns>
```

***

## 8. Triển khai CreateWallTool

Đây là ví dụ cụ thể về một MCP tool: tạo tường (Wall) trong Revit từ yêu cầu của AI Agent.

```csharp
// File: Tools/CreateWallTool.cs
using System;
using System.Text.Json.Nodes;
using Autodesk.Revit.DB;
using RevitMcp.Core;

namespace RevitMcp.Tools
{
    /// <summary>
    /// Tool tạo tường trong Revit.
    ///
    /// AI Agent sẽ gọi tool này khi người dùng nói:
    ///   "Tạo một bức tường dài 5m ở vị trí (0,0) đến (5,0) trên Level 1"
    ///
    /// Input JSON (tất cả đơn vị là millimet):
    /// {
    ///   "start_x": 0,
    ///   "start_y": 0,
    ///   "end_x": 5000,
    ///   "end_y": 0,
    ///   "height": 3000,
    ///   "level_name": "Level 1"
    /// }
    ///
    /// Lưu ý quan trọng về đơn vị:
    ///   - Input từ AI Agent: millimet (mm) — dễ hiểu với người dùng
    ///   - Revit API internal: feet (ft) — 1 ft = 304.8 mm
    ///   - Tool phải tự chuyển đổi
    /// </summary>
    public class CreateWallTool : IMcpTool
    {
        public string Name => "create_wall";

        public string Description =>
            "Tạo một bức tường (Wall) trong Revit. " +
            "Tham số cần có: tọa độ điểm đầu (start_x, start_y), " +
            "tọa độ điểm cuối (end_x, end_y), chiều cao (height) — " +
            "tất cả tính bằng millimet. " +
            "Và tên Level (level_name, ví dụ: 'Level 1').";

        public JsonObject Execute(JsonObject parameters, Document doc)
        {
            // ── BƯỚC 1: Đọc tham số từ JSON ────────────────────────────────────
            double startX = parameters["start_x"]?.GetValue<double>() ?? 0;
            double startY = parameters["start_y"]?.GetValue<double>() ?? 0;
            double endX   = parameters["end_x"]?.GetValue<double>()   ?? 1000;
            double endY   = parameters["end_y"]?.GetValue<double>()   ?? 0;
            double height = parameters["height"]?.GetValue<double>()  ?? 3000;
            string levelName = parameters["level_name"]?.GetValue<string>()
                               ?? "Level 1";

            // ── BƯỚC 2: Validate tham số ────────────────────────────────────────
            if (height <= 0)
            {
                return new JsonObject
                {
                    ["error"] = "Chiều cao phải lớn hơn 0.",
                    ["received_height"] = height
                };
            }

            // Kiểm tra điểm đầu và điểm cuối không trùng nhau
            if (Math.Abs(endX - startX) < 1 && Math.Abs(endY - startY) < 1)
            {
                return new JsonObject
                {
                    ["error"] = "Điểm đầu và điểm cuối không được trùng nhau. " +
                                "Chiều dài tường phải lớn hơn 1mm."
                };
            }

            // ── BƯỚC 3: Chuyển đổi đơn vị mm → feet ───────────────────────────
            // Revit API LUÔN sử dụng đơn vị feet (internal units)
            // 1 foot = 304.8 mm
            const double mmToFeet = 1.0 / 304.8;

            double startXFt = startX * mmToFeet;
            double startYFt = startY * mmToFeet;
            double endXFt   = endX   * mmToFeet;
            double endYFt   = endY   * mmToFeet;
            double heightFt = height * mmToFeet;

            // ── BƯỚC 4: Tìm Level trong model ──────────────────────────────────
            Level level = FindLevelByName(doc, levelName);
            if (level == null)
            {
                return new JsonObject
                {
                    ["error"] = $"Không tìm thấy Level '{levelName}' trong model.",
                    ["hint"] = "Hãy kiểm tra tên Level chính xác. " +
                               "Sử dụng tool 'get_levels' để xem danh sách."
                };
            }

            // ── BƯỚC 5: Tạo Wall trong Transaction ─────────────────────────────
            // MỌI thao tác ghi vào model PHẢI nằm trong Transaction
            using (var tx = new Transaction(doc, "MCP: Create Wall"))
            {
                tx.Start();

                try
                {
                    // Tạo đường thẳng (Line) từ điểm đầu đến điểm cuối
                    var startPoint = new XYZ(startXFt, startYFt, 0);
                    var endPoint   = new XYZ(endXFt,   endYFt,   0);
                    var wallLine   = Line.CreateBound(startPoint, endPoint);

                    // Tạo tường
                    // Wall.Create(doc, curve, levelId, structural)
                    // structural = false: tường kiến trúc (không phải tường kết cấu)
                    var wall = Wall.Create(
                        doc,
                        wallLine,
                        level.Id,
                        structural: false
                    );

                    // Đặt chiều cao tường (Unconnected Height parameter)
                    var heightParam = wall.get_Parameter(
                        BuiltInParameter.WALL_USER_HEIGHT_PARAM);
                    if (heightParam != null && !heightParam.IsReadOnly)
                    {
                        heightParam.Set(heightFt);
                    }

                    tx.Commit();

                    // Tính chiều dài thực tế của tường (mm)
                    double lengthMm = Math.Round(wallLine.Length / mmToFeet, 1);

                    // Trả kết quả thành công
                    return new JsonObject
                    {
                        ["success"]  = true,
                        ["wall_id"]  = wall.Id.IntegerValue,
                        ["message"]  = $"Đã tạo tường thành công! " +
                                       $"ElementId = {wall.Id.IntegerValue}",
                        ["details"]  = new JsonObject
                        {
                            ["length_mm"]  = lengthMm,
                            ["height_mm"]  = height,
                            ["level"]      = levelName,
                            ["wall_type"]  = wall.WallType?.Name ?? "Unknown"
                        }
                    };
                }
                catch (Exception ex)
                {
                    // Lỗi xảy ra — rollback Transaction để không làm hỏng model
                    if (tx.GetStatus() == TransactionStatus.Started)
                        tx.RollBack();

                    return new JsonObject
                    {
                        ["error"]       = $"Lỗi khi tạo tường: {ex.Message}",
                        ["stack_trace"] = ex.StackTrace
                    };
                }
            }
        }

        /// <summary>
        /// Tìm Level theo tên trong Document.
        /// Dùng FilteredElementCollector — cách chuẩn để query element trong Revit.
        /// </summary>
        private Level FindLevelByName(Document doc, string name)
        {
            var collector = new FilteredElementCollector(doc)
                .OfClass(typeof(Level));

            foreach (Level level in collector)
            {
                if (level.Name.Equals(name, StringComparison.OrdinalIgnoreCase))
                    return level;
            }

            return null;
        }
    }
}
```

### Giải thích chi tiết

#### Tại sao phải chuyển đổi đơn vị?

Revit API **luôn sử dụng đơn vị feet** (internal units) bất kể cài đặt hiển thị của project. Nếu bạn muốn tạo tường dài 5 mét, bạn phải truyền vào `5000 / 304.8 ≈ 16.4 feet`.

Quy ước trong khóa học này: AI Agent gửi **millimet** (mm) — đơn vị thân quen với kỹ sư xây dựng — và tool tự chuyển sang feet.

#### Tại sao phải dùng Transaction?

Revit API bắt buộc mọi thao tác **ghi** (tạo, sửa, xóa element) phải nằm trong một `Transaction`. Điều này giúp:

* **Undo/Redo** hoạt động đúng trong Revit
* Đảm bảo **tính nhất quán** của model (atomic operation)
* Cho phép **rollback** khi có lỗi

Pattern `using` với `Transaction` đảm bảo Transaction luôn được `Dispose()` dù có lỗi hay không. Nếu Transaction chưa được Commit hoặc RollBack, `Dispose()` sẽ tự động RollBack.

#### Tại sao cần validate tham số?

AI Agent có thể gửi dữ liệu không hợp lệ (chiều cao âm, điểm đầu = điểm cuối, level không tồn tại...). Validate sớm giúp trả về thông báo lỗi rõ ràng thay vì để Revit API throw exception khó hiểu.

***

## 9. Test với Postman / curl

### 9.1. Kiểm tra Initialize (Handshake)

```bash
curl -X POST http://localhost:8080/ \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "initialize",
    "params": {}
  }'
```

**Kết quả mong đợi:**

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2024-11-05",
    "capabilities": {
      "tools": { "listChanged": false }
    },
    "serverInfo": {
      "name": "revit-mcp-server",
      "version": "1.0.0",
      "description": "MCP Server nhúng trong Revit plugin"
    }
  }
}
```

### 9.2. Xem danh sách Tool

```bash
curl -X POST http://localhost:8080/ \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 2,
    "method": "tools/list",
    "params": {}
  }'
```

**Kết quả mong đợi:**

```json
{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "tools": [
      {
        "name": "create_wall",
        "description": "Tạo một bức tường (Wall) trong Revit..."
      },
      {
        "name": "get_elements",
        "description": "Lấy danh sách element theo category..."
      }
    ]
  }
}
```

### 9.3. Gọi CreateWallTool

```bash
curl -X POST http://localhost:8080/ \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 3,
    "method": "tools/call",
    "params": {
      "name": "create_wall",
      "arguments": {
        "start_x": 0,
        "start_y": 0,
        "end_x": 5000,
        "end_y": 0,
        "height": 3000,
        "level_name": "Level 1"
      }
    }
  }'
```

**Kết quả mong đợi (thành công):**

```json
{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "success": true,
    "wall_id": 12345,
    "message": "Đã tạo tường thành công! ElementId = 12345",
    "details": {
      "length_mm": 5000.0,
      "height_mm": 3000,
      "level": "Level 1",
      "wall_type": "Basic Wall"
    }
  }
}
```

**Kết quả khi có lỗi (Level không tồn tại):**

```json
{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "error": "Không tìm thấy Level 'Ground Floor' trong model.",
    "hint": "Hãy kiểm tra tên Level chính xác. Sử dụng tool 'get_levels' để xem danh sách."
  }
}
```

### 9.4. Test với Postman

1. Mở Postman, tạo request mới
2. Chọn method: **POST**
3. URL: `http://localhost:8080/`
4. Tab **Body** > chọn **raw** > chọn **JSON**
5. Dán nội dung JSON-RPC ở trên
6. Nhấn **Send**

> **Lưu ý:** Revit phải đang mở và có một project/document đang active. Nếu không, tool sẽ trả về lỗi `"No active document in Revit"`.

***

## 10. Câu hỏi tự suy nghĩ

### Câu 1: Tại sao không thể gọi Revit API trực tiếp từ HttpListener thread?

<details>

<summary>Xem đáp án</summary>

Revit API được thiết kế theo mô hình **Single-Threaded Apartment (STA)**. Mọi thao tác với model (đọc/ghi element, mở Transaction) **phải chạy trên Main Thread** của Revit — đó là thread mà Revit sử dụng để cập nhật UI và xử lý sự kiện.

Nếu bạn gọi Revit API từ một thread khác (như Worker Thread của HttpListener), bạn sẽ gặp một trong các vấn đề sau:

* `InvalidOperationException`: Revit từ chối thao tác
* **Crash**: Revit bị treo hoặc đóng đột ngột
* **Data corruption**: Model bị hỏng — tệ nhất trong 3 trường hợp

Vì vậy, chúng ta phải dùng cơ chế `ExternalEvent` để "nhảy" từ Worker Thread sang Main Thread. ExternalEvent hoạt động như một hàng đợi: bạn đặt hành động vào, Revit sẽ thực thi khi nó "rảnh" (idle).

</details>

***

### Câu 2: ExternalEvent vs Idling Event — Khi nào dùng cái nào?

<details>

<summary>Xem đáp án</summary>

|              | ExternalEvent                            | Idling Event                               |
| ------------ | ---------------------------------------- | ------------------------------------------ |
| Cách dùng    | Gọi `Raise()` khi cần                    | Đăng ký 1 lần, Revit gọi liên tục          |
| Khi nào chạy | Khi Revit idle VÀ bạn đã Raise           | Mỗi khi Revit idle (rất thường xuyên)      |
| Hiệu suất    | Tốt — chỉ chạy khi cần                   | Có thể ảnh hưởng hiệu suất nếu xử lý nhiều |
| Phù hợp      | **MCP Server** — chỉ chạy khi có request | Polling, cập nhật UI liên tục              |

**ExternalEvent** phù hợp hơn cho MCP vì:

* Chỉ chạy khi có request từ AI Agent (không lãng phí CPU)
* Có thể truyền dữ liệu qua handler một cách an toàn
* Không cần unsubscribe — tự động quản lý bởi Revit

**Idling Event** phù hợp khi bạn cần kiểm tra điều kiện liên tục (ví dụ: theo dõi thay đổi trong model và tự động sync).

</details>

***

### Câu 3: Điều gì xảy ra nếu một tool mất 60 giây để hoàn thành nhưng timeout là 30 giây?

<details>

<summary>Xem đáp án</summary>

Nếu tool mất hơn 30 giây, `WaitOne()` sẽ trả về `false`, và server trả về lỗi timeout cho AI Agent. **Nhưng vấn đề là: tool vẫn đang chạy trên Main Thread!** Điều này có thể gây ra:

1. Tool hoàn thành nhưng kết quả bị mất (không ai đọc)
2. Request tiếp theo có thể bị xung đột với tool đang chạy (cả hai cùng cố giữ lock)

**Cách khắc phục:**

1. **Tăng timeout** cho các tool nặng:

```csharp
// Trong HandleToolCall — xác định timeout theo từng tool
int timeoutMs = toolName == "process_all_elements" ? 120_000 : 30_000;
bool completed = _eventHandler.Signal.WaitOne(timeoutMs);
```

2. **Thêm CancellationToken** để có thể hủy tool:

```csharp
public class McpEventHandler : IExternalEventHandler
{
    public CancellationTokenSource CancellationSource { get; set; }
    // Trong tool: kiểm tra CancellationSource.Token.IsCancellationRequested
}
```

3. **Chia nhỏ công việc**: Nếu tool cần xử lý 1000 element, chia thành nhiều batch nhỏ, mỗi batch là một request riêng.
4. **Async pattern**: Trả về ngay một `job_id`, client poll kết quả sau — phức tạp hơn nhưng robust hơn cho các tác vụ dài.

</details>

***

### Câu 4: HTTP server đang lắng nghe trên localhost:8080 có những rủi ro bảo mật nào?

<details>

<summary>Xem đáp án</summary>

**Rủi ro:**

* Bất kỳ ứng dụng nào trên máy đều có thể gửi request đến localhost:8080
* Một trang web độc hại có thể gọi API qua JavaScript (CSRF attack)
* Không có authentication — ai cũng gọi được

**Cách giảm thiểu:**

1. **API Key**: Yêu cầu header `X-API-Key` trong mọi request:

```csharp
var apiKey = request.Headers["X-API-Key"];
if (apiKey != _expectedApiKey)
{
    SendError(response, 401, "Unauthorized: Invalid API Key");
    return;
}
```

2. **CORS nghiêm ngặt**: Chỉ cho phép origin cụ thể thay vì `*`
3. **Rate limiting**: Giới hạn số request/giây để tránh bị abuse
4. **Localhost only**: Chỉ bind trên `127.0.0.1`, không bind `0.0.0.0`
5. **Token-based auth**: Tạo token ngẫu nhiên khi Revit khởi động, AI Agent phải dùng token này

Trong thực tế, vì server chỉ chạy trên localhost và chỉ phục vụ AI Agent trên cùng máy, rủi ro tương đối thấp. Nhưng trong môi trường doanh nghiệp, nên áp dụng ít nhất API Key.

</details>

***

### Câu 5: Nếu muốn thêm tool "get\_all\_walls" để lấy danh sách tất cả tường, cần làm những bước nào?

<details>

<summary>Xem đáp án</summary>

Chỉ cần **2 bước** — đây chính là ưu điểm của Router pattern:

**Bước 1:** Tạo class mới implement `IMcpTool`:

```csharp
public class GetAllWallsTool : IMcpTool
{
    public string Name => "get_all_walls";

    public string Description =>
        "Lấy danh sách tất cả tường (Wall) trong model hiện tại. " +
        "Trả về: ElementId, tên Wall Type, chiều dài (mm), chiều cao (mm), Level.";

    public JsonObject Execute(JsonObject parameters, Document doc)
    {
        var collector = new FilteredElementCollector(doc)
            .OfClass(typeof(Wall))
            .WhereElementIsNotElementType();

        var walls = new JsonArray();
        foreach (Wall wall in collector)
        {
            double lengthMm = wall
                .get_Parameter(BuiltInParameter.CURVE_ELEM_LENGTH)
                ?.AsDouble() * 304.8 ?? 0;

            double heightMm = wall
                .get_Parameter(BuiltInParameter.WALL_USER_HEIGHT_PARAM)
                ?.AsDouble() * 304.8 ?? 0;

            walls.Add(new JsonObject
            {
                ["id"]        = wall.Id.IntegerValue,
                ["type"]      = wall.WallType?.Name ?? "Unknown",
                ["length_mm"] = Math.Round(lengthMm, 1),
                ["height_mm"] = Math.Round(heightMm, 1),
                ["level"]     = (doc.GetElement(wall.LevelId) as Level)?.Name ?? "?"
            });
        }

        return new JsonObject
        {
            ["count"] = walls.Count,
            ["walls"] = walls
        };
    }
}
```

**Bước 2:** Đăng ký trong `OnStartup()`:

```csharp
router.RegisterTool(new GetAllWallsTool());
```

Không cần sửa bất kỳ code nào khác — Router sẽ tự động định tuyến tool mới.

</details>

***

### Câu 6: Tại sao dùng `lock (_requestLock)` trong HandleToolCall?

<details>

<summary>Xem đáp án</summary>

`lock` đảm bảo chỉ **một request** được xử lý tại một thời điểm.

**Nếu không có lock:**

Giả sử AI Agent gửi 2 request đồng thời (request A và request B):

1. Request A gán `PendingAction` = "create wall" vào `_eventHandler`
2. Request B gán `PendingAction` = "delete element" vào `_eventHandler` — **ghi đè** của Request A!
3. Revit gọi `Execute()` — chạy "delete element" thay vì "create wall"
4. Request A nhận kết quả của "delete element" — sai hoàn toàn

**Với lock:**

* Request A vào lock, Request B phải chờ
* Request A hoàn thành (Raise → Wait → nhận kết quả)
* Lock được giải phóng
* Request B mới được xử lý

**Tại sao cần thiết trong context Revit:** Revit chỉ cho phép một Transaction tại một thời điểm. Nếu hai tool đều cố tạo Transaction đồng thời, sẽ có exception. Lock ở tầng HTTP xử lý vấn đề này trước khi đến Revit.

</details>

***

## 11. Tổng kết

### Những khái niệm đã học

| Khái niệm                                 | Mục đích                                                    |
| ----------------------------------------- | ----------------------------------------------------------- |
| `IExternalApplication`                    | Entry point của plugin, chạy suốt vòng đời Revit            |
| `HttpListener`                            | Tạo HTTP server nhẹ trong Revit để nhận request từ AI       |
| `ExternalEvent` + `IExternalEventHandler` | Chuyển việc từ Worker Thread sang Main Thread (thread-safe) |
| `ManualResetEvent`                        | Đồng bộ hóa giữa 2 thread (chờ kết quả)                     |
| Router Pattern                            | Định tuyến tool call, dễ mở rộng                            |
| `Transaction`                             | Bắt buộc khi ghi vào Revit model                            |
| Chuyển đổi đơn vị                         | mm (user) ↔ feet (Revit API internal)                       |
| `lock`                                    | Đảm bảo chỉ 1 request xử lý tại 1 thời điểm                 |

### Kiến trúc tổng thể — Luồng dữ liệu

```mermaid
flowchart TB
    subgraph "AI Agent (Claude / GPT)"
        Agent["AI Agent\nPrompt → Tool call"]
    end

    subgraph "Revit Process (revit.exe)"
        subgraph "Worker Thread"
            Listener["HttpListener\nPort 8080"]
            Router["McpRouter\n(Dictionary lookup)"]
        end

        subgraph "Revit Main Thread"
            Event["ExternalEvent\n(queue)"]
            Handler["McpEventHandler\n(PendingAction + Signal)"]
            subgraph "Tools"
                T1["CreateWallTool"]
                T2["GetElementsTool"]
                T3["..."]
            end
            API["Revit API\nDocument, Transaction"]
        end
    end

    Agent -->|"HTTP POST\nJSON-RPC 2.0"| Listener
    Listener --> Router
    Router -->|"Raise()"| Event
    Event -->|"Execute()"| Handler
    Handler --> T1
    Handler --> T2
    Handler --> T3
    T1 --> API
    T2 --> API
    API -->|"ElementId, data"| Handler
    Handler -->|"Signal.Set()"| Listener
    Listener -->|"HTTP Response\nJSON"| Agent
```

### Bước tiếp theo

Trong **Bài 6**, chúng ta sẽ:

* Cài đặt, cấu hình và kiểm tra tất cả các thành phần (Revit Plugin, MCP Server, VS Code, Claude Code)
* Build plugin C# và load vào Revit
* Cấu hình VS Code (`settings.json`) và Claude Code (`.mcp.json`) để kết nối MCP Server
* Xử lý các lỗi thường gặp khi kết nối các thành phần

***

## 12. Tài liệu tham khảo

### Mã nguồn tham khảo

| Tài nguyên                                            | Đường dẫn                                                                                                                                                                                          |
| ----------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **DeepBIM Revit MCP Plugin** (source code đầy đủ)     | [github.com/nguyenngocdue/deepbim-revit-mcp-plugin](https://github.com/nguyenngocdue/deepbim-revit-mcp-plugin)                                                                                     |
| **Core source cho Bài 5**                             | [github.com/nguyenngocdue/deepbim-revit-mcp-plugin/tree/master/plugin/Core](https://github.com/nguyenngocdue/deepbim-revit-mcp-plugin/tree/master/plugin/Core)                                     |
| **CommandSet source cho Bài 5**                       | [github.com/nguyenngocdue/deepbim-revit-mcp-plugin/tree/master/commandset](https://github.com/nguyenngocdue/deepbim-revit-mcp-plugin/tree/master/commandset)                                       |
| **Addin entry file**                                  | [github.com/nguyenngocdue/deepbim-revit-mcp-plugin/blob/master/plugin/revit-mcp-plugin.addin](https://github.com/nguyenngocdue/deepbim-revit-mcp-plugin/blob/master/plugin/revit-mcp-plugin.addin) |
| **Revit MCP Server** (tham chiếu tool/server thực tế) | [github.com/nguyenngocdue/revit-mcp-server](https://github.com/nguyenngocdue/revit-mcp-server)                                                                                                     |

### MCP Protocol

| Tài liệu                                     | Đường dẫn                                                                                                                                                  |
| -------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| MCP Specification chính thức                 | [spec.modelcontextprotocol.io](https://spec.modelcontextprotocol.io/)                                                                                      |
| MCP Transports (stdio, SSE, HTTP Streamable) | [spec.modelcontextprotocol.io/specification/2024-11-05/basic/transports/](https://spec.modelcontextprotocol.io/specification/2024-11-05/basic/transports/) |
| MCP TypeScript SDK                           | [github.com/modelcontextprotocol/typescript-sdk](https://github.com/modelcontextprotocol/typescript-sdk)                                                   |

### Revit API

| Tài liệu                                        | Đường dẫn                                                                                                      |
| ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| Revit API Documentation                         | [revitapidocs.com](https://www.revitapidocs.com/)                                                              |
| ExternalEvent Class                             | [revitapidocs.com — ExternalEvent](https://www.revitapidocs.com/2024/4b5f84a0-3e98-c4e1-135a-ee578ee22e6e.htm) |
| IExternalEventHandler                           | [revitapidocs.com — IExternalEventHandler](https://www.revitapidocs.com/2024/)                                 |
| Wall.Create()                                   | [revitapidocs.com — Wall.Create](https://www.revitapidocs.com/2024/)                                           |
| The Building Coder (blog kinh nghiệm Revit API) | [thebuildingcoder.typepad.com](https://thebuildingcoder.typepad.com/)                                          |
| Revit API Forum (Autodesk)                      | [forums.autodesk.com/t5/revit-api-forum/bd-p/160](https://forums.autodesk.com/t5/revit-api-forum/bd-p/160)     |

### .NET / C\#

| Tài liệu                      | Đường dẫn                                                                                                                |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| HttpListener (Microsoft Docs) | [learn.microsoft.com — HttpListener](https://learn.microsoft.com/en-us/dotnet/api/system.net.httplistener)               |
| ManualResetEvent              | [learn.microsoft.com — ManualResetEvent](https://learn.microsoft.com/en-us/dotnet/api/system.threading.manualresetevent) |
| Transaction (Revit API)       | [revitapidocs.com — Transaction](https://www.revitapidocs.com/2024/)                                                     |

***

> **Ghi chú:** Phần **core** trong Bài 5 được trích từ [`plugin/Core`](https://github.com/nguyenngocdue/deepbim-revit-mcp-plugin/tree/master/plugin/Core). Một số đoạn có thể được rút gọn hoặc thêm chú thích để phục vụ học tập, còn bản production đầy đủ nằm trong repository chính thức.

***

**Điều hướng:**

[← Bài 4: Dựng MCP Server bằng Node.js](/revit-mcp-ai/phan-2-xay-dung-tung-thanh-phan/bai-4.md) | [Bài 6: Chuẩn bị sẵn sàng kết nối các thành phần →](/revit-mcp-ai/phan-3-ket-noi-and-van-hanh/bai-6.md)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://deepbim.gitbook.io/revit-mcp-ai/phan-2-xay-dung-tung-thanh-phan/bai-5.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
