# Bài 8: Mở rộng — Viết thêm Tool

> **"Một plugin mạnh mẽ không phải là plugin có nhiều tính năng nhất, mà là plugin có những công cụ đúng chỗ, đúng lúc."**
>
> **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. [Tổng quan hệ sinh thái Tool](#2-tổng-quan-hệ-sinh-thái-tool)
3. [Triển khai GetRoomsTool](#3-triển-khai-getroomstool)
4. [Triển khai GetFloorsTool](#4-triển-khai-getfloorstool)
5. [Triển khai CreateColumnTool](#5-triển-khai-createcolumntool)
6. [Triển khai DeleteElementTool](#6-triển-khai-deleteelementtool)
7. [Đăng ký Tools mới trong MCP Server (TypeScript)](#7-đăng-ký-tools-mới-trong-mcp-server-typescript)
8. [Prompt Engineering cho BIM](#8-prompt-engineering-cho-bim)
9. [Ví dụ nâng cao: Multi-tool Workflow](#9-ví-dụ-nâng-cao-multi-tool-workflow)
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

Sau khi hoàn thành Bài 7, bạn đã có hệ thống **Revit Plugin (C#) ↔ MCP Server (TypeScript) ↔ AI** hoạt động trơn tru với các tool cơ bản như `get_walls` và `create_wall`. Đây là nền tảng vững chắc để tiến lên tầng tiếp theo.

Bài học này đưa bạn vào giai đoạn **mở rộng tool ecosystem** với bốn công cụ mới: hai tool đọc dữ liệu (`GetRoomsTool`, `GetFloorsTool`) và hai tool thao tác mô hình (`CreateColumnTool`, `DeleteElementTool`). Bên cạnh đó, bạn sẽ học cách viết prompt hiệu quả để AI khai thác tối đa những công cụ này trong ngữ cảnh BIM.

### Mục tiêu học tập

| Sau bài học này, bạn sẽ...                         | Mức độ         |
| -------------------------------------------------- | -------------- |
| Viết C# tool mới cho bất kỳ kiểu phần tử Revit nào | Thành thạo     |
| Đăng ký tool trong MCP Server TypeScript           | Thành thạo     |
| Hiểu vòng đời Transaction trong Revit API          | Nắm vững       |
| Chuyển đổi đơn vị feet ↔ mét chính xác             | Nắm vững       |
| Viết prompt BIM chuẩn mực, hiệu quả                | Ứng dụng được  |
| Thiết kế multi-tool workflow cho tác vụ phức tạp   | Hiểu nguyên lý |

### Điều kiện tiên quyết

* Đã hoàn thành Bài 3 đến Bài 7
* Plugin đang chạy và kết nối thành công với MCP Server
* Quen thuộc với cú pháp C# cơ bản và TypeScript cơ bản

***

## 2. Tổng quan hệ sinh thái Tool

Trước khi viết code, hãy nhìn toàn cảnh những gì chúng ta đang xây dựng. Một hệ thống tool tốt cần có cả công cụ đọc và công cụ ghi, phân loại rõ ràng để AI không bị nhầm lẫn.

### Sơ đồ hệ sinh thái Tool

```mermaid
graph TB
    subgraph AI["AI Client (Claude / GPT)"]
        P[Prompt người dùng]
        R[Kết quả trả về]
    end

    subgraph MCP["MCP Server (TypeScript)"]
        direction TB
        TR[Tool Registry]
        TR --> T1[get_walls]
        TR --> T2[create_wall]
        TR --> T3[get_rooms]
        TR --> T4[get_floors]
        TR --> T5[create_column]
        TR --> T6[delete_element]
    end

    subgraph PLUGIN["Revit Plugin (C#)"]
        direction TB
        RC[RequestController]
        RC --> W1[GetWallsTool]
        RC --> W2[CreateWallTool]
        RC --> W3[GetRoomsTool]
        RC --> W4[GetFloorsTool]
        RC --> W5[CreateColumnTool]
        RC --> W6[DeleteElementTool]
    end

    subgraph REVIT["Revit Application"]
        DB[(Document / BIM DB)]
    end

    P -->|tool call| TR
    TR -->|HTTP JSON| RC
    RC -->|Revit API| DB
    DB -->|dữ liệu BIM| RC
    RC -->|JSON response| TR
    TR -->|kết quả| R

    style AI fill:#dbeafe,stroke:#3b82f6
    style MCP fill:#dcfce7,stroke:#22c55e
    style PLUGIN fill:#fef9c3,stroke:#eab308
    style REVIT fill:#fee2e2,stroke:#ef4444
```

### Phân loại Tool theo chức năng

```mermaid
graph LR
    Tools[Tất cả Tools] --> Read[Nhóm Đọc\nRead-Only]
    Tools --> Write[Nhóm Ghi\nWrite / Modify]

    Read --> R1["get_walls\n(Lấy danh sách tường)"]
    Read --> R2["get_rooms\n(Lấy danh sách phòng)"]
    Read --> R3["get_floors\n(Lấy danh sách sàn)"]

    Write --> W1["create_wall\n(Tạo tường mới)"]
    Write --> W2["create_column\n(Tạo cột mới)"]
    Write --> W3["delete_element\n(Xóa phần tử)"]

    style Read fill:#dcfce7,stroke:#16a34a
    style Write fill:#fee2e2,stroke:#dc2626
```

> **Quy tắc vàng:** Tool thuộc nhóm Write **bắt buộc** phải sử dụng `Transaction`. Tool thuộc nhóm Read không cần Transaction nhưng phải xử lý trường hợp danh sách rỗng và phần tử bị lỗi.

### Bảng so sánh các kiểu phần tử sẽ xây dựng trong bài này

| Phần tử      | Lớp Revit API             | Nhóm  | Transaction? |
| ------------ | ------------------------- | ----- | ------------ |
| Phòng (Room) | `SpatialElement` → `Room` | Read  | Không        |
| Sàn (Floor)  | `Floor`                   | Read  | Không        |
| Cột (Column) | `FamilyInstance`          | Write | **Có**       |
| Xóa (Delete) | `Document.Delete()`       | Write | **Có**       |

***

## 3. Triển khai GetRoomsTool

### 3.1. Hiểu về Room trong Revit API

Trong Revit, `Room` là một `SpatialElement` — phần tử không gian được tính từ các tường bao quanh. Room có diện tích (`Area`), chu vi (`Perimeter`), thể tích (`Volume`), số tầng (`Level`), và tên (`Name`).

```
Room (SpatialElement)
├── Name           → "Phòng khách", "Phòng ngủ chính", ...
├── Number         → "101", "B-02", ...
├── Area           → diện tích (ft²) — phải chuyển sang m²
├── Perimeter      → chu vi (ft) — phải chuyển sang m
├── Volume         → thể tích (ft³) — phải chuyển sang m³
├── Level          → tầng chứa phòng
└── Location       → tọa độ trọng tâm phòng (LocationPoint)
```

**Lưu ý quan trọng:** Revit API luôn lưu trữ tất cả giá trị đo lường theo đơn vị **feet** bất kể cài đặt hiển thị của dự án. Bạn phải tự chuyển đổi sang mét trong code.

| Đại lượng | Đơn vị nội bộ Revit | Đơn vị hiển thị | Hệ số chuyển đổi |
| --------- | ------------------- | --------------- | ---------------- |
| Chiều dài | feet                | mét (m)         | × 0.3048         |
| Diện tích | feet²               | mét² (m²)       | × 0.092903       |
| Thể tích  | feet³               | mét³ (m³)       | × 0.028317       |
| Góc       | radian              | độ (°)          | × (180/π)        |

### 3.2. Full C# Code — GetRoomsTool.cs

```csharp
// File: Tools/GetRoomsTool.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Autodesk.Revit.DB;
using Autodesk.Revit.DB.Architecture;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace DeepBIM.RevitMCP.Tools
{
    /// <summary>
    /// Tool đọc danh sách phòng (Room / SpatialElement) từ Revit model.
    /// Trả về thông tin: tên, số phòng, diện tích, chu vi, thể tích, tầng.
    /// Không yêu cầu Transaction vì chỉ đọc dữ liệu.
    /// </summary>
    public class GetRoomsTool : IRevitTool
    {
        public string ToolName => "get_rooms";

        public JObject Execute(Document doc, JObject parameters)
        {
            try
            {
                // Đọc tham số lọc tùy chọn
                string levelFilter = parameters?["levelName"]?.ToString();
                string nameFilter  = parameters?["nameContains"]?.ToString();

                // Hệ số chuyển đổi đơn vị
                const double SqFeetToSqMeters = 0.092903;
                const double FeetToMeters     = 0.3048;
                const double CuFeetToCuMeters = 0.028317;

                // Lấy tất cả Room trong document
                // Room kế thừa từ SpatialElement, thuộc category OST_Rooms
                FilteredElementCollector collector = new FilteredElementCollector(doc)
                    .OfClass(typeof(SpatialElement))
                    .OfCategory(BuiltInCategory.OST_Rooms);

                List<Room> rooms = collector
                    .Cast<Room>()
                    // Bỏ qua phòng "unplaced" — tồn tại trong DB nhưng chưa được vẽ
                    // Room.Area == 0 nghĩa là phòng chưa đặt hoặc không được bao kín bởi tường
                    .Where(r => r != null && r.Area > 0)
                    .ToList();

                // Áp dụng bộ lọc tầng (không phân biệt hoa thường)
                if (!string.IsNullOrWhiteSpace(levelFilter))
                {
                    rooms = rooms
                        .Where(r => r.Level != null &&
                               r.Level.Name.IndexOf(levelFilter,
                                   StringComparison.OrdinalIgnoreCase) >= 0)
                        .ToList();
                }

                // Áp dụng bộ lọc tên phòng
                if (!string.IsNullOrWhiteSpace(nameFilter))
                {
                    rooms = rooms
                        .Where(r => r.Name != null &&
                               r.Name.IndexOf(nameFilter,
                                   StringComparison.OrdinalIgnoreCase) >= 0)
                        .ToList();
                }

                var roomList = rooms
                    .OrderBy(r => r.Level?.Name)
                    .ThenBy(r => r.Number)
                    .Select(room =>
                    {
                        // Đọc diện tích từ BuiltInParameter (chính xác hơn room.Area trực tiếp)
                        double areaFt2 = room.get_Parameter(BuiltInParameter.ROOM_AREA)
                                            ?.AsDouble() ?? room.Area;

                        // Đọc chu vi
                        double perimeterFt = room.get_Parameter(BuiltInParameter.ROOM_PERIMETER)
                                                ?.AsDouble() ?? 0;

                        // Đọc thể tích
                        double volumeFt3 = room.get_Parameter(BuiltInParameter.ROOM_VOLUME)
                                              ?.AsDouble() ?? 0;

                        // Đọc chiều cao phòng
                        double heightFt = room.get_Parameter(BuiltInParameter.ROOM_HEIGHT)
                                             ?.AsDouble() ?? 0;

                        // Lấy tọa độ trọng tâm — có thể null với phòng chưa đặt
                        LocationPoint loc = room.Location as LocationPoint;
                        double centerX = loc != null
                            ? Math.Round(loc.Point.X * FeetToMeters, 3)
                            : 0;
                        double centerY = loc != null
                            ? Math.Round(loc.Point.Y * FeetToMeters, 3)
                            : 0;

                        return new
                        {
                            id          = room.Id.IntegerValue,
                            name        = room.Name ?? "(Chưa đặt tên)",
                            number      = room.Number ?? "",
                            levelName   = room.Level?.Name ?? "(Không có tầng)",
                            department  = room.get_Parameter(BuiltInParameter.ROOM_DEPARTMENT)
                                             ?.AsString() ?? "",
                            areaSqM     = Math.Round(areaFt2 * SqFeetToSqMeters, 2),
                            perimeterM  = Math.Round(perimeterFt * FeetToMeters, 2),
                            volumeCuM   = Math.Round(volumeFt3 * CuFeetToCuMeters, 2),
                            heightM     = Math.Round(heightFt * FeetToMeters, 2),
                            centerX,
                            centerY
                        };
                    })
                    .ToList();

                // Thống kê theo tầng
                var summaryByLevel = roomList
                    .GroupBy(r => r.levelName)
                    .Select(g => new
                    {
                        levelName    = g.Key,
                        roomCount    = g.Count(),
                        totalAreaSqM = Math.Round(g.Sum(r => r.areaSqM), 2)
                    })
                    .OrderBy(s => s.levelName)
                    .ToList();

                double totalArea = roomList.Sum(r => r.areaSqM);

                return new JObject
                {
                    ["success"]       = true,
                    ["totalCount"]    = roomList.Count,
                    ["totalAreaSqM"]  = Math.Round(totalArea, 2),
                    ["filters"] = new JObject
                    {
                        ["levelName"]    = levelFilter ?? "Tất cả tầng",
                        ["nameContains"] = nameFilter ?? ""
                    },
                    ["summaryByLevel"] = JArray.FromObject(summaryByLevel),
                    ["rooms"]          = JArray.FromObject(roomList)
                };
            }
            catch (Exception ex)
            {
                return new JObject
                {
                    ["success"] = false,
                    ["error"]   = ex.Message,
                    ["tool"]    = ToolName
                };
            }
        }
    }
}
```

### 3.3. Giải thích chi tiết các thành phần quan trọng

| Dòng code                                            | Ý nghĩa                                                                                 |
| ---------------------------------------------------- | --------------------------------------------------------------------------------------- |
| `.OfClass(typeof(SpatialElement))`                   | Lọc đúng lớp cha của Room, Area, Space — hiệu quả hơn `.WhereElementIsNotElementType()` |
| `.OfCategory(BuiltInCategory.OST_Rooms)`             | Thu hẹp về phòng (Room), loại trừ Area và MEP Space                                     |
| `.Where(r => r.Area > 0)`                            | Loại bỏ Room unplaced hoặc unbounded (phòng chưa được bao kín bởi tường)                |
| `BuiltInParameter.ROOM_AREA`                         | Đọc qua parameter thay vì `room.Area` trực tiếp — nhất quán hơn khi có override         |
| `LocationPoint loc = room.Location as LocationPoint` | Location của Room là `LocationPoint`, không phải `LocationCurve` như tường              |

***

## 4. Triển khai GetFloorsTool

### 4.1. Floor trong Revit API

`Floor` là phần tử thuộc lớp `Floor` — khác hoàn toàn với `SpatialElement`. Sàn được định nghĩa bởi một sketch (đường biên), có thể có hình dạng phức tạp (đa giác bất kỳ), nhiều lớp vật liệu (`CompoundStructure`), và thuộc một tầng (`Level`) cụ thể.

```
Floor
├── FloorType       → kiểu sàn (ví dụ: "Sàn bê tông 200mm", "Sàn gỗ")
├── LevelId         → ID của tầng sàn thuộc về
├── HOST_AREA_COMPUTED   → diện tích (ft²) — tính tự động từ sketch
├── HOST_PERIMETER_COMPUTED → chu vi (ft)
├── FLOOR_HEIGHTABOVELEVEL_PARAM → offset so với tầng (ft)
└── CompoundStructure → cấu tạo các lớp vật liệu
```

### 4.2. Full C# Code — GetFloorsTool.cs

```csharp
// File: Tools/GetFloorsTool.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Autodesk.Revit.DB;
using Newtonsoft.Json.Linq;

namespace DeepBIM.RevitMCP.Tools
{
    /// <summary>
    /// Tool đọc danh sách sàn (Floor) từ Revit model.
    /// Trả về: loại sàn, diện tích (m²), độ dày (m), cao độ, tầng.
    /// Hỗ trợ lọc theo tầng, loại sàn, diện tích tối thiểu.
    /// </summary>
    public class GetFloorsTool : IRevitTool
    {
        public string ToolName => "get_floors";

        public JObject Execute(Document doc, JObject parameters)
        {
            try
            {
                // Đọc tham số lọc
                string levelFilter   = parameters?["levelName"]?.ToString();
                string typeFilter    = parameters?["floorType"]?.ToString();
                double minAreaFilter = parameters?["minAreaSqM"]?.Value<double>() ?? 0;

                // Hệ số chuyển đổi
                const double SqFeetToSqMeters = 0.092903;
                const double FeetToMeters     = 0.3048;

                // Lấy tất cả Floor — dùng OfClass(typeof(Floor)) thay vì OfCategory
                // vì Floor không có subtype phức tạp như FamilyInstance
                FilteredElementCollector collector = new FilteredElementCollector(doc)
                    .OfClass(typeof(Floor));

                List<Floor> floors = collector.Cast<Floor>().ToList();

                // Lọc theo tên tầng
                if (!string.IsNullOrWhiteSpace(levelFilter))
                {
                    floors = floors.Where(f =>
                    {
                        // Floor không có thuộc tính Level trực tiếp
                        // Phải lấy qua LevelId
                        Level level = doc.GetElement(f.LevelId) as Level;
                        return level != null &&
                               level.Name.IndexOf(levelFilter,
                                   StringComparison.OrdinalIgnoreCase) >= 0;
                    }).ToList();
                }

                // Lọc theo tên loại sàn
                if (!string.IsNullOrWhiteSpace(typeFilter))
                {
                    floors = floors.Where(f =>
                        f.FloorType?.Name?.IndexOf(typeFilter,
                            StringComparison.OrdinalIgnoreCase) >= 0
                    ).ToList();
                }

                var floorList = floors.Select(floor =>
                {
                    Level level = doc.GetElement(floor.LevelId) as Level;

                    // Diện tích — dùng HOST_AREA_COMPUTED (không phải ROOM_AREA)
                    double areaFt2 = floor.get_Parameter(BuiltInParameter.HOST_AREA_COMPUTED)
                                         ?.AsDouble() ?? 0;

                    // Chu vi
                    double perimeterFt = floor.get_Parameter(
                                             BuiltInParameter.HOST_PERIMETER_COMPUTED)
                                         ?.AsDouble() ?? 0;

                    // Offset sàn so với tầng (có thể âm nếu sàn thấp hơn tầng)
                    double offsetFt = floor.get_Parameter(
                                         BuiltInParameter.FLOOR_HEIGHTABOVELEVEL_PARAM)
                                     ?.AsDouble() ?? 0;

                    // Độ dày từ CompoundStructure — chính xác hơn dùng BuiltInParameter
                    FloorType floorType = floor.FloorType;
                    double thicknessFt = 0;
                    int layerCount = 0;

                    if (floorType != null)
                    {
                        CompoundStructure cs = floorType.GetCompoundStructure();
                        if (cs != null)
                        {
                            thicknessFt = cs.GetWidth();
                            layerCount  = cs.LayerCount;
                        }
                    }

                    double areaSqM = Math.Round(areaFt2 * SqFeetToSqMeters, 2);

                    return new
                    {
                        id             = floor.Id.IntegerValue,
                        typeName       = floorType?.Name ?? "(Không có kiểu)",
                        levelName      = level?.Name ?? "(Không có tầng)",
                        levelElevM     = Math.Round((level?.Elevation ?? 0) * FeetToMeters, 3),
                        offsetM        = Math.Round(offsetFt * FeetToMeters, 3),
                        areaSqM,
                        perimeterM     = Math.Round(perimeterFt * FeetToMeters, 2),
                        thicknessM     = Math.Round(thicknessFt * FeetToMeters, 3),
                        layerCount,
                        isValidArea    = areaSqM > 0
                    };
                })
                .Where(f => f.areaSqM >= minAreaFilter)
                .OrderBy(f => f.levelName)
                .ThenByDescending(f => f.areaSqM)
                .ToList();

                double totalArea = floorList.Sum(f => f.areaSqM);

                // Thống kê theo tầng
                var summaryByLevel = floorList
                    .GroupBy(f => f.levelName)
                    .Select(g => new
                    {
                        levelName    = g.Key,
                        floorCount   = g.Count(),
                        totalAreaSqM = Math.Round(g.Sum(f => f.areaSqM), 2)
                    })
                    .OrderBy(s => s.levelName)
                    .ToList();

                return new JObject
                {
                    ["success"]      = true,
                    ["totalCount"]   = floorList.Count,
                    ["totalAreaSqM"] = Math.Round(totalArea, 2),
                    ["filters"] = new JObject
                    {
                        ["levelName"]   = levelFilter ?? "Tất cả",
                        ["floorType"]   = typeFilter ?? "Tất cả",
                        ["minAreaSqM"]  = minAreaFilter
                    },
                    ["summaryByLevel"] = JArray.FromObject(summaryByLevel),
                    ["floors"]         = JArray.FromObject(floorList)
                };
            }
            catch (Exception ex)
            {
                return new JObject
                {
                    ["success"] = false,
                    ["error"]   = ex.Message,
                    ["tool"]    = ToolName
                };
            }
        }
    }
}
```

### 4.3. Điểm khác biệt then chốt giữa Floor và Room

| Tiêu chí            | Room (SpatialElement)               | Floor                           |
| ------------------- | ----------------------------------- | ------------------------------- |
| Class               | `SpatialElement` → cast `Room`      | `Floor` (trực tiếp)             |
| Category filter     | `OST_Rooms`                         | Không cần (dùng `OfClass`)      |
| Diện tích parameter | `ROOM_AREA`                         | `HOST_AREA_COMPUTED`            |
| Chu vi parameter    | `ROOM_PERIMETER`                    | `HOST_PERIMETER_COMPUTED`       |
| Lấy tầng            | `room.Level` (thuộc tính trực tiếp) | `doc.GetElement(floor.LevelId)` |
| Khi Area = 0        | Phòng chưa đặt (unplaced)           | Sketch bị lỗi                   |
| Độ dày              | Không áp dụng                       | `CompoundStructure.GetWidth()`  |

***

## 5. Triển khai CreateColumnTool

### 5.1. FamilyInstance và cột trong Revit API

Cột trong Revit là một `FamilyInstance` — một phiên bản (instance) được tạo từ một `FamilySymbol` (kiểu cột/loại cột). Để tạo cột, bạn cần:

1. Tìm `FamilySymbol` phù hợp (theo tên family và/hoặc tên type)
2. Kích hoạt symbol nếu chưa được kích hoạt (`symbol.Activate()`)
3. Gọi `doc.Create.NewFamilyInstance()` với tọa độ, symbol, tầng, và `StructuralType`

```
FamilySymbol (kiểu/loại cột)
├── FamilyName    → "Concrete-Rectangular-Column"
├── Name (type)   → "300x300mm", "400x600mm"
└── IsActive      → phải là true trước khi tạo instance

FamilyInstance (cột cụ thể trong model)
├── Location      → XYZ tọa độ đặt cột
├── Symbol        → FamilySymbol
├── Host Level    → tầng đặt cột
└── StructuralType → Column hoặc NonStructural
```

### 5.2. Full C# Code — CreateColumnTool.cs

```csharp
// File: Tools/CreateColumnTool.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Autodesk.Revit.DB;
using Autodesk.Revit.DB.Structure;
using Newtonsoft.Json.Linq;

namespace DeepBIM.RevitMCP.Tools
{
    /// <summary>
    /// Tool tạo cột (Column) trong Revit.
    /// Hỗ trợ cả cột kiến trúc và cột kết cấu.
    /// Tham số bắt buộc: x, y (tọa độ mét), levelName
    /// Tham số tùy chọn: familyName, typeName, isStructural (mặc định: true), heightM
    /// </summary>
    public class CreateColumnTool : IRevitTool
    {
        public string ToolName => "create_column";

        public JObject Execute(Document doc, JObject parameters)
        {
            // Validate và đọc tham số đầu vào
            if (parameters == null)
            {
                return ErrorResult("Thiếu tham số. Cần ít nhất: x, y, levelName");
            }

            double xM          = parameters["x"]?.Value<double>() ?? double.NaN;
            double yM          = parameters["y"]?.Value<double>() ?? double.NaN;
            string levelName   = parameters["levelName"]?.ToString() ?? "";
            string familyName  = parameters["familyName"]?.ToString() ?? "";
            string typeName    = parameters["typeName"]?.ToString() ?? "";
            bool isStructural  = parameters["isStructural"]?.Value<bool>() ?? true;
            double heightM     = parameters["heightM"]?.Value<double>() ?? 0;

            if (double.IsNaN(xM) || double.IsNaN(yM))
                return ErrorResult("Tọa độ x và y phải là số thực hợp lệ");

            if (string.IsNullOrWhiteSpace(levelName))
                return ErrorResult("Thiếu tham số bắt buộc: levelName");

            // Chuyển từ mét sang feet (đơn vị nội bộ Revit)
            const double FeetPerMeter = 3.280839895;
            double xFt = xM * FeetPerMeter;
            double yFt = yM * FeetPerMeter;

            using (Transaction tx = new Transaction(doc, "MCP: Tạo cột mới"))
            {
                try
                {
                    tx.Start();

                    // --- Bước 1: Tìm tầng (Level) theo tên ---
                    Level level = new FilteredElementCollector(doc)
                        .OfClass(typeof(Level))
                        .Cast<Level>()
                        .FirstOrDefault(l => l.Name.Equals(levelName,
                            StringComparison.OrdinalIgnoreCase));

                    if (level == null)
                    {
                        tx.RollBack();

                        // Lấy danh sách tầng có sẵn để gợi ý
                        List<string> availableLevels = new FilteredElementCollector(doc)
                            .OfClass(typeof(Level))
                            .Cast<Level>()
                            .OrderBy(l => l.Elevation)
                            .Select(l => l.Name)
                            .ToList();

                        return new JObject
                        {
                            ["success"]         = false,
                            ["error"]           = $"Không tìm thấy tầng: '{levelName}'",
                            ["availableLevels"] = JArray.FromObject(availableLevels)
                        };
                    }

                    // --- Bước 2: Xác định category cột ---
                    BuiltInCategory columnCategory = isStructural
                        ? BuiltInCategory.OST_StructuralColumns
                        : BuiltInCategory.OST_Columns;

                    // --- Bước 3: Tìm FamilySymbol phù hợp ---
                    FilteredElementCollector symbolCollector = new FilteredElementCollector(doc)
                        .OfClass(typeof(FamilySymbol))
                        .OfCategory(columnCategory);

                    FamilySymbol symbol = null;

                    if (!string.IsNullOrWhiteSpace(familyName) &&
                        !string.IsNullOrWhiteSpace(typeName))
                    {
                        // Tìm chính xác theo cả family lẫn type name
                        symbol = symbolCollector
                            .Cast<FamilySymbol>()
                            .FirstOrDefault(s =>
                                s.FamilyName.IndexOf(familyName,
                                    StringComparison.OrdinalIgnoreCase) >= 0 &&
                                s.Name.IndexOf(typeName,
                                    StringComparison.OrdinalIgnoreCase) >= 0);
                    }
                    else if (!string.IsNullOrWhiteSpace(typeName))
                    {
                        symbol = symbolCollector
                            .Cast<FamilySymbol>()
                            .FirstOrDefault(s =>
                                s.Name.IndexOf(typeName,
                                    StringComparison.OrdinalIgnoreCase) >= 0);
                    }
                    else if (!string.IsNullOrWhiteSpace(familyName))
                    {
                        symbol = symbolCollector
                            .Cast<FamilySymbol>()
                            .FirstOrDefault(s =>
                                s.FamilyName.IndexOf(familyName,
                                    StringComparison.OrdinalIgnoreCase) >= 0);
                    }
                    else
                    {
                        // Lấy symbol đầu tiên tìm được (fallback)
                        symbol = symbolCollector.Cast<FamilySymbol>().FirstOrDefault();
                    }

                    if (symbol == null)
                    {
                        tx.RollBack();
                        return new JObject
                        {
                            ["success"] = false,
                            ["error"]   = "Không tìm thấy FamilySymbol phù hợp cho cột",
                            ["hint"]    = $"isStructural={isStructural}, " +
                                         $"familyName='{familyName}', typeName='{typeName}'"
                        };
                    }

                    // --- Bước 4: Kích hoạt Symbol nếu chưa active ---
                    // Revit yêu cầu symbol phải được activate trước khi tạo instance
                    // Symbol inactive = family đã load nhưng chưa có instance nào trong model
                    if (!symbol.IsActive)
                    {
                        symbol.Activate();
                        doc.Regenerate(); // cần Regenerate để Revit cập nhật trạng thái
                    }

                    // --- Bước 5: Tạo điểm đặt cột ---
                    // Elevation của Level đã là feet (đơn vị nội bộ)
                    XYZ insertionPoint = new XYZ(xFt, yFt, level.Elevation);

                    // --- Bước 6: Tạo FamilyInstance ---
                    StructuralType structType = isStructural
                        ? StructuralType.Column
                        : StructuralType.NonStructural;

                    FamilyInstance column = doc.Create.NewFamilyInstance(
                        insertionPoint,
                        symbol,
                        level,
                        structType);

                    // --- Bước 7: Đặt chiều cao cột (nếu yêu cầu) ---
                    if (heightM > 0)
                    {
                        Parameter topOffset = column.get_Parameter(
                            BuiltInParameter.FAMILY_TOP_LEVEL_OFFSET_PARAM);
                        topOffset?.Set(heightM * FeetPerMeter);
                    }

                    tx.Commit();

                    return new JObject
                    {
                        ["success"]      = true,
                        ["message"]      = $"Đã tạo cột thành công tại ({xM}m, {yM}m) trên {level.Name}",
                        ["elementId"]    = column.Id.IntegerValue,
                        ["familyName"]   = symbol.FamilyName,
                        ["typeName"]     = symbol.Name,
                        ["levelName"]    = level.Name,
                        ["positionX_m"]  = xM,
                        ["positionY_m"]  = yM,
                        ["isStructural"] = isStructural
                    };
                }
                catch (Exception ex)
                {
                    if (tx.GetStatus() == TransactionStatus.Started)
                        tx.RollBack();

                    return new JObject
                    {
                        ["success"] = false,
                        ["error"]   = ex.Message,
                        ["tool"]    = ToolName
                    };
                }
            }
        }

        private JObject ErrorResult(string message) =>
            new JObject { ["success"] = false, ["error"] = message };
    }
}
```

### 5.3. Vòng đời Transaction

```mermaid
sequenceDiagram
    participant Tool as CreateColumnTool
    participant TX as Transaction
    participant DB as Revit Document

    Tool->>TX: new Transaction(doc, "MCP: Tạo cột mới")
    Tool->>TX: tx.Start()
    TX->>DB: Mở transaction, bắt đầu theo dõi thay đổi

    Tool->>DB: Tìm Level theo tên
    Tool->>DB: Tìm FamilySymbol
    Tool->>DB: symbol.Activate() + doc.Regenerate()

    alt Tạo thành công
        Tool->>DB: doc.Create.NewFamilyInstance(...)
        Tool->>TX: tx.Commit()
        TX->>DB: Ghi vĩnh viễn vào model, tạo Undo entry
        Tool-->>Tool: Trả về success = true, elementId
    else Lỗi (Exception xảy ra)
        Tool->>TX: tx.RollBack()
        TX->>DB: Huỷ toàn bộ thay đổi, model không bị ảnh hưởng
        Tool-->>Tool: Trả về success = false, error message
    end
```

> **Quy tắc Transaction bắt buộc:**
>
> * **Luôn** bọc code thay đổi model trong `using (Transaction tx = ...)`.
> * **Luôn** gọi `tx.RollBack()` trong khối `catch`.
> * **Không bao giờ** gọi `tx.Start()` mà không có `Commit()` hoặc `RollBack()`.
> * Transaction tạo ra entry trong lịch sử **Undo** của Revit — người dùng có thể Ctrl+Z để hoàn tác.

***

## 6. Triển khai DeleteElementTool

### 6.1. Nguyên tắc xóa phần tử an toàn

Xóa phần tử trong Revit cần thận trọng vì:

* Một phần tử có thể bị **tham chiếu** bởi phần tử khác (ví dụ: cửa gắn trên tường).
* Revit có thể tự động xóa **các phần tử phụ thuộc** (dependent elements) cùng lúc.
* Phần tử bị **ghim (Pinned)** không thể xóa trực tiếp — phải bỏ ghim trước.
* Một số phần tử hệ thống (Level, Grid) không thể xóa qua API.

### 6.2. Full C# Code — DeleteElementTool.cs

```csharp
// File: Tools/DeleteElementTool.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Autodesk.Revit.DB;
using Newtonsoft.Json.Linq;

namespace DeepBIM.RevitMCP.Tools
{
    /// <summary>
    /// Tool xóa một hoặc nhiều phần tử khỏi Revit model.
    /// Hỗ trợ: xóa theo elementId đơn lẻ hoặc mảng elementIds.
    /// Tự động báo cáo danh sách phần tử phụ thuộc bị xóa kèm theo.
    /// Kiểm tra trạng thái Pinned trước khi xóa.
    /// </summary>
    public class DeleteElementTool : IRevitTool
    {
        public string ToolName => "delete_element";

        public JObject Execute(Document doc, JObject parameters)
        {
            // Hỗ trợ cả single ID và array of IDs
            List<int> idsToDelete = new List<int>();

            if (parameters?["elementId"] != null)
            {
                idsToDelete.Add(parameters["elementId"].Value<int>());
            }

            if (parameters?["elementIds"] is JArray idArray)
            {
                idsToDelete.AddRange(idArray.Select(t => t.Value<int>()));
            }

            if (idsToDelete.Count == 0)
            {
                return new JObject
                {
                    ["success"] = false,
                    ["error"]   = "Thiếu tham số: elementId (int) hoặc elementIds (array of int)"
                };
            }

            // Bước kiểm tra trước: xác nhận element tồn tại và có thể xóa
            var validElements    = new List<ElementId>();
            var notFoundIds      = new List<int>();
            var pinnedIds        = new List<int>();
            var elementInfoBefore = new List<object>();

            foreach (int id in idsToDelete)
            {
                ElementId elemId = new ElementId(id);
                Element elem = doc.GetElement(elemId);

                if (elem == null)
                {
                    notFoundIds.Add(id);
                    continue;
                }

                // Kiểm tra phần tử bị ghim
                if (elem.Pinned)
                {
                    pinnedIds.Add(id);
                    continue;
                }

                validElements.Add(elemId);

                // Lưu thông tin trước khi xóa để báo cáo
                elementInfoBefore.Add(new
                {
                    id       = id,
                    name     = elem.Name ?? "(Không tên)",
                    category = elem.Category?.Name ?? "(Không có danh mục)"
                });
            }

            // Báo cáo sớm nếu không có gì để xóa
            if (validElements.Count == 0)
            {
                return new JObject
                {
                    ["success"]    = false,
                    ["error"]      = "Không có phần tử hợp lệ nào để xóa",
                    ["notFound"]   = JArray.FromObject(notFoundIds),
                    ["pinned"]     = JArray.FromObject(pinnedIds),
                    ["hint"]       = pinnedIds.Count > 0
                                        ? "Một số phần tử đang bị ghim (Pinned). Bỏ ghim trước khi xóa."
                                        : ""
                };
            }

            using (Transaction tx = new Transaction(doc, "MCP: Xóa phần tử"))
            {
                try
                {
                    tx.Start();

                    // doc.Delete() trả về ICollection<ElementId> gồm TẤT CẢ phần tử bị xóa
                    // bao gồm cả dependent elements (phần tử phụ thuộc tự động xóa theo)
                    ICollection<ElementId> deletedIds = doc.Delete(validElements);

                    tx.Commit();

                    // Phân biệt: phần tử được yêu cầu xóa vs phần tử bị xóa tự động kèm theo
                    var requestedDeleted = deletedIds
                        .Where(d => validElements.Any(v => v.IntegerValue == d.IntegerValue))
                        .Select(d => d.IntegerValue)
                        .ToList();

                    var dependentDeleted = deletedIds
                        .Where(d => !validElements.Any(v => v.IntegerValue == d.IntegerValue))
                        .Select(d => d.IntegerValue)
                        .ToList();

                    string summary = $"Đã xóa {requestedDeleted.Count} phần tử" +
                                     (dependentDeleted.Count > 0
                                         ? $" cùng với {dependentDeleted.Count} phần tử phụ thuộc"
                                         : "");

                    return new JObject
                    {
                        ["success"]          = true,
                        ["message"]          = summary,
                        ["totalDeleted"]     = deletedIds.Count,
                        ["requestedDeleted"] = JArray.FromObject(requestedDeleted),
                        ["dependentDeleted"] = JArray.FromObject(dependentDeleted),
                        ["notFoundIds"]      = JArray.FromObject(notFoundIds),
                        ["pinnedIds"]        = JArray.FromObject(pinnedIds),
                        ["elementsInfo"]     = JArray.FromObject(elementInfoBefore)
                    };
                }
                catch (Autodesk.Revit.Exceptions.ArgumentException argEx)
                {
                    if (tx.GetStatus() == TransactionStatus.Started)
                        tx.RollBack();

                    return new JObject
                    {
                        ["success"] = false,
                        ["error"]   = $"Lỗi tham số khi xóa: {argEx.Message}",
                        ["hint"]    = "Một số phần tử hệ thống không thể xóa (Level, Grid, View...)"
                    };
                }
                catch (Exception ex)
                {
                    if (tx.GetStatus() == TransactionStatus.Started)
                        tx.RollBack();

                    return new JObject
                    {
                        ["success"] = false,
                        ["error"]   = ex.Message,
                        ["tool"]    = ToolName
                    };
                }
            }
        }
    }
}
```

### 6.3. Cây quyết định khi xử lý yêu cầu xóa

```mermaid
flowchart TD
    A[AI yêu cầu\nxóa element ID] --> B{ElementId\ntồn tại?}
    B -- Không --> C[Trả về lỗi\nnot found]
    B -- Có --> D{Element bị\nghim Pinned?}
    D -- Có --> E[Trả về lỗi\nyêu cầu bỏ ghim trước]
    D -- Không --> F[Mở Transaction\ntx.Start]
    F --> G[doc.Delete elementIds]
    G --> H{Commit\nthành công?}
    H -- Có --> I[Báo cáo:\nđã xóa N phần tử\n+ M phần tử phụ thuộc]
    H -- Không --> J[tx.RollBack\nModel không đổi\nTrả về error]

    style C fill:#fee2e2,stroke:#dc2626
    style E fill:#fee2e2,stroke:#dc2626
    style J fill:#fee2e2,stroke:#dc2626
    style I fill:#dcfce7,stroke:#16a34a
```

***

## 7. Đăng ký Tools mới trong MCP Server (TypeScript)

### 7.1. Cấu trúc thư mục MCP Server

```
mcp-server/
├── src/
│   ├── index.ts              ← Entry point, khởi tạo MCP server
│   ├── server.ts             ← Đăng ký tất cả tool handlers
│   ├── revitClient.ts        ← HTTP client giao tiếp với Revit plugin
│   └── tools/
│       ├── wallTools.ts      ← get_walls, create_wall (đã có từ bài trước)
│       ├── roomTools.ts      ← get_rooms  ← MỚI bài này
│       ├── floorTools.ts     ← get_floors ← MỚI bài này
│       ├── columnTools.ts    ← create_column ← MỚI bài này
│       └── deleteTools.ts    ← delete_element ← MỚI bài này
├── package.json
└── tsconfig.json
```

### 7.2. Định nghĩa Tool — roomTools.ts

```typescript
// File: src/tools/roomTools.ts
import { Tool } from "@modelcontextprotocol/sdk/types.js";
import { RevitClient } from "../revitClient.js";

// Schema định nghĩa tool get_rooms
// Description là thứ AI đọc để quyết định khi nào gọi tool này
export const getRoomsTool: Tool = {
  name: "get_rooms",
  description:
    "Lấy danh sách tất cả phòng (Room) trong Revit model. " +
    "Trả về tên phòng, số phòng, diện tích (m²), chu vi (m), " +
    "thể tích (m³), chiều cao (m) và tên tầng. " +
    "Bao gồm thống kê tổng diện tích và số phòng theo từng tầng. " +
    "Chỉ trả về phòng đã được bao kín bởi tường (diện tích > 0). " +
    "Có thể lọc theo tên tầng hoặc từ khóa trong tên phòng.",
  inputSchema: {
    type: "object",
    properties: {
      levelName: {
        type: "string",
        description:
          "Tên tầng cần lọc (tùy chọn). Ví dụ: 'Level 1', 'Tầng trệt'. " +
          "Bỏ trống để lấy tất cả tầng.",
      },
      nameContains: {
        type: "string",
        description:
          "Từ khóa trong tên phòng (tùy chọn). Ví dụ: 'ngủ' để tìm tất cả phòng ngủ.",
      },
    },
    required: [],
  },
};

export async function handleGetRooms(
  args: Record<string, unknown>,
  client: RevitClient
): Promise<string> {
  const payload = {
    tool: "get_rooms",
    parameters: {
      levelName:    (args.levelName as string)    || null,
      nameContains: (args.nameContains as string) || null,
    },
  };

  const result = await client.callRevitTool(payload);

  if (!result.success) {
    return `Lỗi khi lấy danh sách phòng: ${result.error}`;
  }

  const rooms = result.rooms as Array<{
    id: number;
    name: string;
    number: string;
    levelName: string;
    areaSqM: number;
    perimeterM: number;
    heightM: number;
  }>;

  if (rooms.length === 0) {
    return (
      `Không tìm thấy phòng nào` +
      (args.levelName ? ` trên tầng '${args.levelName}'` : "") +
      (args.nameContains ? ` có tên chứa '${args.nameContains}'` : "") +
      `.`
    );
  }

  const header =
    `Tìm thấy **${result.totalCount} phòng** ` +
    `(tổng diện tích: **${result.totalAreaSqM} m²**)\n\n`;

  const table =
    `| ID | Số | Tên phòng | Tầng | Diện tích (m²) | Chu vi (m) |\n` +
    `|----|----|-----------|------|----------------|------------|\n` +
    rooms
      .map(
        (r) =>
          `| ${r.id} | ${r.number} | ${r.name} | ${r.levelName} | ${r.areaSqM} | ${r.perimeterM} |`
      )
      .join("\n");

  return header + table;
}
```

### 7.3. Định nghĩa Tool — columnTools.ts

```typescript
// File: src/tools/columnTools.ts
import { Tool } from "@modelcontextprotocol/sdk/types.js";
import { RevitClient } from "../revitClient.js";

export const createColumnTool: Tool = {
  name: "create_column",
  description:
    "Tạo một cột mới (Column) trong Revit tại tọa độ chỉ định. " +
    "Hỗ trợ cả cột kiến trúc và cột kết cấu (structural). " +
    "Tọa độ x, y tính bằng MÉT theo hệ tọa độ project của Revit. " +
    "Cần biết tên tầng chính xác — dùng get_levels nếu chưa biết tên tầng. " +
    "Trả về ElementId của cột vừa tạo.",
  inputSchema: {
    type: "object",
    properties: {
      x: {
        type: "number",
        description: "Tọa độ X đặt cột, tính bằng MÉT (m)",
      },
      y: {
        type: "number",
        description: "Tọa độ Y đặt cột, tính bằng MÉT (m)",
      },
      levelName: {
        type: "string",
        description:
          "Tên tầng đặt chân cột. Phải khớp chính xác với tên Level trong Revit. " +
          "Ví dụ: 'Level 1', 'Ground Floor', 'Tầng 1'",
      },
      familyName: {
        type: "string",
        description:
          "Tên family cột (tùy chọn). Ví dụ: 'Concrete-Rectangular-Column'. " +
          "Bỏ trống để dùng family đầu tiên tìm được.",
      },
      typeName: {
        type: "string",
        description:
          "Tên type/kích thước cột (tùy chọn). Ví dụ: '300x300mm', '400x400mm'. " +
          "Bỏ trống để dùng type đầu tiên tìm được.",
      },
      isStructural: {
        type: "boolean",
        description:
          "true = cột kết cấu (Structural Column, tính tải), " +
          "false = cột kiến trúc (Architectural Column, chỉ trang trí). " +
          "Mặc định: true",
      },
      heightM: {
        type: "number",
        description:
          "Chiều cao cột tính bằng mét (tùy chọn). " +
          "Nếu không cung cấp, cột sẽ có chiều cao theo tầng.",
      },
    },
    required: ["x", "y", "levelName"],
  },
};

export async function handleCreateColumn(
  args: Record<string, unknown>,
  client: RevitClient
): Promise<string> {
  if (typeof args.x !== "number" || typeof args.y !== "number") {
    return "Lỗi: Tọa độ x và y phải là số thực (number). Ví dụ: x=5.0, y=3.0";
  }

  const payload = {
    tool: "create_column",
    parameters: {
      x:            args.x as number,
      y:            args.y as number,
      levelName:    args.levelName as string,
      familyName:   (args.familyName as string)  || "",
      typeName:     (args.typeName as string)     || "",
      isStructural: args.isStructural !== undefined
                      ? (args.isStructural as boolean)
                      : true,
      heightM:      (args.heightM as number) || 0,
    },
  };

  const result = await client.callRevitTool(payload);

  if (!result.success) {
    let message = `Không thể tạo cột: ${result.error}`;
    if (result.hint)       message += `\nGợi ý: ${result.hint}`;
    if (result.availableLevels) {
      message += `\nCác tầng có sẵn: ${(result.availableLevels as string[]).join(", ")}`;
    }
    return message;
  }

  return (
    `Đã tạo cột thành công!\n` +
    `  ElementId: **${result.elementId}**\n` +
    `  Family: ${result.familyName} — Type: ${result.typeName}\n` +
    `  Vị trí: (${result.positionX_m}m, ${result.positionY_m}m)\n` +
    `  Tầng: ${result.levelName}\n` +
    `  Loại: ${result.isStructural ? "Cột kết cấu (Structural)" : "Cột kiến trúc"}`
  );
}
```

### 7.4. Đăng ký tất cả tools trong server.ts

```typescript
// File: src/server.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { RevitClient } from "./revitClient.js";

import { getWallsTool,     handleGetWalls }     from "./tools/wallTools.js";
import { createWallTool,   handleCreateWall }   from "./tools/wallTools.js";
import { getRoomsTool,     handleGetRooms }     from "./tools/roomTools.js";
import { getFloorsTool,    handleGetFloors }    from "./tools/floorTools.js";
import { createColumnTool, handleCreateColumn } from "./tools/columnTools.js";
import { deleteElementTool, handleDeleteElement } from "./tools/deleteTools.js";

export function registerAllTools(
  server: McpServer,
  client: RevitClient
): void {
  // ── Nhóm Read-Only ──────────────────────────────────────────
  server.tool(
    getWallsTool.name,
    getWallsTool.description,
    getWallsTool.inputSchema.properties as Record<string, unknown>,
    async ({ levelName }) => ({
      content: [{
        type: "text",
        text: await handleGetWalls({ levelName }, client),
      }],
    })
  );

  server.tool(
    getRoomsTool.name,
    getRoomsTool.description,
    getRoomsTool.inputSchema.properties as Record<string, unknown>,
    async ({ levelName, nameContains }) => ({
      content: [{
        type: "text",
        text: await handleGetRooms({ levelName, nameContains }, client),
      }],
    })
  );

  server.tool(
    getFloorsTool.name,
    getFloorsTool.description,
    getFloorsTool.inputSchema.properties as Record<string, unknown>,
    async ({ levelName, floorType, minAreaSqM }) => ({
      content: [{
        type: "text",
        text: await handleGetFloors({ levelName, floorType, minAreaSqM }, client),
      }],
    })
  );

  // ── Nhóm Write / Modify ────────────────────────────────────
  server.tool(
    createWallTool.name,
    createWallTool.description,
    createWallTool.inputSchema.properties as Record<string, unknown>,
    async (args) => ({
      content: [{
        type: "text",
        text: await handleCreateWall(args, client),
      }],
    })
  );

  server.tool(
    createColumnTool.name,
    createColumnTool.description,
    createColumnTool.inputSchema.properties as Record<string, unknown>,
    async (args) => ({
      content: [{
        type: "text",
        text: await handleCreateColumn(args, client),
      }],
    })
  );

  server.tool(
    deleteElementTool.name,
    deleteElementTool.description,
    deleteElementTool.inputSchema.properties as Record<string, unknown>,
    async (args) => ({
      content: [{
        type: "text",
        text: await handleDeleteElement(args, client),
      }],
    })
  );

  console.log(
    `[MCP Server] Đã đăng ký thành công ${6} tools: ` +
    `get_walls, get_rooms, get_floors, ` +
    `create_wall, create_column, delete_element`
  );
}
```

***

## 8. Prompt Engineering cho BIM

Prompt Engineering cho BIM khác với prompt thông thường. AI cần làm việc với dữ liệu có cấu trúc chặt chẽ (tọa độ, ID, tên tầng, kiểu phần tử) và thực thi thao tác có tính **không thể hoàn tác** từ phía MCP (trừ khi người dùng dùng Ctrl+Z trong Revit).

### 8.1. Các nguyên tắc cốt lõi

```mermaid
mindmap
  root((Prompt BIM\nHiệu quả))
    Rõ ràng về tọa độ
      Luôn chỉ định đơn vị m hay mm
      Dùng hệ tọa độ tuyệt đối khi có thể
      Tránh mô tả mơ hồ như "gần tường"
    Xác nhận trước khi thay đổi
      Đọc dữ liệu trước khi create/delete
      Xác nhận tên tầng và loại family
      Tóm tắt kế hoạch trước khi thực thi
    Xử lý lỗi gracefully
      Đọc error message từ tool
      Đề xuất giải pháp thay thế
      Không tiếp tục khi tool thất bại
    Ngữ cảnh BIM đầy đủ
      Chỉ rõ tầng nào
      Kết cấu hay kiến trúc
      Quan hệ giữa các phần tử
```

### 8.2. Bảng so sánh Prompt Kém vs Prompt Tốt

| Tình huống        | Prompt kém            | Prompt tốt                                                                          |
| ----------------- | --------------------- | ----------------------------------------------------------------------------------- |
| Tạo cột           | "Tạo một cột"         | "Tạo cột kết cấu 300x300mm tại tọa độ (5.0m, 3.0m) trên Level 1"                    |
| Xóa phần tử       | "Xóa cái tường đó"    | "Gọi get\_walls trước, sau đó xóa tường có ID 28503"                                |
| Tính diện tích    | "Tính tổng diện tích" | "Gọi get\_rooms, lọc theo Level 1, rồi tính và báo cáo tổng diện tích"              |
| Tác vụ nhiều bước | "Làm tất cả"          | "Bước 1: lấy danh sách tầng. Bước 2: lấy phòng từng tầng. Bước 3: tổng hợp báo cáo" |
| Tạo lưới cột      | "Đặt cột theo lưới"   | "Tạo lưới cột 3×3, khoảng cách 5m, góc đầu tiên tại (0,0), trên Level 1"            |

### 8.3. System Prompt mẫu cho BIM Agent

```
Bạn là BIM AI Assistant, chuyên hỗ trợ làm việc với mô hình Revit thông qua MCP.

QUY TẮC BẮT BUỘC:
1. Trước khi tạo hoặc xóa bất kỳ phần tử nào, PHẢI gọi tool đọc tương ứng
   (get_walls, get_rooms, get_floors) để xác nhận ngữ cảnh hiện tại.
2. Tất cả tọa độ nhập vào tools là đơn vị MÉT (m).
   Khi người dùng cung cấp mm, tự động chuyển đổi và thông báo.
3. Khi tool trả về lỗi, đọc error message và đề xuất giải pháp —
   không im lặng bỏ qua lỗi.
4. Với thao tác xóa: luôn liệt kê tên và ID phần tử sẽ bị xóa,
   yêu cầu người dùng xác nhận trước khi gọi delete_element.
5. Khi thực hiện nhiều bước, tóm tắt kế hoạch trước khi bắt đầu.

ĐỊNH DẠNG BÁO CÁO:
- Dùng bảng markdown khi trình bày danh sách phần tử
- Làm tròn số đến 2 chữ số thập phân
- Ghi rõ đơn vị (m, m², mm) bên cạnh mọi số liệu
- Nếu có cảnh báo về phần tử phụ thuộc bị xóa, nêu rõ
```

### 8.4. Patterns Prompt nâng cao

**Pattern 1 — Confirm Before Modify (Xác nhận trước khi thay đổi):**

```
Trước khi tạo 4 cột tại các góc phòng khách, hãy:
1. Gọi get_rooms để xác nhận vị trí và kích thước phòng khách
2. Trình bày cho tôi: tọa độ 4 góc phòng dự kiến, loại cột sẽ dùng
3. Chờ tôi xác nhận "đồng ý" trước khi thực sự gọi create_column
```

**Pattern 2 — Error Recovery (Phục hồi khi có lỗi):**

```
Tạo lưới cột 3×3 trên Level 1. Nếu create_column thất bại với một cột:
- Ghi nhận cột đó bị lỗi và tiếp tục với cột tiếp theo
- Không dừng toàn bộ quy trình
- Cuối cùng báo cáo: thành công X cột, thất bại Y cột kèm lý do
```

**Pattern 3 — Aggregate Analysis (Phân tích tổng hợp):**

```
Phân tích toàn bộ mô hình:
1. Dùng get_rooms để lấy tất cả phòng
2. Dùng get_floors để lấy tất cả sàn
3. Tạo báo cáo so sánh: diện tích phòng vs diện tích sàn theo từng tầng,
   phòng lớn nhất và nhỏ nhất, tỷ lệ sử dụng diện tích
```

**Pattern 4 — Safe Cleanup (Dọn dẹp an toàn):**

```
Tìm và liệt kê tất cả sàn có diện tích nhỏ hơn 0.5 m² —
đây có thể là sàn lỗi hoặc rác trong model.
KHÔNG xóa ngay — chỉ trình bày danh sách cho tôi xem,
bao gồm ID và tầng của mỗi sàn.
Tôi sẽ quyết định xóa cái nào.
```

***

## 9. Ví dụ nâng cao: Multi-tool Workflow

### 9.1. Ví dụ 1 — Báo cáo tổng diện tích phòng theo tầng

**Prompt người dùng:**

```
Tính tổng diện tích các phòng theo từng tầng. Tầng nào có tổng diện tích lớn nhất?
Tầng nào có diện tích phòng trung bình cao nhất?
```

**Sequence diagram của workflow:**

```mermaid
sequenceDiagram
    actor User as Người dùng
    participant AI as Claude AI
    participant MCP as MCP Server
    participant Revit as Revit Plugin

    User->>AI: "Tính tổng diện tích phòng theo tầng"

    Note over AI: Kế hoạch:<br/>1. get_rooms (tất cả tầng)<br/>2. Nhóm và tính toán<br/>3. Báo cáo

    AI->>MCP: tool_call: get_rooms {}
    MCP->>Revit: POST /api/tool { tool: "get_rooms" }
    Revit-->>MCP: { rooms: [...32 phòng...], totalAreaSqM: 2851 }
    MCP-->>AI: Danh sách 32 phòng với summaryByLevel

    Note over AI: Nhóm phòng theo tầng<br/>Tính tổng DT, trung bình<br/>Xác định tầng lớn nhất

    AI-->>User: Báo cáo bảng markdown
```

**Kết quả mẫu AI trả về:**

```markdown
## Báo cáo Diện tích Phòng theo Tầng

| Tầng    | Số phòng | Tổng DT (m²) | DT trung bình (m²/phòng) |
|---------|----------|--------------|--------------------------|
| Level 1 | 12       | 980.50       | 81.71                    |
| Level 2 | 11       | 960.30       | 87.30                    |
| Level 3 | 9        | 910.20       | 101.13                   |
| **Tổng**| **32**   | **2851.00**  | **89.09**                |

**Tầng có tổng diện tích lớn nhất:** Level 1 (980.50 m²)
**Tầng có diện tích phòng trung bình lớn nhất:** Level 3 (101.13 m²/phòng)
```

***

### 9.2. Ví dụ 2 — Tạo lưới cột tự động

**Prompt người dùng:**

```
Tạo lưới cột 3×3 (9 cột) trên Level 1, khoảng cách 5m,
góc đầu tiên tại tọa độ (0, 0). Dùng cột kết cấu.
```

**Sequence diagram:**

```mermaid
sequenceDiagram
    actor User as Người dùng
    participant AI as Claude AI
    participant MCP as MCP Server
    participant Revit as Revit Plugin

    User->>AI: "Tạo lưới cột 3×3 trên Level 1, khoảng cách 5m"

    Note over AI: Tính toán 9 tọa độ:<br/>(0,0) (5,0) (10,0)<br/>(0,5) (5,5) (10,5)<br/>(0,10) (5,10) (10,10)

    loop 9 lần — mỗi vị trí một cột
        AI->>MCP: tool_call: create_column { x, y, levelName: "Level 1" }
        MCP->>Revit: POST /api/tool { tool: "create_column" }
        Revit-->>MCP: { success: true, elementId: XXXX }
        MCP-->>AI: Cột tại (x,y) đã tạo
    end

    Note over AI: Tổng hợp: 9/9 thành công

    AI-->>User: "Đã tạo 9 cột thành công\nDanh sách ElementId: [...]"
```

**Bảng kết quả mẫu:**

```markdown
## Kết quả tạo lưới cột 3×3

| Cột | X (m) | Y (m) | ElementId | Trạng thái  |
|-----|-------|-------|-----------|-------------|
| 1   | 0.0   | 0.0   | 30001     | Thành công  |
| 2   | 5.0   | 0.0   | 30002     | Thành công  |
| 3   | 10.0  | 0.0   | 30003     | Thành công  |
| 4   | 0.0   | 5.0   | 30004     | Thành công  |
| 5   | 5.0   | 5.0   | 30005     | Thành công  |
| 6   | 10.0  | 5.0   | 30006     | Thành công  |
| 7   | 0.0   | 10.0  | 30007     | Thành công  |
| 8   | 5.0   | 10.0  | 30008     | Thành công  |
| 9   | 10.0  | 10.0  | 30009     | Thành công  |

Tổng cộng: **9/9 cột** tạo thành công trên Level 1.
```

***

### 9.3. Ví dụ 3 — Phân tích và dọn dẹp sàn lỗi

**Prompt người dùng:**

```
Tìm tất cả sàn có diện tích nhỏ hơn 1 m² — đây có thể là sàn lỗi trong model.
Liệt kê chúng cho tôi xem, rồi hỏi tôi có muốn xóa không.
```

**Workflow tổng hợp:**

```mermaid
flowchart LR
    A[get_floors\nminAreaSqM=0] --> B{Lọc: area < 1 m²}
    B -- Không có sàn lỗi --> C[Báo cáo:\nModel sạch không có sàn lỗi]
    B -- Có N sàn nghi lỗi --> D[Trình bày bảng\ndanh sách cho người dùng]
    D --> E{Người dùng\nxác nhận xóa?}
    E -- Không --> F[Kết thúc\nkhông thay đổi model]
    E -- Có danh sách ID cụ thể --> G[delete_element\nelementIds là danh sách ID]
    G --> H[Báo cáo kết quả\nđã xóa N sàn]

    style C fill:#dcfce7,stroke:#16a34a
    style F fill:#fef9c3,stroke:#eab308
    style H fill:#dcfce7,stroke:#16a34a
```

***

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

<details>

<summary><strong>Câu 1:</strong> Tại sao <code>GetRoomsTool</code> cần lọc <code>r.Area > 0</code>? Điều gì xảy ra nếu bỏ qua điều kiện này?</summary>

**Trả lời:**

Trong Revit, một `Room` object có thể tồn tại trong database ở trạng thái **chưa đặt vào model** (unplaced room). Những phòng này được tạo ra trong Room Schedule nhưng chưa được vẽ vào sàn tầng — chúng có `Area = 0` và `Location = null`.

Nếu bỏ điều kiện `r.Area > 0`, bạn sẽ gặp hai vấn đề ngay lập tức:

1. `loc = room.Location as LocationPoint` trả về `null`, gây `NullReferenceException` khi truy cập `loc.Point.X`.
2. Kết quả trả về AI sẽ chứa các phòng "ảo" có diện tích 0, khiến AI báo cáo sai số lượng phòng và tính sai tổng diện tích.

Ngoài ra còn có trạng thái **unbounded room** — phòng đã đặt nhưng tường bao quanh không kín (có lỗ hổng), dẫn đến diện tích cũng bằng 0. Điều kiện `r.Area > 0` lọc bỏ cả hai trường hợp này.

</details>

<details>

<summary><strong>Câu 2:</strong> Vì sao cần gọi <code>symbol.Activate()</code> trước khi tạo FamilyInstance? Khi nào symbol chưa được kích hoạt?</summary>

**Trả lời:**

Trong Revit, một `FamilySymbol` (kiểu family) có thể tồn tại trong project nhưng ở trạng thái **inactive**. Điều này xảy ra khi:

1. Family được load vào project nhưng chưa có bất kỳ instance nào được đặt vào model.
2. Family được load thông qua project template mà chưa thực sự sử dụng.

Nếu gọi `doc.Create.NewFamilyInstance()` với symbol inactive, Revit ném `InvalidOperationException` với thông báo: *"The FamilySymbol must be activated before using it."*

Cách kiểm tra và kích hoạt đúng:

```csharp
if (!symbol.IsActive)
{
    symbol.Activate();
    doc.Regenerate(); // Bắt buộc: Revit cần Regenerate để cập nhật trạng thái nội bộ
}
```

Quan trọng: cả `Activate()` lẫn `doc.Regenerate()` đều phải nằm **bên trong** Transaction đang mở.

</details>

<details>

<summary><strong>Câu 3:</strong> Khi <code>doc.Delete(elementIds)</code> trả về nhiều ID hơn số ID bạn yêu cầu, những phần tử dư là gì?</summary>

**Trả lời:**

`doc.Delete()` trả về `ICollection<ElementId>` chứa **tất cả** phần tử bị xóa, bao gồm:

1. **Phần tử bạn yêu cầu xóa** — các ID bạn truyền vào.
2. **Dependent elements** — những phần tử không thể tồn tại độc lập sau khi phần tử chủ bị xóa.

Ví dụ dependent elements thường gặp:

* Xóa **tường** → tự động xóa **cửa/cửa sổ** gắn trên tường đó
* Xóa **sàn** → tự động xóa **Room Separation Lines** nằm trên sàn
* Xóa **tầng (Level)** → tự động xóa **các view mặt bằng** gắn với tầng đó
* Xóa **cột kết cấu** → tự động xóa **tag/annotation** của cột

Trong `DeleteElementTool`, chúng ta phân biệt rõ hai nhóm trong response: `requestedDeleted` và `dependentDeleted`. Thông tin này rất quan trọng để AI báo cáo trung thực, tránh model thay đổi ngoài ý muốn mà người dùng không biết.

</details>

<details>

<summary><strong>Câu 4:</strong> Tại sao tool <code>description</code> trong TypeScript quan trọng với AI? Mô tả kém có hậu quả gì?</summary>

**Trả lời:**

`description` trong `Tool` schema là **nguồn thông tin duy nhất** AI có để quyết định:

1. **Khi nào** nên gọi tool này thay vì tool khác
2. **Tham số nào** bắt buộc, tùy chọn, giá trị mặc định
3. **Đơn vị** của các tham số số (mét, mm, feet?)
4. **Kết quả** trả về có dạng gì

Hậu quả cụ thể khi description kém:

* AI gọi `create_wall` khi người dùng muốn `create_column` (cả hai đều "tạo phần tử")
* AI truyền tọa độ bằng mm thay vì mét (vì description không ghi rõ đơn vị)
* AI không biết `levelName` là bắt buộc và bỏ qua, gây lỗi ở tầng C#
* AI gọi lại tool nhiều lần không cần thiết vì không hiểu kết quả trả về

**Công thức viết description hiệu quả:**

```
[Động từ hành động] [đối tượng cụ thể] trong Revit model.
Trả về: [mô tả rõ kết quả].
[Đơn vị của tham số].
[Điều kiện/yêu cầu đặc biệt nếu có].
```

</details>

<details>

<summary><strong>Câu 5:</strong> Nếu muốn thêm <code>MoveElementTool</code> (di chuyển phần tử đến vị trí mới), bạn cần thay đổi những gì ở cả hai phía C# và TypeScript?</summary>

**Trả lời:**

**Phía C# (Revit Plugin) — khoảng 50 dòng:**

1. Tạo file `Tools/MoveElementTool.cs` implement `IRevitTool`
2. Trong `Execute()`: đọc `elementId`, `deltaX`, `deltaY`, `deltaZ` từ parameters
3. Dùng `ElementTransformUtils.MoveElement(doc, elementId, translationVector)` bọc trong `Transaction`
4. Đăng ký vào router: thêm case `"move_element"` vào switch/dictionary routing

**Phía TypeScript (MCP Server) — khoảng 40 dòng:**

1. Tạo file `src/tools/moveTools.ts` với `moveElementTool: Tool` và `handleMoveElement`
2. Schema: `elementId` (int, bắt buộc), `deltaX`, `deltaY`, `deltaZ` (number mét, bắt buộc)
3. Import và đăng ký trong `server.ts` qua `server.tool(moveElementTool.name, ...)`

**Tổng cộng:** \~90 dòng code mới. Đây là sức mạnh của pattern tool modular — mỗi tool mới hoàn toàn độc lập, không ảnh hưởng đến bất kỳ tool nào đang hoạt động.

</details>

***

## 11. Tổng kết

### Những gì bạn đã xây dựng trong Bài 8

```mermaid
graph TD
    B8[Bài 8: Mở rộng Tool Ecosystem] --> G1[GetRoomsTool]
    B8 --> G2[GetFloorsTool]
    B8 --> G3[CreateColumnTool]
    B8 --> G4[DeleteElementTool]
    B8 --> G5[TypeScript Registration]
    B8 --> G6[Prompt Engineering]
    B8 --> G7[Multi-tool Workflows]

    G1 --> R1["SpatialElement, Area, Perimeter\nChuyển đổi ft² → m²"]
    G2 --> R2["HOST_AREA_COMPUTED\nCompoundStructure, GroupBy Level"]
    G3 --> R3["FamilyInstance, FamilySymbol\nsymbol.Activate(), Transaction"]
    G4 --> R4["doc.Delete(), Dependent elements\nPinned check, Safe rollback"]
    G5 --> R5["Tool schema, Handler function\nserver.tool() registration"]
    G6 --> R6["System prompt chuẩn\nConfirm, Error Recovery patterns"]
    G7 --> R7["Báo cáo DT phòng\nLưới cột, Dọn model lỗi"]
```

### Bảng tổng kết kỹ thuật

| Tool              | Revit API Class           | Parameter quan trọng                        | Cần Transaction? |
| ----------------- | ------------------------- | ------------------------------------------- | ---------------- |
| GetRoomsTool      | `SpatialElement` → `Room` | `ROOM_AREA`, `ROOM_PERIMETER`               | Không            |
| GetFloorsTool     | `Floor`                   | `HOST_AREA_COMPUTED`, `CompoundStructure`   | Không            |
| CreateColumnTool  | `FamilyInstance`          | `FamilySymbol.Activate()`, `StructuralType` | **Có**           |
| DeleteElementTool | `Document.Delete()`       | `ElementId`, Pinned check                   | **Có**           |

### Quy trình chuẩn khi viết một tool mới

```mermaid
flowchart LR
    A["1. Xác định\nyêu cầu"] --> B["2. Viết code C#\ntrong Revit Plugin"]
    B --> C["3. Thêm route\ntrong HTTP Listener"]
    C --> D["4. Định nghĩa schema\nvà handler TypeScript"]
    D --> E["5. Đăng ký trong\nserver.ts"]
    E --> F["6. Viết description\nrõ ràng cho AI"]
    F --> G["7. Test với\nprompt thực tế"]

    style A fill:#e8eaf6,stroke:#3949ab
    style B fill:#fff3e0,stroke:#e65100
    style C fill:#fff3e0,stroke:#e65100
    style D fill:#e3f2fd,stroke:#1565c0
    style E fill:#e3f2fd,stroke:#1565c0
    style F fill:#e8f5e9,stroke:#2e7d32
    style G fill:#fce4ec,stroke:#c62828
```

### Năm nguyên tắc vàng khi viết Revit Tool

1. **Một tool — một nhiệm vụ:** Mỗi tool chỉ nên thực hiện một việc duy nhất và làm tốt việc đó.
2. **Description là tất cả:** AI hiểu tool thông qua description — hãy viết thật rõ ràng về mục đích, tham số, đơn vị và kết quả.
3. **Chuyển đổi đơn vị nhất quán:** Luôn nhận tọa độ bằng mét từ AI, chuyển sang feet khi gọi Revit API, trả về mét trong JSON response.
4. **Xử lý lỗi có ý nghĩa:** Mọi trường hợp lỗi đều phải trả về `success: false` với `error` message rõ ràng và `hint` gợi ý giải pháp khi có thể.
5. **Transaction cho mọi thay đổi:** Mọi thao tác ghi đều cần `Transaction` với `try/catch/rollback` đầy đủ — đây là điều kiện bắt buộc, không phải tùy chọn.

***

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

| Tài liệu                                                                                                      | Mô tả                                                      |
| ------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- |
| [deepbim-revit-mcp-plugin](https://github.com/nguyenngocdue/deepbim-revit-mcp-plugin)                         | Source code tham khảo chính của khóa học                   |
| [revit-mcp-server](https://github.com/nguyenngocdue/revit-mcp-server)                                         | Tham chiếu thực tế cho phần tạo tool và tổ chức MCP Server |
| [Revit API Docs — SpatialElement](https://www.revitapidocs.com/2024/ed4d1e5d-4258-7fe0-bea1-bdd93ca0e4d7.htm) | Tài liệu chính thức về Room và SpatialElement              |
| [Revit API Docs — Floor Class](https://www.revitapidocs.com/2024/d0e9a9ac-1a42-6b41-e512-6daa99c11e09.htm)    | Tài liệu chính thức về Floor                               |
| [Revit API Docs — FamilyInstance](https://www.revitapidocs.com/2024/0d2231d8-ab0b-b921-4b31-3e7c01d05a62.htm) | Tài liệu chính thức về FamilyInstance (cột, dầm...)        |
| [Revit API Docs — Transaction](https://www.revitapidocs.com/2024/308ebf8d-d96d-4643-cd1d-34fffcea53fd.htm)    | Vòng đời Transaction và best practices                     |
| [BuiltInParameter Enum](https://www.revitapidocs.com/2024/fb011c91-be7e-f737-28c7-3f1e1917a0e0.htm)           | Danh sách đầy đủ các BuiltInParameter                      |
| [MCP SDK TypeScript](https://github.com/modelcontextprotocol/typescript-sdk)                                  | SDK chính thức của MCP cho TypeScript/Node.js              |
| [Anthropic Prompt Engineering Guide](https://docs.anthropic.com/en/docs/build-with-claude/prompt-engineering) | Hướng dẫn viết prompt hiệu quả cho Claude                  |

***

> *"Mỗi tool bạn viết thêm là một khả năng mới cho AI. Hệ sinh thái tool càng phong phú, AI càng có thể giải quyết những bài toán BIM phức tạp hơn — nhưng description của mỗi tool phải càng rõ ràng hơn."*

***

## Điều hướng

|                      |                                                                                             |
| -------------------- | ------------------------------------------------------------------------------------------- |
| **Bài trước**        | [Bài 7: Kết nối mọi thứ lại với nhau](/revit-mcp-ai/phan-3-ket-noi-and-van-hanh/bai-7.md)   |
| **Bài tiếp theo**    | [Bài 9: Đóng gói Plugin](/revit-mcp-ai/phan-4-nang-cao-and-hoan-thien/bai-9.md)             |
| **Mục lục khóa học** | [COURSE-OUTLINE.md](https://github.com/nguyenngocdue/Revit-MCP/blob/main/COURSE-OUTLINE.md) |

***

*Bài 8 / 10 — Revit API × MCP × AI: Từ Zero đến Plugin hoàn chỉnh*


---

# 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-4-nang-cao-and-hoan-thien/bai-8.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.
