回到文章列表

【Agentic AI】實作 Model Context Protocol (MCP) 標準的 AI 代理

1. Overview

隨著大型語言模型(LLM)在各領域的應用日益普及,如何讓這些強大的 AI 代理安全、高效地與本地資料和工具互動,成為了業界關注的焦點。傳統上,開發者需要手動複製貼上資料,或透過複雜的 API 整合來橋接 AI 與本地系統,這不僅效率低下,也存在潛在的資料安全風險。為了解決這一挑戰,Model Context Protocol (MCP) 應運而生,它是一個開放標準,旨在為 AI 應用程式與外部資料源及工具之間建立安全、雙向的連接 [1]。

本文件將深入探討 MCP 的核心概念與架構,並提供一份實戰指南,教您如何從零開始建構一個符合 MCP 標準的 AI 代理伺服器(MCP Server)。我們將以整合本地 SQL Server 資料庫為例,展示如何定義 Tools 讓 AI 能夠安全地查詢資料、獲取資料表結構,而無需每次手動操作。透過實作 MCP Server,您可以讓 Claude Desktop、IDE 中的 AI 助手(如 Cursor 或 VS Code Copilot)直接存取您的本地數據,極大提升開發效率,同時確保資料隱私與安全性。

2. Architecture / Design

Model Context Protocol (MCP) 採用 Client-Host-Server 的架構模型,旨在提供一個標準化的框架,讓 AI 應用程式(Host)能夠與外部資料和能力(Server)進行整合 [2]。

2.1 Client-Host-Server 模型

  • Host (AI 工具):這是使用者直接互動的 AI 應用程式,例如 Anthropic 的 Claude Desktop、整合 AI 功能的 IDE(如 Cursor 或 Visual Studio Code),或是其他支援 MCP 的 AI 代理平台。Host 負責向使用者呈現 AI 的回應,並在需要時透過 MCP Client 向 MCP Server 發送請求。
  • Client (MCP Client):通常內建於 Host 應用程式中,負責處理與 MCP Server 的通訊細節。它會根據 Host 的需求,將 AI 生成的 Tool Call 請求轉換為符合 MCP 規範的訊息,並發送給 MCP Server。同時,它也會接收 MCP Server 的回應,並將結果傳回給 Host。
  • Server (MCP Server):這是由開發者實作的應用程式,運行在本地環境中。MCP Server 的核心職責是連接到本地資料源(如 SQL Server、檔案系統、API)或本地工具,並將這些能力以標準化的 Resources 和 Tools 形式暴露給 MCP Client。它接收來自 Client 的請求,執行相應的操作,並將結果返回 [3]。

2.2 MCP 的三大核心概念

MCP 透過以下三個核心概念,實現 AI 代理與外部世界的互動:

  • Resources (資源):Resources 代表 AI 可以讀取的靜態資料,例如文件、程式碼檔案、日誌檔、配置檔等。當 AI 需要額外的上下文資訊來理解問題或生成回應時,它可以請求讀取這些 Resources。MCP Server 負責提供這些 Resources 的內容 [4]。
  • Tools (工具):Tools 代表 AI 可以呼叫的動態操作或功能,例如執行 SQL 查詢、呼叫本地 API、寫入檔案、執行腳本等。Tools 允許 AI 不僅僅是「讀取」資訊,還能「執行」操作,從而實現更強大的 Agentic 能力。每個 Tool 都會有明確的輸入參數和預期的輸出格式 [5]。
  • Prompts (提示):Prompts 是預定義的提示模板,它們指導 AI 如何理解和使用 MCP Server 提供的 Resources 和 Tools。這些 Prompts 通常包含對 Server 功能的描述、Tools 的使用說明、以及如何處理特定情境的指引。透過 Prompts,開發者可以精確地控制 AI 與 Server 的互動方式,確保 AI 能夠正確地利用本地能力 [6]。

2.3 SQL Server 整合工作流

當 AI 代理需要與本地 SQL Server 互動時,其工作流如下:

  1. AI 意圖識別:使用者向 AI 提出一個需要查詢本地資料庫的問題(例如:「查詢 Orders 表中最近 10 筆訂單的資訊」)。
  2. Tool Call 生成:AI 代理(Host)根據其內部的邏輯和 MCP Server 提供的 Tools 描述,判斷需要呼叫哪個 Tool(例如 query_database),並生成相應的 Tool Call 請求,包含必要的參數(例如 SQL 查詢語句)。
  3. 請求發送:MCP Client 將 Tool Call 請求封裝為符合 MCP 規範的訊息,透過本地網路發送給 MCP Server。
  4. Server 執行 Tool:MCP Server 接收到請求後,解析 Tool Call,並執行對應的 Tool 邏輯。例如,它會連接到本地 SQL Server,執行 AI 提供的 SQL 查詢。
  5. 結果返回:SQL Server 返回查詢結果給 MCP Server。MCP Server 將結果格式化,並透過 MCP Client 返回給 AI 代理。
  6. AI 回應生成:AI 代理接收到查詢結果後,將其整合到其上下文中,生成一個自然語言的回應給使用者。

3. Prerequisites

要實作一個整合 SQL Server 的 MCP Server,您需要具備以下環境和知識:

  • Node.js 環境:建議使用 LTS 版本,因為 MCP Server 通常會使用 TypeScript/JavaScript 進行開發。
  • TypeScript 知識:MCP SDK 通常以 TypeScript 提供,熟悉 TypeScript 將有助於開發和理解程式碼。
  • SQL Server 實例:一個可訪問的本地 SQL Server 實例,包含您希望 AI 存取的資料庫和資料表。
  • SQL Server 連接資訊:包括伺服器位址、資料庫名稱、使用者名稱和密碼。
  • MCP SDK:安裝官方提供的 @modelcontextprotocol/sdk 套件。
  • 基礎網路知識:理解本地網路通訊和埠號。
  • AI 工具:一個支援 MCP 的 AI Host 應用程式,例如 Claude Desktop 或 VS Code 的 AI 擴充功能,用於測試您的 MCP Server。

4. Implementation / Code Example

本節將提供一個使用 TypeScript 和 Node.js 實作 MCP Server 的範例,該 Server 將暴露三個 Tools,允許 AI 代理與本地 SQL Server 進行互動:list_tables (列出所有資料表)、describe_table (描述特定資料表的 Schema) 和 query_database (執行 SQL 查詢)。

4.1 專案初始化

首先,創建一個新的 Node.js 專案並安裝必要的套件:

mkdir mcp-sql-server-agent
cd mcp-sql-server-agent
npm init -y
npm install typescript @types/node @modelcontextprotocol/sdk mssql @types/mssql dotenv
npx tsc --init

tsconfig.json 中,確保 target 設置為 es2020 或更高,module 設置為 commonjsesnext

4.2 配置 SQL Server 連接

創建 .env 檔案來儲存敏感的資料庫連接資訊:

SQL_SERVER_HOST=localhost
SQL_SERVER_PORT=1433
SQL_SERVER_DATABASE=YourDatabaseName
SQL_SERVER_USER=YourUsername
SQL_SERVER_PASSWORD=YourPassword

4.3 實作 MCP Server (src/server.ts)

import { Server, Tool, RequestHandler } from "@modelcontextprotocol/sdk";
import * as mssql from "mssql";
import * as dotenv from "dotenv";

dotenv.config(); // 加載 .env 檔案

// SQL Server 配置
const sqlConfig: mssql.config = {
  user: process.env.SQL_SERVER_USER,
  password: process.env.SQL_SERVER_PASSWORD,
  server: process.env.SQL_SERVER_HOST || "localhost", // 您 SQL Server 的 IP 或主機名
  port: parseInt(process.env.SQL_SERVER_PORT || "1433"),
  database: process.env.SQL_SERVER_DATABASE,
  options: {
    encrypt: false, // For Azure SQL Database, set true
    trustServerCertificate: true, // Change to false for production
  },
};

// 創建一個 SQL Server 連接池
let pool: mssql.ConnectionPool | undefined;

async function getSqlConnection() {
  if (!pool || !pool.connected) {
    try {
      pool = new mssql.ConnectionPool(sqlConfig);
      await pool.connect();
      console.log("Connected to SQL Server");
    } catch (err) {
      console.error("SQL Server Connection Failed:", err);
      throw new Error("Failed to connect to SQL Server");
    }
  }
  return pool;
}

// 1. Tool: 列出所有資料表
const listTablesTool: Tool = {
  name: "list_tables",
  description: "列出當前資料庫中所有可用的資料表名稱。",
  parameters: {
    type: "object",
    properties: {},
    required: [],
  },
  returns: {
    type: "array",
    items: {
      type: "string",
    },
  },
};

const listTablesHandler: RequestHandler<typeof listTablesTool> = async () => {
  const pool = await getSqlConnection();
  const request = pool.request();
  const result = await request.query(
    "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_CATALOG = @databaseName"
  );
  return result.recordset.map((row) => row.TABLE_NAME);
};

// 2. Tool: 描述特定資料表的 Schema
const describeTableTool: Tool = {
  name: "describe_table",
  description: "獲取指定資料表的欄位名稱、資料類型和是否允許為空。",
  parameters: {
    type: "object",
    properties: {
      tableName: {
        type: "string",
        description: "要描述的資料表名稱。",
      },
    },
    required: ["tableName"],
  },
  returns: {
    type: "array",
    items: {
      type: "object",
      properties: {
        columnName: { type: "string" },
        dataType: { type: "string" },
        isNullable: { type: "boolean" },
      },
    },
  },
};

const describeTableHandler: RequestHandler<typeof describeTableTool> = async ({
  tableName,
}) => {
  const pool = await getSqlConnection();
  const request = pool.request();
  request.input("tableName", mssql.NVarChar, tableName);
  const result = await request.query(
    `SELECT COLUMN_NAME as columnName, DATA_TYPE as dataType, IS_NULLABLE as isNullable
     FROM INFORMATION_SCHEMA.COLUMNS
     WHERE TABLE_NAME = @tableName AND TABLE_CATALOG = @databaseName`
  );
  return result.recordset.map((row) => ({
    columnName: row.columnName,
    dataType: row.dataType,
    isNullable: row.isNullable === "YES",
  }));
};

// 3. Tool: 執行 SQL 查詢 (唯讀)
const queryDatabaseTool: Tool = {
  name: "query_database",
  description: "執行一個 SQL SELECT 查詢並返回結果。僅支援 SELECT 語句。",
  parameters: {
    type: "object",
    properties: {
      query: {
        type: "string",
        description: "要執行的 SQL SELECT 查詢語句。",
      },
    },
    required: ["query"],
  },
  returns: {
    type: "array",
    items: {
      type: "object", // 返回任意 JSON 物件陣列
    },
  },
};

const queryDatabaseHandler: RequestHandler<typeof queryDatabaseTool> = async ({
  query,
}) => {
  if (!query.trim().toLowerCase().startsWith("select")) {
    throw new Error("Only SELECT queries are allowed for security reasons.");
  }
  const pool = await getSqlConnection();
  const request = pool.request();
  // 注意:這裡直接執行查詢,實際生產環境應考慮更嚴格的 SQL 注入防護和權限控制。
  // 建議使用參數化查詢或預處理語句來處理使用者輸入。
  const result = await request.query(query);
  return result.recordset;
};

async function main() {
  const server = new Server();

  // 註冊 Tools
  server.registerTool(listTablesTool, listTablesHandler);
  server.registerTool(describeTableTool, describeTableHandler);
  server.registerTool(queryDatabaseTool, queryDatabaseHandler);

  // 啟動 Server
  const port = 3000; // 您可以自定義埠號
  await server.start(port);
  console.log(`MCP Server started on port ${port}`);

  // 為了讓 AI 代理知道如何使用這些 Tools,您需要提供 Prompts。
  // 這些 Prompts 通常會被 AI Host 應用程式讀取並用於引導 AI 的行為。
  // 範例 Prompt (這部分通常在 AI Host 端配置,或透過 MCP Server 的 Resource 暴露):
  console.log("\n--- 範例 AI Prompt ---");
  console.log(
    "你是一個專業的資料庫分析師。當使用者需要查詢本地 SQL Server 資料時,請使用以下工具:"
  );
  console.log("- `list_tables()`: 列出所有資料表。");
  console.log(
    "- `describe_table(tableName: string)`: 獲取特定資料表的 Schema。"
  );
  console.log("- `query_database(query: string)`: 執行 SQL SELECT 查詢。");
  console.log(
    "請務必先使用 `list_tables` 和 `describe_table` 來理解資料庫結構,然後再執行 `query_database`。"
  );
  console.log("--- 範例 AI Prompt ---");
}

main().catch(console.error);

4.4 編譯與運行

npx tsc
node dist/server.js

4.5 與 AI Host 整合 (以 Claude Desktop 為例)

  1. 配置 claude_desktop_config.json: 在您的使用者目錄下(例如 ~/.claude_desktop/claude_desktop_config.json),添加以下配置,指向您的 MCP Server:

    {
      "mcp_servers": [
        {
          "name": "Local SQL Server Agent",
          "url": "http://localhost:3000", // 您的 MCP Server 運行地址和埠號
          "description": "提供對本地 SQL Server 資料庫的查詢和結構檢視能力。"
        }
      ]
    }
    
  2. 重啟 Claude Desktop:確保配置生效。

  3. 與 AI 互動:現在,您可以在 Claude Desktop 中向 AI 提問,例如:「我的本地資料庫裡有哪些資料表?」或「請查詢 Products 表中價格大於 100 的所有產品。」AI 將會自動呼叫您的 MCP Server 提供的 Tools 來獲取答案。

5. Parameters / API Reference

MCP Server 的 API 參考主要體現在其暴露的 Tools 定義上。這些 Tools 的 namedescriptionparametersreturns 結構,共同構成了 AI 代理理解和使用這些功能的介面。

5.1 MCP SDK 核心介面

介面/類別描述
ServerMCP Server 的主類別,用於註冊 Tools 和啟動 Server。
Tool定義一個 AI 可呼叫的工具,包含名稱、描述、輸入參數和預期返回值。
RequestHandler處理 Tool 呼叫的非同步函數,接收 Tool 的參數並返回結果。
Resource定義一個 AI 可讀取的靜態資源,包含名稱、描述和內容。

5.2 本地 SQL Server Agent 暴露的 Tools

Tool 名稱描述輸入參數返回值
list_tables列出當前資料庫中所有可用的資料表名稱。Array<string> (資料表名稱陣列)
describe_table獲取指定資料表的欄位名稱、資料類型和是否允許為空。tableName: string (要描述的資料表名稱)Array<{ columnName: string, dataType: string, isNullable: boolean }>
query_database執行一個 SQL SELECT 查詢並返回結果。僅支援 SELECT 語句。query: string (要執行的 SQL SELECT 查詢語句)Array<Object> (查詢結果的 JSON 物件陣列)

6. Notes & Best Practices

  1. 安全性至上
    • 最小權限原則:為 MCP Server 連接 SQL Server 的帳戶配置最小必要的權限,例如只讀取特定資料庫或資料表,避免使用 sa 帳戶。
    • SQL 注入防護:在 query_database Tool 中,直接執行 AI 生成的 SQL 查詢存在 SQL 注入風險。在生產環境中,應嚴格限制 AI 只能生成預定義的查詢模板,或使用參數化查詢來處理使用者輸入,絕不能直接拼接字串 [7]。
    • 資料量限制:在 query_database Tool 的實作中,應限制查詢結果的行數(例如使用 TOP 100),以防止 AI 請求過大的數據量導致上下文溢出或性能問題。
    • 網路隔離:考慮將 MCP Server 部署在受保護的內部網路中,僅允許受信任的 AI Host 應用程式訪問。
  2. 錯誤處理與日誌
    • 在每個 Tool 的 RequestHandler 中,應包含完善的錯誤處理機制,捕獲資料庫連接失敗、查詢語法錯誤等異常,並返回清晰的錯誤訊息給 AI 代理。
    • 實作詳細的日誌記錄,記錄 AI 代理的 Tool Call 請求、執行結果和任何錯誤,以便於調試和審計。
  3. Prompts 優化
    • 為 AI 代理提供清晰、詳細的 Prompts,明確說明每個 Tool 的功能、輸入輸出和使用限制。例如,強調 query_database 僅支援 SELECT 語句。
    • 引導 AI 代理在執行查詢前,先使用 list_tablesdescribe_table 來理解資料庫結構,以減少錯誤的 Tool Call。
  4. 性能考量
    • 使用資料庫連接池來管理 SQL Server 連接,避免頻繁建立和關閉連接,提升性能。
    • 對於複雜或耗時的查詢,考慮在 Tool 內部進行優化,或提供非同步的 Tool 執行機制。
  5. 版本控制與可維護性
    • 將 MCP Server 的程式碼納入版本控制系統,並遵循良好的程式碼編寫規範。
    • 隨著資料庫結構或 AI 代理需求的變化,定期更新和維護 MCP Server 的 Tools 定義和實作。

7. 為什麼選擇這種方式?

在 AI 代理與本地資料互動的場景中,選擇實作 Model Context Protocol (MCP) 標準的 AI 代理伺服器,具有以下不可替代的優勢:

  1. 標準化與互通性:MCP 作為一個開放標準,確保了您的 MCP Server 可以與任何支援 MCP 的 AI Host 應用程式(如 Claude Desktop、Cursor、VS Code 等)無縫協作 [1]。這避免了為每個 AI 工具開發獨立整合的重複工作,極大地提升了互通性和開發效率。
  2. 資料隱私與安全性:透過 MCP,您的敏感本地資料無需上傳到雲端或第三方服務。AI 代理僅透過本地運行的 MCP Server 進行互動,資料始終保留在您的控制之下。MCP Server 可以實作精細的權限控制和資料過濾,確保 AI 代理只能存取其被授權的資料和執行被允許的操作,有效防範資料洩露風險 [8]。
  3. 增強 AI 上下文與準確性:AI 代理可以透過 MCP Server 即時獲取最新的本地資料,這為其提供了遠超訓練資料的豐富上下文。這種「即時 RAG」能力使得 AI 能夠生成更精確、更相關的回應,特別是在處理不斷變化的業務數據或個人筆記時 [9]。
  4. 消除手動操作,提升效率:MCP 讓 AI 代理能夠自動執行查詢、分析和操作本地數據,從而消除了開發者手動複製貼上、切換應用程式的繁瑣步驟。這將顯著提升開發、數據分析和日常工作的效率。
  5. Agentic AI 的基石:MCP 是構建真正 Agentic AI 的關鍵基礎設施之一。它賦予 AI 代理「行動」的能力,使其不僅能理解和生成文本,還能與真實世界的工具和數據進行有意義的互動,開啟了 AI 應用程式的全新可能性。

實作心得

實際把 MCP Server 接上 Claude Desktop 時,踩到的第一個坑是 @modelcontextprotocol/sdk 的版本問題。早期版本的 Tool 型別定義和最新規格有落差,照著官方文件寫卻一直拿到 schema validation error,後來才發現要明確指定 npm 套件版本。

另一個意想不到的問題是本地 SQL Server 的防火牆設定。MCP Server 跑在 localhost,Claude Desktop 連過來時走的是同一台機器的 loopback,但如果 SQL Server 的 TCP/IP 協定沒有明確啟用,仍然會連線失敗,訊息還很難排查。建議在開始整合前,先用 sqlcmd 確認連線正常再開始寫 MCP 程式碼。

最讓我印象深刻的是 query_database 這個 Tool 的安全設計決策。文章中提到只允許 SELECT,但在實際使用時,AI 有時候會嘗試用 subquery 或 CTE 做複雜查詢,結果超出了 TOP 100 的限制或讓查詢計畫變得很差。後來改成在 Tool 層面直接加上 SET ROWCOUNT 200 的前綴,並且限制查詢時間,才真正把「AI 能做什麼」的邊界控制好。整體來說,MCP 是目前最乾淨的 AI 與本地工具整合方式,值得投入時間建立自己的 Tool 庫。


參考資料