Skip to main content
Glama
huafua

mcp-simulator

by huafua
README.md
# MCP Simulator (Node.js MCP Server)

`mcp simulator` 是一個基於 Node.js 構建的輕量級 **MCP 伺服器 (Model Context Protocol Server)**。本專案採用零外部依賴設計(僅使用原生 `http` 模組),透過模組化的 `McpServer` 與 `McpRegistry` 提供動態的工具註冊與 HTTP 遠端調用(RPC)能力。

---

## 🚀 核心特性

- **零外部依賴**:完全依賴 Node.js 原生的 `http` 模組,無需 `express` 等框架。
- **全新 MCP 核心架構**:
    - `McpRegistry`:負責維護工具清單與執行邏輯(支援同步與非同步 `async` 方法)。
    - `McpServer`:提供基於 HTTP POST 的執行入口與統一的 JSON 回應封裝。
- **簡潔的 API 註冊設計**:提供支援鏈式調用的 `register()` 介面,僅需提供「工具定義」與「執行回呼」兩個參數即可輕鬆註冊。
- **內建工具與反射機制**:
    - 內建 `tool/list` 動態查詢所有已註冊的工具。
    - 提供同步計算、文字處理以及非同步 (`async`) 模擬 API 請求(`fetch-posts`)等完整示範。

---

## 📁 檔案結構

```text
mcp-simulator/
├── mcp.core.js             # 伺服器核心引擎(定義 McpServer 與 McpRegistry 類別)
├── index.js                # 專案主入口(載入核心引擎並註冊具體工具)
├── index.http              # HTTP API 測試腳本(搭配 VS Code REST Client 使用)
├── package.json            # 專案配置文件
└── README.md               # 本專案說明文件
```

---

## ⚙️ 快速開始

### 啟動伺服器

請在專案根目錄執行以下指令:

```bash
node index.js
```

伺服器預設會監聽 `8889` 端口(或讀取環境變數 `PORT`)。啟動後控制台將顯示:

```text
Server running at 8889
```

---

## 🔌 API 協議規範

所有的 API 調用均透過單一入口點進行。

- **請求方法**:`POST`
- **伺服器位址**:`http://localhost:8889`
- **請求標頭 (Header)**:`Content-Type: application/json`
- **請求體格式 (Payload)**:
    ```json
    {
        "name": "要調用的工具名稱",
        "args": {
            "參數鍵": "參數值"
        }
    }
    ```

### 統一回應結構 (Response)

所有請求成功處理後,伺服器將回傳統一封裝的 JSON 結構:

```json
{
    "code": 200,
    "message": "success",
    "data": {
        /* 工具回傳的原始結果 */
    }
}
```

#### 伺服器錯誤狀態一覽

| HTTP 狀態碼 | 情境說明                                   | 回應內容 (JSON)                                                       |
| :---------: | :----------------------------------------- | :-------------------------------------------------------------------- |
|   **200**   | **Header 錯誤**(未指定 application/json) | `{"code": 406, "message": "Content-type must be 'application/json'"}` |
|   **200**   | **JSON 格式錯誤**(無法被解析)            | `{"code": 500, "message": "Request body is not valid format"}`        |
|   **200**   | **未提供工具名稱**(缺少 name 欄位)       | `{"code": 406, "message": "Name must be provided"}`                   |
|   **200**   | **呼叫未註冊的工具**                       | `{"code": 200, "message": "success", "data": null}`                   |

---

## 🛠️ 內建方法調用示例

以下是以 `localhost:8889` 為例的實際調用資料:

### 1. 獲取可用工具清單 (`tool/list`)

列出伺服器中已註冊的所有工具定義。

- **請求 Payload**:`{"name": "tool/list", "args": {}}`
- **回應範例**:
    ```json
    {
        "code": 200,
        "message": "success",
        "data": [
            { "name": "info", "description": "..." },
            {
                "name": "hello",
                "description": "just say hello to someone",
                "args": { "username": "string" }
            },
            {
                "name": "calculate",
                "description": "calculate sum of two numbers",
                "args": { "a": "number", "b": "number" }
            },
            {
                "name": "fetch-posts",
                "description": "fetch posts from https://jsonplaceholder.typicode.com/posts"
            }
        ]
    }
    ```

### 2. 計算兩數之和 (`calculate`)

- **請求 Payload**:`{"name": "calculate", "args": {"a": 20, "b": 30}}`
- **回應範例**:
    ```json
    {
        "code": 200,
        "message": "success",
        "data": { "result": 50 }
    }
    ```

### 3. 非同步請求測試 (`fetch-posts`)

示範 `async` 回呼函數的用法,返回一組使用者的假資料(陣列)。

- **請求 Payload**:`{"name": "fetch-posts"}`
- **回應範例**:
    ```json
    {
        "code": 200,
        "message": "success",
        "data": [
            {
                "id": 1,
                "name": "Leanne Graham",
                "username": "Bret",
                "email": "Sincere@april.biz"
                // ... (其他資料略)
            }
        ]
    }
    ```

---

## 📝 開發與擴充自定義工具

您可以修改 `index.js`,透過鏈式調用 `.register()` 新增您的工具。

### API 簽名

```javascript
server.register(toolDefinition, callback);
```

- **`toolDefinition`** (Object): 必須包含 `name`,並可選提供 `description` 與 `args`(參數定義)。
- **`callback`** (Function / Async Function): 當接收到請求時執行的回呼。接收一個來自 `req.params.args` 的**單一物件參數**。

### 註冊範例

```javascript
const { McpServer } = require("./mcp.core");

new McpServer(8889)
    // 註冊一個需要參數的非同步工具
    .register(
        {
            name: "get_user",
            description: "獲取特定使用者資料",
            args: { userId: "number" },
        },
        async ({ userId }) => {
            // ⚠️ 必須使用物件解構讀取參數
            const user = await database.find(userId);
            return { result: user };
        },
    )
    .start();
```

> **💡 開發重點提醒:**
>
> 1. **參數接收**:因為客戶端傳來的 `args` 會被當作**單一物件**傳遞給回呼函數,所以若工具定義了多個參數,請務必在回呼函數使用 `{ param1, param2 }` 進行**物件解構**。
> 2. **非同步支援**:`McpRegistry` 內部使用 `await` 執行工具,您可以放心在回呼函數中使用 `async/await` 進行資料庫查詢或發送網路請求。

---

## 📄 授權協議

本專案採用 [MIT License](LICENSE) 條款進行開源。