# Bài 4: Dựng MCP Server bằng Node.js

> **Khóa học:** Revit API x MCP x AI — Từ Zero đến Plugin hoàn chỉnh
>
> **Mục tiêu:** Hiểu và xây dựng một MCP Server hoàn chỉnh bằng Node.js/TypeScript, kết nối AI với Revit thông qua Model Context Protocol.
>
> **Thời lượng dự kiến:** 2–3 giờ
>
> **Mã nguồn tham khảo:** [deepbim-revit-mcp-plugin](https://github.com/nguyenngocdue/deepbim-revit-mcp-plugin)
>
> **Tham chiếu triển khai Tool/MCP Server:** [revit-mcp-server](https://github.com/nguyenngocdue/revit-mcp-server)

***

## Mục lục

* [Giới thiệu bài học](#giới-thiệu-bài-học)
* [MCP Server là gì và tại sao cần nó](#mcp-server-là-gì-và-tại-sao-cần-nó)
* [Kiến trúc tổng quan](#kiến-trúc-tổng-quan)
* [Khởi tạo project Node.js](#khởi-tạo-project-nodejs)
* [Cấu trúc thư mục project](#cấu-trúc-thư-mục-project)
* [Triển khai MCP Server cơ bản](#triển-khai-mcp-server-cơ-bản)
* [Ví dụ tạo Tool theo dự án thực tế: say hello](#ví-dụ-tạo-tool-theo-dự-án-thực-tế-say-hello)
* [Đăng ký Tool: get\_walls](#đăng-ký-tool-get_walls)
* [Đăng ký Tool: create\_wall](#đăng-ký-tool-create_wall)
* [Transport: stdio và SSE](#transport-stdio-và-sse)
* [Chạy và kiểm tra MCP Server](#chạy-và-kiểm-tra-mcp-server)
* [Câu hỏi tự suy nghĩ](#câu-hỏi-tự-suy-nghĩ)
* [Tổng kết](#tổng-kết)
* [Tài liệu tham khảo](#tài-liệu-tham-khảo)

***

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

Trong bài trước, chúng ta đã tìm hiểu về **Model Context Protocol (MCP)** và kiến trúc tổng thể của hệ thống. Bài này sẽ đi sâu vào việc **xây dựng MCP Server** — thành phần trung tâm chịu trách nhiệm:

1. **Nhận yêu cầu** từ AI client (Claude, Cursor, VS Code...)
2. **Chuyển tiếp** yêu cầu đến Revit Plugin đang chạy trong Revit
3. **Trả kết quả** về cho AI để xử lý và hiển thị cho người dùng

MCP Server đóng vai trò như một **cầu nối thông minh** giữa thế giới AI và phần mềm Revit. Nó không trực tiếp thao tác với Revit API, mà giao tiếp với Revit Plugin thông qua HTTP requests.

> **Lưu ý:** Toàn bộ code trong bài này sử dụng TypeScript. Nếu bạn chưa quen với TypeScript, hãy xem lại phần phụ lục về TypeScript cơ bản.

***

## MCP Server là gì và tại sao cần nó

### Định nghĩa

**MCP Server** là một ứng dụng Node.js thực thi chuẩn [Model Context Protocol](https://modelcontextprotocol.io/), cung cấp các **tools** (công cụ) mà AI có thể gọi để tương tác với hệ thống bên ngoài — trong trường hợp của chúng ta là **Autodesk Revit**.

### Tại sao không để AI gọi thẳng Revit?

Câu hỏi hay gặp nhất là: "Tại sao không để AI gọi thẳng API của Revit?"

| Vấn đề                      | Giải thích                                                                   |
| --------------------------- | ---------------------------------------------------------------------------- |
| **Revit không có REST API** | Revit là ứng dụng desktop, không có sẵn web API. Cần một plugin làm cầu nối. |
| **AI cần giao thức chuẩn**  | Các AI client (Claude, Cursor) giao tiếp qua MCP, không phải HTTP tùy ý.     |
| **Bảo mật**                 | MCP Server kiểm soát những gì AI có thể làm, tránh truy cập tùy ý vào Revit. |
| **Tách biệt trách nhiệm**   | Mỗi thành phần làm một việc, dễ bảo trì và mở rộng.                          |

### Ví dụ thực tế

Khi người dùng hỏi Claude: *"Hãy liệt kê tất cả các tường trong mô hình Revit"*, luồng xử lý sẽ là:

```
Người dùng → Claude AI → MCP Client → MCP Server → HTTP Request → Revit Plugin → Revit API
                                            ↑
                                     Chúng ta xây dựng phần này
```

***

## Kiến trúc tổng quan

### Sơ đồ kiến trúc MCP Server

```mermaid
graph TB
    subgraph "AI Layer"
        A[Claude / Cursor / VS Code]
        B[MCP Client]
    end

    subgraph "MCP Server Layer"
        C[MCP Server - Node.js]
        D[Tool Registry]
        E[Transport Handler]
        F[Request Processor]
    end

    subgraph "Revit Layer"
        G[Revit Plugin - HTTP Listener]
        H[Revit API]
        I[Revit Model .rvt]
    end

    A -->|"Prompt"| B
    B -->|"MCP Protocol"| E
    E --> F
    F -->|"Lookup tool"| D
    D -->|"Execute"| F
    F -->|"HTTP Request"| G
    G -->|"Revit API Call"| H
    H -->|"Read/Write"| I
    I -->|"Result"| H
    H -->|"Response"| G
    G -->|"JSON Response"| F
    F -->|"MCP Response"| E
    E -->|"Result"| B
    B -->|"Answer"| A

    style C fill:#4CAF50,stroke:#333,color:#fff
    style D fill:#2196F3,stroke:#333,color:#fff
    style E fill:#FF9800,stroke:#333,color:#fff
    style F fill:#9C27B0,stroke:#333,color:#fff
```

### Sequence Diagram: Luồng xử lý từ AI đến Revit

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

    User->>AI: "Liệt kê tất cả các tường"
    AI->>Client: Gọi tool get_walls
    Client->>Server: MCP Request (tool: get_walls)
    Server->>Server: Validate request & lookup tool
    Server->>Plugin: HTTP GET /api/walls
    Plugin->>Revit: FilteredElementCollector
    Revit-->>Plugin: List<Wall>
    Plugin-->>Server: JSON Response
    Server-->>Client: MCP Response (content)
    Client-->>AI: Tool result
    AI-->>User: "Mô hình có 42 tường..."
```

### Component Diagram: Bên trong MCP Server

```mermaid
graph LR
    subgraph "MCP Server Internals"
        direction TB
        A[index.ts<br/>Entry Point] --> B[McpServer Instance]
        B --> C[Tool: get_walls]
        B --> D[Tool: create_wall]
        B --> E[Tool: delete_element]
        B --> F[Tool: get_model_info]
        B --> G[Transport Layer]
        G --> G1[StdioServerTransport]
        G --> G2[SSEServerTransport]
    end

    subgraph "External Dependencies"
        H[zod - Schema Validation]
        I[node-fetch - HTTP Client]
        J[@modelcontextprotocol/sdk]
    end

    J --> B
    H --> C
    H --> D
    I --> C
    I --> D

    style B fill:#4CAF50,stroke:#333,color:#fff
    style G fill:#FF9800,stroke:#333,color:#fff
```

***

## Khởi tạo project Node.js

### Bước 1: Tạo project mới

```bash
# Tạo thư mục project
mkdir revit-mcp-server
cd revit-mcp-server

# Khởi tạo project Node.js
npm init -y
```

### Bước 2: Cài đặt dependencies

```bash
# Dependencies chính
npm install @modelcontextprotocol/sdk zod

# DevDependencies cho TypeScript
npm install -D typescript @types/node tsx
```

| Package                     | Vai trò                                                                |
| --------------------------- | ---------------------------------------------------------------------- |
| `@modelcontextprotocol/sdk` | SDK chính thức của MCP, cung cấp `McpServer`, `Transport`, v.v.        |
| `zod`                       | Thư viện validate schema — định nghĩa kiểu dữ liệu cho input của tools |
| `typescript`                | Compiler TypeScript                                                    |
| `@types/node`               | Type definitions cho Node.js                                           |
| `tsx`                       | Chạy file TypeScript trực tiếp không cần build                         |

### Bước 3: Cấu hình TypeScript

Tạo file `tsconfig.json`:

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}
```

### Bước 4: Cấu hình package.json

Thêm các scripts vào `package.json`:

```json
{
  "name": "revit-mcp-server",
  "version": "1.0.0",
  "description": "MCP Server kết nối AI với Revit",
  "type": "module",
  "main": "dist/index.js",
  "scripts": {
    "build": "tsc",
    "start": "node dist/index.js",
    "dev": "tsx src/index.ts"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.12.1",
    "zod": "^3.24.4"
  },
  "devDependencies": {
    "@types/node": "^22.15.3",
    "tsx": "^4.19.4",
    "typescript": "^5.8.3"
  }
}
```

***

## Cấu trúc thư mục project

```
revit-mcp-server/
├── src/
│   ├── index.ts              # Entry point — khởi tạo server và transport
│   ├── config.ts             # Cấu hình (URL Revit plugin, port, v.v.)
│   ├── tools/
│   │   ├── get-walls.ts      # Tool: lấy danh sách tường
│   │   ├── create-wall.ts    # Tool: tạo tường mới
│   │   └── index.ts          # Export tất cả tools
│   ├── utils/
│   │   ├── revit-client.ts   # HTTP client gọi Revit Plugin
│   │   └── logger.ts         # Logging utility
│   └── types/
│       └── revit.ts          # Type definitions cho Revit data
├── tsconfig.json
├── package.json
└── README.md
```

> **Ghi chú:** Trong bài học này, để đơn giản, chúng ta sẽ viết toàn bộ code trong một file `index.ts`. Trong dự án thực tế, nên tách ra theo cấu trúc trên.

***

## Triển khai MCP Server cơ bản

### Flow Diagram: Quy trình đăng ký và thực thi Tool

```mermaid
flowchart TD
    A[Khởi động MCP Server] --> B[Tạo McpServer instance]
    B --> C[Đăng ký Tool: get_walls]
    B --> D[Đăng ký Tool: create_wall]
    C --> E[Định nghĩa schema với zod]
    D --> F[Định nghĩa schema với zod]
    E --> G[Đăng ký handler function]
    F --> H[Đăng ký handler function]
    G --> I[Khởi tạo Transport]
    H --> I
    I --> J{Loại Transport?}
    J -->|stdio| K[StdioServerTransport]
    J -->|SSE| L[SSEServerTransport]
    K --> M[Server sẵn sàng nhận request]
    L --> M
    M --> N[AI gọi tool]
    N --> O[MCP SDK validate input theo schema]
    O --> P[Gọi handler function]
    P --> Q[HTTP request đến Revit Plugin]
    Q --> R[Trả kết quả về AI]

    style A fill:#4CAF50,stroke:#333,color:#fff
    style M fill:#2196F3,stroke:#333,color:#fff
    style R fill:#FF9800,stroke:#333,color:#fff
```

### Code hoàn chỉnh: `src/index.ts`

Dưới đây là toàn bộ code cho MCP Server. Chúng ta sẽ phân tích từng phần sau.

```typescript
// src/index.ts
// MCP Server cho Revit — Kết nối AI với Autodesk Revit

// ============================================================
// 1. IMPORTS
// ============================================================
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

// ============================================================
// 2. CẤU HÌNH
// ============================================================

// URL của Revit Plugin HTTP Listener
// Revit Plugin sẽ lắng nghe tại địa chỉ này
const REVIT_PLUGIN_URL = "http://localhost:8080";

// Timeout cho các request đến Revit (ms)
const REQUEST_TIMEOUT = 30000;

// ============================================================
// 3. UTILITY FUNCTIONS
// ============================================================

/**
 * Gọi HTTP request đến Revit Plugin
 * Đây là hàm trung tâm để giao tiếp với Revit
 *
 * @param endpoint - Đường dẫn API (ví dụ: "/api/walls")
 * @param method   - HTTP method (GET, POST, DELETE, v.v.)
 * @param body     - Dữ liệu gửi kèm (cho POST/PUT)
 * @returns Dữ liệu JSON trả về từ Revit Plugin
 */
async function callRevitPlugin(
  endpoint: string,
  method: string = "GET",
  body?: Record<string, unknown>
): Promise<unknown> {
  const url = `${REVIT_PLUGIN_URL}${endpoint}`;

  // Tạo AbortController để xử lý timeout
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT);

  try {
    const options: RequestInit = {
      method,
      headers: {
        "Content-Type": "application/json",
        Accept: "application/json",
      },
      signal: controller.signal,
    };

    // Chỉ thêm body khi có dữ liệu và method không phải GET
    if (body && method !== "GET") {
      options.body = JSON.stringify(body);
    }

    const response = await fetch(url, options);

    // Kiểm tra response status
    if (!response.ok) {
      const errorText = await response.text();
      throw new Error(
        `Revit Plugin trả về lỗi ${response.status}: ${errorText}`
      );
    }

    // Parse JSON response
    const data = await response.json();
    return data;
  } catch (error) {
    // Xử lý các loại lỗi khác nhau
    if (error instanceof Error) {
      if (error.name === "AbortError") {
        throw new Error(
          `Request đến Revit Plugin bị timeout sau ${REQUEST_TIMEOUT}ms`
        );
      }
      // Lỗi kết nối — Revit Plugin chưa chạy
      if (error.message.includes("ECONNREFUSED")) {
        throw new Error(
          "Không thể kết nối đến Revit Plugin. " +
            "Hãy đảm bảo Revit đã mở và Plugin đang chạy."
        );
      }
      throw error;
    }
    throw new Error("Lỗi không xác định khi gọi Revit Plugin");
  } finally {
    // Luôn clear timeout để tránh memory leak
    clearTimeout(timeoutId);
  }
}

/**
 * Format kết quả thành MCP content response
 * MCP yêu cầu response phải có dạng { content: [...] }
 */
function formatResponse(data: unknown): {
  content: { type: "text"; text: string }[];
} {
  return {
    content: [
      {
        type: "text" as const,
        text: JSON.stringify(data, null, 2),
      },
    ],
  };
}

/**
 * Format lỗi thành MCP error response
 */
function formatError(message: string): {
  content: { type: "text"; text: string }[];
  isError: boolean;
} {
  return {
    content: [
      {
        type: "text" as const,
        text: `Lỗi: ${message}`,
      },
    ],
    isError: true,
  };
}

// ============================================================
// 4. KHỞI TẠO MCP SERVER
// ============================================================

// Tạo instance của McpServer
// Đây là đối tượng chính quản lý tất cả tools và transport
const server = new McpServer({
  name: "revit-mcp-server",       // Tên server — hiển thị trong AI client
  version: "1.0.0",               // Phiên bản
  description:
    "MCP Server kết nối AI với Autodesk Revit thông qua DeepBIM Plugin",
});

// ============================================================
// 5. ĐĂNG KÝ TOOLS
// ============================================================

// ----- Tool 1: get_walls -----
// Lấy danh sách tất cả các tường trong mô hình Revit hiện tại
server.tool(
  // Tên tool — AI sẽ gọi tên này
  "get_walls",

  // Mô tả tool — AI dựa vào mô tả này để quyết định khi nào nên gọi
  "Lấy danh sách tất cả các tường (walls) trong mô hình Revit hiện tại. " +
    "Trả về thông tin bao gồm: ID, tên, chiều dài, chiều cao, độ dày, thể tích.",

  // Schema input — tool này không cần tham số nào
  {},

  // Handler function — logic xử lý khi tool được gọi
  async () => {
    try {
      // Gọi Revit Plugin để lấy danh sách tường
      const walls = await callRevitPlugin("/api/walls");
      return formatResponse(walls);
    } catch (error) {
      const message =
        error instanceof Error ? error.message : "Lỗi không xác định";
      return formatError(message);
    }
  }
);

// ----- Tool 2: create_wall -----
// Tạo một tường mới trong mô hình Revit
server.tool(
  "create_wall",

  "Tạo một tường mới trong mô hình Revit. " +
    "Cần chỉ định tọa độ điểm đầu, điểm cuối, chiều cao và loại tường.",

  // Schema input — sử dụng zod để validate
  {
    // Tọa độ điểm đầu của tường (đơn vị: feet)
    startX: z.number().describe("Tọa độ X của điểm đầu (đơn vị: feet)"),
    startY: z.number().describe("Tọa độ Y của điểm đầu (đơn vị: feet)"),

    // Tọa độ điểm cuối của tường (đơn vị: feet)
    endX: z.number().describe("Tọa độ X của điểm cuối (đơn vị: feet)"),
    endY: z.number().describe("Tọa độ Y của điểm cuối (đơn vị: feet)"),

    // Chiều cao tường (đơn vị: feet)
    height: z
      .number()
      .positive()       // Phải là số dương
      .default(10)      // Mặc định 10 feet
      .describe("Chiều cao tường (đơn vị: feet, mặc định: 10)"),

    // Tên loại tường (Wall Type)
    wallType: z
      .string()
      .optional()       // Không bắt buộc
      .describe(
        'Tên loại tường trong Revit (ví dụ: "Generic - 200mm"). ' +
          "Nếu không chỉ định, sẽ dùng loại mặc định."
      ),

    // Level (tầng) để đặt tường
    levelName: z
      .string()
      .optional()
      .describe(
        'Tên Level để đặt tường (ví dụ: "Level 1"). ' +
          "Nếu không chỉ định, sẽ dùng Level hiện tại."
      ),
  },

  // Handler function
  async (params) => {
    try {
      // Chuẩn bị dữ liệu gửi đến Revit Plugin
      const wallData = {
        start: { x: params.startX, y: params.startY, z: 0 },
        end: { x: params.endX, y: params.endY, z: 0 },
        height: params.height,
        wallType: params.wallType ?? null,
        levelName: params.levelName ?? null,
      };

      // Gọi Revit Plugin để tạo tường
      const result = await callRevitPlugin("/api/walls", "POST", wallData);
      return formatResponse(result);
    } catch (error) {
      const message =
        error instanceof Error ? error.message : "Lỗi không xác định";
      return formatError(message);
    }
  }
);

// ----- Tool 3: get_model_info -----
// Lấy thông tin tổng quan về mô hình Revit
server.tool(
  "get_model_info",

  "Lấy thông tin tổng quan về mô hình Revit hiện tại: " +
    "tên file, đường dẫn, số lượng element, danh sách levels, v.v.",

  {},

  async () => {
    try {
      const info = await callRevitPlugin("/api/model/info");
      return formatResponse(info);
    } catch (error) {
      const message =
        error instanceof Error ? error.message : "Lỗi không xác định";
      return formatError(message);
    }
  }
);

// ----- Tool 4: delete_element -----
// Xóa một element trong Revit theo ID
server.tool(
  "delete_element",

  "Xóa một element trong mô hình Revit theo Element ID. " +
    "Chú ý: hành động này không thể undo từ phía AI.",

  {
    elementId: z
      .number()
      .int()
      .positive()
      .describe("Element ID của đối tượng cần xóa trong Revit"),
  },

  async (params) => {
    try {
      const result = await callRevitPlugin(
        `/api/elements/${params.elementId}`,
        "DELETE"
      );
      return formatResponse(result);
    } catch (error) {
      const message =
        error instanceof Error ? error.message : "Lỗi không xác định";
      return formatError(message);
    }
  }
);

// ============================================================
// 6. KHỞI TẠO TRANSPORT VÀ CHẠY SERVER
// ============================================================

/**
 * Hàm main — khởi động server
 * Sử dụng stdio transport (giao tiếp qua stdin/stdout)
 */
async function main(): Promise<void> {
  // Tạo stdio transport
  // Đây là cách đơn giản nhất để kết nối với AI client
  const transport = new StdioServerTransport();

  // Kết nối server với transport và bắt đầu lắng nghe
  await server.connect(transport);

  // Log thông báo ra stderr (không phải stdout vì stdout dùng cho MCP)
  console.error("Revit MCP Server đã khởi động thành công!");
  console.error("Đang chờ kết nối từ AI client...");
  console.error(`Revit Plugin URL: ${REVIT_PLUGIN_URL}`);
}

// Chạy server
main().catch((error) => {
  console.error("Lỗi khi khởi động MCP Server:", error);
  process.exit(1);
});
```

***

## Ví dụ tạo Tool theo dự án thực tế: say hello

Phần này phân tích trực tiếp tool `say_hello` trong dự án:

* [src/tools/say\_hello.ts](https://github.com/nguyenngocdue/revit-mcp-server/blob/master/src/tools/say_hello.ts)

### Kiến trúc Tool (nắm trước khi đọc code)

Trong `revit-mcp-server`, một tool vận hành theo 4 lớp:

1. **Server entry point**: `src/index.ts` tạo `McpServer`, gọi `registerTools(server)`, rồi connect bằng `StdioServerTransport`.
2. **Auto-register layer**: `src/tools/register.ts` quét thư mục `src/tools/` và tự gọi hàm dạng `registerXxxTool(server)`.
3. **Tool module**: `src/tools/say_hello.ts` định nghĩa `name`, `description`, `schema`, `handler` qua `server.tool(...)`.
4. **Revit bridge layer**: tool gọi `withRevitConnection(...)` (trong `ConnectionManager`) để tìm port plugin Revit, mở TCP, gửi lệnh JSON-RPC (`sendCommand`) và trả kết quả về MCP response.

```mermaid
flowchart LR
    A["AI Host<br/>(Claude/VS Code)"] --> B["MCP Server<br/>src/index.ts"]
    B --> C["registerTools()<br/>src/tools/register.ts"]
    C --> D["registerSayHelloTool()<br/>src/tools/say_hello.ts"]
    D --> E["withRevitConnection()<br/>ConnectionManager"]
    E --> F["RevitClientConnection.sendCommand()<br/>SocketClient"]
    F --> G["Revit Plugin<br/>(TCP JSON-RPC)"]
    G --> H["Revit API"]
    H --> G --> F --> E --> D --> B --> A
```

Tài liệu tham chiếu kiến trúc:

* [src/index.ts](https://github.com/nguyenngocdue/revit-mcp-server/blob/master/src/index.ts)
* [src/tools/register.ts](https://github.com/nguyenngocdue/revit-mcp-server/blob/master/src/tools/register.ts)
* [src/utils/ConnectionManager.ts](https://github.com/nguyenngocdue/revit-mcp-server/blob/master/src/utils/ConnectionManager.ts)
* [src/utils/SocketClient.ts](https://github.com/nguyenngocdue/revit-mcp-server/blob/master/src/utils/SocketClient.ts)

### Phân tích từng phần của `say_hello.ts`

```typescript
import { z } from "zod";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { withRevitConnection } from "../utils/ConnectionManager.js";

export function registerSayHelloTool(server: McpServer) {
  server.tool(
    "say_hello",
    "Display a greeting dialog in Revit. Useful for testing the connection between Claude and Revit.",
    {
      message: z
        .string()
        .optional()
        .describe("Optional custom message to display in the dialog. Defaults to 'Hello MCP!'"),
    },
    async (args) => {
      try {
        const response = await withRevitConnection(async (revitClient) => {
          return await revitClient.sendCommand("say_hello", args);
        });

        return {
          content: [
            {
              type: "text" as const,
              text: JSON.stringify(response, null, 2),
            },
          ],
        };
      } catch (error) {
        return {
          content: [
            {
              type: "text" as const,
              text: `Say hello failed: ${
                error instanceof Error ? error.message : String(error)
              }`,
            },
          ],
        };
      }
    }
  );
}
```

1. **Imports** `z` dùng để khai báo schema input; `McpServer` cung cấp API `server.tool(...)`; `withRevitConnection` là adapter kết nối sang Revit plugin.
2. **Hàm đăng ký tool** `registerSayHelloTool(server)` là convention của repo để auto-register.
3. **Metadata tool**

* `name`: `"say_hello"` là ID để AI gọi.
* `description`: giúp model quyết định khi nào nên gọi tool.

4. **Schema input** `message` là optional string, có `describe(...)` để tăng độ chính xác khi AI tự điền tham số.
5. **Handler logic** Handler nhận `args`, gọi `withRevitConnection(...)`, rồi chuyển tiếp lệnh `say_hello` qua `revitClient.sendCommand("say_hello", args)`.
6. **Chuẩn output MCP** Kết quả trả về theo format `content[]`, `type: "text"`, text là JSON đã stringify để AI đọc được ngay.
7. **Nhánh lỗi** Bắt mọi exception và trả text lỗi có ngữ cảnh (`Say hello failed: ...`) thay vì throw trực tiếp.

### Luồng thực thi của riêng tool `say_hello`

```mermaid
sequenceDiagram
    participant AI as AI Client
    participant MCP as MCP Server
    participant Tool as say_hello handler
    participant Conn as ConnectionManager
    participant Socket as SocketClient
    participant Revit as Revit Plugin

    AI->>MCP: tools/call name=\"say_hello\"
    MCP->>Tool: validate args (zod)
    Tool->>Conn: withRevitConnection(...)
    Conn->>Socket: sendCommand(\"say_hello\", args)
    Socket->>Revit: JSON-RPC method=say_hello
    Revit-->>Socket: result
    Socket-->>Tool: response
    Tool-->>MCP: { content: [{type:\"text\", text:\"...\"}] }
    MCP-->>AI: Tool result
```

> **Điểm quan trọng:** `say_hello` là tool tối giản nhưng thể hiện đầy đủ pattern chuẩn trong dự án: `metadata + schema + handler + bridge + mcp response`.

***

## Đăng ký Tool: get\_walls

Hãy phân tích kỹ hơn cách đăng ký tool `get_walls`:

### Cấu trúc của `server.tool()`

```typescript
server.tool(
  name,           // string: Tên tool (AI dùng tên này để gọi)
  description,    // string: Mô tả (AI đọc để hiểu khi nào nên dùng)
  inputSchema,    // object: Schema validate input (dùng zod)
  handler         // function: Logic xử lý
);
```

### Mô tả tool rất quan trọng

AI client (Claude, Cursor) đọc **mô tả** của tool để quyết định có nên gọi tool đó hay không. Vì vậy, mô tả cần:

* **Rõ ràng**: Nói chính xác tool làm gì
* **Cụ thể**: Liệt kê những thông tin tool trả về
* **Có ngữ cảnh**: AI hiểu khi nào nên sử dụng

```typescript
// Mô tả TỐT — cụ thể, rõ ràng
"Lấy danh sách tất cả các tường (walls) trong mô hình Revit hiện tại. " +
"Trả về thông tin bao gồm: ID, tên, chiều dài, chiều cao, độ dày, thể tích."

// Mô tả KHÔNG TỐT — quá chung chung
"Lấy thông tin tường"
```

### Dữ liệu trả về từ Revit Plugin

Khi gọi `GET /api/walls`, Revit Plugin sẽ trả về JSON có dạng:

```json
{
  "success": true,
  "data": [
    {
      "id": 12345,
      "name": "Basic Wall - Generic - 200mm",
      "length": 15.5,
      "height": 10.0,
      "width": 0.656,
      "volume": 101.68,
      "level": "Level 1",
      "wallType": "Generic - 200mm"
    },
    {
      "id": 12346,
      "name": "Basic Wall - Generic - 200mm",
      "length": 8.2,
      "height": 10.0,
      "width": 0.656,
      "volume": 53.79,
      "level": "Level 1",
      "wallType": "Generic - 200mm"
    }
  ],
  "count": 2
}
```

***

## Đăng ký Tool: create\_wall

Tool `create_wall` phức tạp hơn vì nó cần **validate input** từ AI. Đây là nơi **zod** phát huy tác dụng.

### Tại sao cần validate input?

AI có thể gửi dữ liệu không hợp lệ. Ví dụ:

* Chiều cao âm: `height: -5`
* Thiếu tọa độ: chỉ gửi `startX` mà không có `startY`
* Sai kiểu dữ liệu: `height: "mười"` thay vì `height: 10`

Zod giúp chúng ta định nghĩa **schema** rõ ràng và tự động validate trước khi handler chạy.

### Phân tích schema của create\_wall

```typescript
// Mỗi trường trong schema là một zod type
{
  // z.number() — phải là số
  startX: z.number().describe("Tọa độ X của điểm đầu (đơn vị: feet)"),
  startY: z.number().describe("Tọa độ Y của điểm đầu (đơn vị: feet)"),
  endX:   z.number().describe("Tọa độ X của điểm cuối (đơn vị: feet)"),
  endY:   z.number().describe("Tọa độ Y của điểm cuối (đơn vị: feet)"),

  // z.number().positive() — phải là số dương
  // .default(10)          — giá trị mặc định nếu không truyền
  height: z.number().positive().default(10)
    .describe("Chiều cao tường (đơn vị: feet, mặc định: 10)"),

  // z.string().optional() — chuỗi, không bắt buộc
  wallType: z.string().optional()
    .describe("Tên loại tường trong Revit"),

  levelName: z.string().optional()
    .describe("Tên Level để đặt tường"),
}
```

### Các zod types thường dùng trong MCP

| Zod Type                 | Ý nghĩa                 | Ví dụ                            |
| ------------------------ | ----------------------- | -------------------------------- |
| `z.string()`             | Chuỗi bắt buộc          | `"Generic - 200mm"`              |
| `z.number()`             | Số bắt buộc             | `10.5`                           |
| `z.boolean()`            | True/False              | `true`                           |
| `z.number().int()`       | Số nguyên               | `12345`                          |
| `z.number().positive()`  | Số dương                | `10` (không chấp nhận 0 hoặc âm) |
| `z.string().optional()`  | Chuỗi tùy chọn          | `undefined` hoặc `"Level 1"`     |
| `z.number().default(10)` | Số với giá trị mặc định | Nếu không truyền thì là `10`     |
| `z.enum(["a","b"])`      | Giá trị trong tập hợp   | `"a"` hoặc `"b"`                 |
| `z.array(z.number())`    | Mảng số                 | `[1, 2, 3]`                      |

### Sơ đồ luồng validate với zod

```mermaid
flowchart LR
    A[AI gửi input] --> B{zod validate}
    B -->|Hợp lệ| C[Handler chạy]
    B -->|Không hợp lệ| D[MCP SDK trả lỗi ngay]
    C --> E[HTTP request đến Revit]
    D --> F[AI nhận thông báo lỗi rõ ràng]

    style B fill:#FF9800,stroke:#333,color:#fff
    style C fill:#4CAF50,stroke:#333,color:#fff
    style D fill:#F44336,stroke:#333,color:#fff
```

***

## Transport: stdio và SSE

MCP hỗ trợ nhiều loại **transport** — cách mà MCP Client và MCP Server giao tiếp với nhau.

> **Định hướng của khóa học này:** toàn bộ luồng thực hành chính dùng **STDIO** (Claude Code/VS Code spawn MCP Server local).\
> **SSE** được đưa vào như kiến thức mở rộng để bạn triển khai môi trường web/remote sau khóa học.

![Sơ đồ trọng tâm STDIO](/files/a9HAMBPmTHprtmS3yGPs)

### So sánh hai loại transport chính

```mermaid
graph LR
    subgraph "stdio Transport"
        direction LR
        A1[AI Client] -->|stdin| B1[MCP Server Process]
        B1 -->|stdout| A1
    end

    subgraph "SSE Transport"
        direction LR
        A2[AI Client] -->|"HTTP POST /message"| B2[MCP Server - HTTP]
        B2 -->|"SSE events"| A2
    end

    style B1 fill:#4CAF50,stroke:#333,color:#fff
    style B2 fill:#2196F3,stroke:#333,color:#fff
```

| Đặc điểm         | stdio                         | SSE (Server-Sent Events)  |
| ---------------- | ----------------------------- | ------------------------- |
| **Giao tiếp**    | stdin/stdout                  | HTTP                      |
| **Triển khai**   | Process con của AI client     | Server độc lập            |
| **Nhiều client** | Không (1:1)                   | Có (nhiều client kết nối) |
| **Debug**        | Khó (không thể log ra stdout) | Dễ (có HTTP endpoint)     |
| **Cấu hình**     | Đơn giản                      | Cần setup HTTP server     |
| **Sử dụng**      | Claude Desktop, Cursor        | Web apps, remote servers  |

### Cấu hình chuẩn dùng trong khóa học (STDIO)

```json
{
  "mcpServers": {
    "revit-mcp": {
      "command": "node",
      "args": ["./mcp-server/dist/index.js"],
      "env": {
        "REVIT_API_URL": "http://localhost:8080"
      }
    }
  }
}
```

### stdio Transport (Mặc định)

```typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";

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

// ... đăng ký tools ...

// Khởi tạo stdio transport
const transport = new StdioServerTransport();
await server.connect(transport);

// QUAN TRỌNG: Khi dùng stdio, chỉ được log ra stderr
// Vì stdout đã được dùng cho giao tiếp MCP
console.error("Server đã khởi động"); // Đúng — stderr
// console.log("Server đã khởi động"); // SAI — sẽ phá vỡ MCP protocol
```

**Cấu hình trong Claude Desktop** (`claude_desktop_config.json`):

```json
{
  "mcpServers": {
    "revit": {
      "command": "node",
      "args": ["/đường-dẫn/đến/revit-mcp-server/dist/index.js"],
      "env": {
        "REVIT_PLUGIN_URL": "http://localhost:8080"
      }
    }
  }
}
```

### SSE Transport

```typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import express from "express";

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

// ... đăng ký tools ...

// Lưu trữ các session transport
const transports: Record<string, SSEServerTransport> = {};

// Endpoint để client kết nối SSE
app.get("/sse", async (req, res) => {
  // Tạo transport mới cho mỗi kết nối
  const transport = new SSEServerTransport("/message", res);
  transports[transport.sessionId] = transport;

  // Kết nối server với transport
  await server.connect(transport);

  // Xóa transport khi client ngắt kết nối
  res.on("close", () => {
    delete transports[transport.sessionId];
  });
});

// Endpoint để nhận message từ client
app.post("/message", async (req, res) => {
  const sessionId = req.query.sessionId as string;
  const transport = transports[sessionId];

  if (transport) {
    await transport.handlePostMessage(req, res);
  } else {
    res.status(404).json({ error: "Session not found" });
  }
});

// Khởi động HTTP server
const PORT = 3001;
app.listen(PORT, () => {
  console.log(`MCP Server (SSE) đang chạy tại http://localhost:${PORT}`);
});
```

### Khi nào dùng transport nào?

| Trường hợp                                     | Nên dùng  |
| ---------------------------------------------- | --------- |
| Phát triển local, dùng Claude Desktop / Cursor | **stdio** |
| Deploy server trên cloud / mạng nội bộ         | **SSE**   |
| Cần nhiều AI client kết nối cùng lúc           | **SSE**   |
| Demo nhanh, prototype                          | **stdio** |

***

## Chạy và kiểm tra MCP Server

### Bước 1: Build và chạy

```bash
# Build TypeScript sang JavaScript
npm run build

# Chạy server (stdio mode)
npm start

# Hoặc chạy trực tiếp với tsx (không cần build)
npm run dev
```

### Bước 2: Kiểm tra với MCP Inspector

[MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector) là công cụ chính thức để kiểm tra MCP Server:

```bash
# Cài đặt MCP Inspector
npx @modelcontextprotocol/inspector node dist/index.js
```

MCP Inspector sẽ mở trình duyệt tại `http://localhost:5173`, cho phép:

* Xem danh sách tools đã đăng ký
* Gọi thử từng tool với input tự định nghĩa
* Xem response trả về

### Bước 3: Cấu hình trong Claude Desktop

1. Mở Claude Desktop
2. Vào **Settings > Developer > Edit Config**
3. Thêm cấu hình server:

```json
{
  "mcpServers": {
    "revit": {
      "command": "node",
      "args": ["/absolute/path/to/revit-mcp-server/dist/index.js"]
    }
  }
}
```

4. Khởi động lại Claude Desktop
5. Thử hỏi: *"Hãy liệt kê các tường trong mô hình Revit"*

### Bước 4: Kiểm tra kết nối với Revit Plugin

Đảm bảo:

1. **Revit đã mở** và có mô hình (`.rvt`) đang active
2. **DeepBIM Plugin đã được load** trong Revit
3. **HTTP Listener đang chạy** tại `http://localhost:8080`

Kiểm tra nhanh bằng curl:

```bash
# Kiểm tra kết nối đến Revit Plugin
curl http://localhost:8080/api/walls

# Kiểm tra tạo tường
curl -X POST http://localhost:8080/api/walls \
  -H "Content-Type: application/json" \
  -d '{
    "start": {"x": 0, "y": 0, "z": 0},
    "end": {"x": 10, "y": 0, "z": 0},
    "height": 10
  }'
```

***

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

### Câu 1: Vai trò của MCP Server

> Tại sao chúng ta cần MCP Server? Không thể để AI gọi trực tiếp đến Revit Plugin được sao?

<details>

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

MCP Server cần thiết vì nhiều lý do:

1. **Chuẩn giao tiếp**: AI client (Claude, Cursor) chỉ hiểu giao thức MCP, không biết cách gọi HTTP API tùy ý. MCP Server "dịch" giữa hai giao thức này.
2. **Khai báo khả năng**: MCP Server khai báo danh sách tools mà AI có thể dùng. Không có MCP Server, AI không biết Revit Plugin hỗ trợ những lệnh gì.
3. **Bảo mật**: MCP Server kiểm soát chính xác những gì AI có thể làm. Ví dụ, có thể chỉ cho phép `get_walls` mà không cho phép `delete_element`.
4. **Validate dữ liệu**: Trước khi gửi đến Revit, MCP Server validate input bằng zod schema, đảm bảo dữ liệu hợp lệ.
5. **Xử lý lỗi**: MCP Server bắt lỗi từ Revit Plugin và trả về thông báo dễ hiểu cho AI, thay vì để AI nhận raw error.

</details>

### Câu 2: Zod Schema

> Điều gì xảy ra nếu AI gửi `height: -5` cho tool `create_wall`? Giải thích quy trình validate.

<details>

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

Khi AI gửi `height: -5`:

1. MCP SDK nhận request và đọc schema của tool `create_wall`
2. SDK thấy `height` được định nghĩa là `z.number().positive()` — yêu cầu số dương
3. Giá trị `-5` không thỏa mãn điều kiện `.positive()` (phải lớn hơn 0)
4. SDK **tự động từ chối** request và trả về lỗi validation cho AI
5. Handler function **không bao giờ được gọi** — request bị chặn từ trước

Đây là ưu điểm lớn của zod: validate xảy ra ở tầng MCP SDK, trước khi code của chúng ta chạy, đảm bảo handler luôn nhận dữ liệu hợp lệ.

</details>

### Câu 3: stdio vs SSE

> Bạn đang xây dựng một ứng dụng web cho phép nhiều kỹ sư BIM cùng tương tác với Revit qua AI. Bạn sẽ chọn transport nào và tại sao?

<details>

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

Trong trường hợp này, nên chọn **SSE Transport** vì:

1. **Nhiều client**: Nhiều kỹ sư (nhiều trình duyệt) cần kết nối cùng lúc đến MCP Server. stdio chỉ hỗ trợ 1:1.
2. **Web-based**: SSE hoạt động qua HTTP, phù hợp với web app. stdio yêu cầu process con, không khả thi từ trình duyệt.
3. **Deploy tập trung**: MCP Server có thể chạy trên một server trung tâm, nhiều người truy cập từ xa.
4. **Quản lý session**: SSE transport có session ID, có thể theo dõi và quản lý từng kết nối.

Tuy nhiên, cần lưu ý:

* Mỗi session SSE vẫn cần kết nối đến Revit Plugin riêng biệt (hoặc dùng hàng đợi)
* Cần xử lý đồng bộ nếu nhiều người cùng thao tác trên một mô hình Revit
* Cần thêm xác thực (authentication) để bảo mật

</details>

### Câu 4: Error Handling

> Điều gì xảy ra khi MCP Server gửi request đến Revit Plugin nhưng Revit chưa được mở? Làm sao để xử lý tốt hơn?

<details>

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

Khi Revit chưa mở, Revit Plugin cũng chưa chạy, nên HTTP request sẽ thất bại với lỗi `ECONNREFUSED`.

Trong code hiện tại, chúng ta đã xử lý trường hợp này:

```typescript
if (error.message.includes("ECONNREFUSED")) {
  throw new Error(
    "Không thể kết nối đến Revit Plugin. " +
    "Hãy đảm bảo Revit đã mở và Plugin đang chạy."
  );
}
```

Để xử lý tốt hơn, có thể:

1. **Retry mechanism**: Thử lại 2–3 lần trước khi báo lỗi, phòng trường hợp Revit đang khởi động.
2. **Health check**: Tạo một tool `check_connection` để AI kiểm tra kết nối trước khi gọi các tool khác.
3. **Cache trạng thái**: Lưu trạng thái kết nối cuối cùng, tránh gọi liên tục khi biết Revit chưa sẵn sàng.
4. **Thông báo cụ thể**: Trả về hướng dẫn cụ thể cho người dùng (ví dụ: "Mở Revit > Tab Add-ins > Click 'Start DeepBIM Server'").

</details>

### Câu 5: Mở rộng thêm tools

> Hãy thiết kế schema zod cho một tool mới tên `get_elements_by_category` — cho phép AI lấy các element theo category (Walls, Floors, Columns, v.v.). Liệt kê các trường input cần thiết.

<details>

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

```typescript
server.tool(
  "get_elements_by_category",

  "Lấy danh sách các element trong mô hình Revit theo category. " +
    "Hỗ trợ các category như: Walls, Floors, Columns, Doors, Windows, Roofs, Stairs.",

  {
    category: z
      .enum([
        "Walls",
        "Floors",
        "Columns",
        "Doors",
        "Windows",
        "Roofs",
        "Stairs",
        "Furniture",
        "Rooms",
      ])
      .describe("Category của element cần tìm"),

    levelName: z
      .string()
      .optional()
      .describe("Lọc theo Level (ví dụ: 'Level 1'). Nếu không chỉ định, lấy tất cả levels."),

    limit: z
      .number()
      .int()
      .positive()
      .max(1000)
      .default(100)
      .describe("Số lượng element tối đa trả về (mặc định: 100, tối đa: 1000)"),

    includeParameters: z
      .boolean()
      .default(false)
      .describe("Có trả về danh sách Parameters của mỗi element hay không"),
  },

  async (params) => {
    try {
      const queryParams = new URLSearchParams({
        category: params.category,
        limit: params.limit.toString(),
        includeParameters: params.includeParameters.toString(),
      });

      if (params.levelName) {
        queryParams.set("level", params.levelName);
      }

      const result = await callRevitPlugin(
        `/api/elements?${queryParams.toString()}`
      );
      return formatResponse(result);
    } catch (error) {
      const message =
        error instanceof Error ? error.message : "Lỗi không xác định";
      return formatError(message);
    }
  }
);
```

Điểm cần lưu ý:

* Dùng `z.enum()` để giới hạn category hợp lệ, tránh AI gửi giá trị tùy ý
* Dùng `z.number().max(1000)` để giới hạn số lượng, tránh quá tải Revit
* `includeParameters` là boolean optional, cho phép AI quyết định có cần thông tin chi tiết hay không

</details>

***

## Tổng kết

Trong bài này, chúng ta đã học:

| Nội dung             | Chi tiết                                                         |
| -------------------- | ---------------------------------------------------------------- |
| **MCP Server là gì** | Ứng dụng Node.js làm cầu nối giữa AI và Revit                    |
| **Kiến trúc**        | AI → MCP Client → MCP Server → HTTP → Revit Plugin → Revit API   |
| **Khởi tạo project** | npm init, cài đặt `@modelcontextprotocol/sdk`, `zod`, TypeScript |
| **Đăng ký tools**    | `server.tool(name, description, schema, handler)`                |
| **Zod schema**       | Validate input từ AI trước khi xử lý                             |
| **Transport**        | stdio (đơn giản, local) vs SSE (HTTP, nhiều client)              |
| **Error handling**   | Bắt lỗi kết nối, timeout, response không hợp lệ                  |
| **Kiểm tra**         | MCP Inspector, Claude Desktop, curl                              |

### Những điểm chính cần nhớ

1. **MCP Server không trực tiếp gọi Revit API** — nó gọi HTTP đến Revit Plugin, và Plugin mới là nơi tương tác với Revit API.
2. **Mô tả tool quyết định chất lượng** — AI dựa vào mô tả để chọn tool phù hợp. Mô tả kém = AI gọi sai tool.
3. **Zod schema là tuyến phòng thủ đầu tiên** — validate input trước khi chạy logic, tránh lỗi và bảo vệ Revit model.
4. **stdio dùng cho development, SSE dùng cho production** — tùy mục đích mà chọn transport phù hợp.
5. **Chỉ log ra `stderr` khi dùng stdio** — `stdout` dành riêng cho giao tiếp MCP, không được dùng để log.

### Bài tiếp theo

> **Bài 5: Dựng Core MCP cho Revit (C#)** — Tạo plugin lắng nghe HTTP request và tương tác với Revit API để thực thi các lệnh từ MCP Server.

***

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

| Tài liệu                                              | Liên kết                                                                                                       |
| ----------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| **DeepBIM Revit MCP Plugin** (mã nguồn)               | [github.com/nguyenngocdue/deepbim-revit-mcp-plugin](https://github.com/nguyenngocdue/deepbim-revit-mcp-plugin) |
| **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 Specification**                                 | [spec.modelcontextprotocol.io](https://spec.modelcontextprotocol.io/)                                          |
| **MCP TypeScript SDK**                                | [github.com/modelcontextprotocol/typescript-sdk](https://github.com/modelcontextprotocol/typescript-sdk)       |
| **MCP Inspector**                                     | [modelcontextprotocol.io/docs/tools/inspector](https://modelcontextprotocol.io/docs/tools/inspector)           |
| **Zod Documentation**                                 | [zod.dev](https://zod.dev/)                                                                                    |
| **MCP Quickstart (Server)**                           | [modelcontextprotocol.io/quickstart/server](https://modelcontextprotocol.io/quickstart/server)                 |
| **Claude Desktop MCP Setup**                          | [modelcontextprotocol.io/quickstart/user](https://modelcontextprotocol.io/quickstart/user)                     |

***

> **Ghi chú:** Toàn bộ code trong bài học này có thể tìm thấy tại repository [deepbim-revit-mcp-plugin](https://github.com/nguyenngocdue/deepbim-revit-mcp-plugin). Hãy clone về và thử chạy để hiểu rõ hơn cách hoạt động của MCP Server.

***

> **Bài trước:** [← Bài 3: Triển khai Tool cơ bản với Revit API](/revit-mcp-ai/phan-2-xay-dung-tung-thanh-phan/bai-3.md)
>
> **Bài tiếp theo:** [Bài 5: Dựng Core MCP cho Revit (C#) →](/revit-mcp-ai/phan-2-xay-dung-tung-thanh-phan/bai-5.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-4.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.
