回到頂部
MCP Server 開發教學:從零打造串接協議 — 封面

MCP Server 開發教學:從零打造串接協議

MCP 開發完整教學——從零打造 MCP Server,把 API、資料庫接進 Claude、ChatGPT,擴展 AI 能力。

MCP Server 開發是 2026 年 AI 工程師最值得學的新技能之一。MCP(Model Context Protocol)是 Anthropic 推出的開放協議,讓 AI 工具(Claude、ChatGPT 等)能用統一的方式串接任何外部資源。會寫 MCP Server,你就能把公司的 CRM、資料庫、內部 API 變成 AI 可以直接呼叫的「超能力」——這是 RAG 之外另一條重要的企業 AI 落地路徑。

MCP 到底是什麼?一句話講完

MCP 本質上是「AI 工具界的 USB-C」——一套讓 AI 模型、工具、資料源之間能互通的標準介面。過去每個 AI 產品都要為自家接入系統自己設計 plugin 格式,現在有了 MCP,同一個 Server 可以被 Claude、Cursor、AI Agent 同時使用。

對一般 Claude、ChatGPT 使用者來說,MCP 的意義在於「讓 AI 真的能動手做事」,而不只是回答問題。沒有 MCP 之前,你問 Claude「我這個月公司訂單有幾筆」,它只能說「我沒辦法存取你的資料庫」;有了 MCP,它可以實際呼叫你內部 ERP 的查詢工具,回傳真實數字。這個差異——從「會聊天的助理」變成「能執行任務的員工」——就是 2025 年以後整個 AI Agent 生態 爆發的底層基礎建設。

舉個具體對比:之前你要讓 AI 幫你寫週報,得自己複製貼上本週的 Slack 對話、GitHub commit、Jira ticket,然後請 AI 幫你整理。有了 MCP,你只要說「幫我寫本週週報」,AI 會自己呼叫 Slack MCP、GitHub MCP、Jira MCP 收集資料,然後生出初稿。從 15 分鐘的資料搬運,變成 30 秒的一句話指令。

🔌 為什麼要自己寫 MCP Server?

MCP 讓 AI 以統一方式連接工具。社群已有數百個現成 Server,但當你需要連接自己公司的內部系統——你得自己寫。

💡 一句話理解 寫 MCP Server = 幫 AI 裝一個「插頭」,讓它能存取你的資料和工具。

什麼場景需要自己寫

場景現成 MCP Server需要自己寫
連接 GitHub✅ 有
連接 Notion✅ 有
連接公司內部 ERP✅ 自己系統
連接自建資料庫⚠️ 通用的有✅ 需客製查詢
公司 API 做成 AI 工具✅ 自己 API

情境 A:內部知識庫——把公司 Notion / Confluence 接進 Claude

100 人以上的公司,內部文件通常散在 Notion、Confluence、Google Drive、SharePoint 之間。新員工花兩週才搞懂「這份 SOP 在哪」是常態。用 MCP 把這些文件源接進 Claude 之後,員工可以直接問「退貨流程在哪份文件?」,Claude 會跨平台搜尋後回傳連結與摘要。比起自己做一套 RAG 系統,MCP 的優勢是不用搬資料——文件還留在原平台,權限、更新、備份全照舊,Claude 只是「借看」。實務上這種 Server 通常 300-500 行程式碼就能跑,ROI 遠高於自建 RAG。詳細可參考 Notion AI 應用 的整合思路。

情境 B:資料庫查詢——讓 AI 安全查 read-only SQL

BI 分析師一天被問 20 次「上週業績多少」、「哪個產品退貨率最高」,每次都要手動寫 SQL。把資料庫包成 MCP Server(限制為 read-only),老闆自己可以用自然語言問 Claude,AI 會轉譯成 SQL 執行後回傳結果。關鍵是權限控制要在 MCP Server 層做好:只開 SELECT、限制可查詢的 table、加 row limit、記錄所有查詢 log。這等於是給 AI 一個受限的資料庫帳號,比直接給它 admin 權限安全 10 倍。這類應用比 ChatGPT 的 Code Interpreter 強,因為是接到你自己的生產資料,不是上傳 CSV。


🏗️ MCP Server 架構

Claude Desktop / Cursor / AI Agent(MCP Host)
        ↕ JSON-RPC over stdio / SSE
你的 MCP Server

你的工具 / 資料
(資料庫、API、檔案系統⋯)

MCP Server 提供三種能力

能力說明範例
ToolsAI 可以呼叫的功能搜尋商品、建立訂單
ResourcesAI 可以讀取的資料資料庫內容、設定檔
Prompts預設的提示模板「分析報表」模板

Transport 選項比較:stdio vs HTTP vs WebSocket

MCP 協議支援三種 transport,選哪一種會直接影響部署方式與安全模型:

Transport適用場景優點缺點
stdio本機 Desktop 應用(Claude Desktop、Cursor)安全、零網路設定、啟動快只能本機用,無法跨機器共用
HTTP (SSE)團隊共用、遠端部署跨機器、可加認證要做 auth、HTTPS、防火牆設定
WebSocket雙向即時通訊場景延遲低、可 push 事件實作複雜、相容性差

個人與小團隊 90% 用 stdio 就夠——Claude Desktop 直接 spawn 你的 Server 程序,透過標準輸入輸出交換 JSON-RPC 訊息,沒有網路通訊,也就沒有被中間人攻擊的風險。只有當你需要「一個 Server 給整個團隊的 Claude 用」時才考慮 HTTP 版本。

安全模型:為什麼 stdio 預設比 HTTP 安全

stdio 模式下,MCP Server 以「使用者本人」的權限跑在本機,外部無法連進來——這等同於你自己啟動的任何 CLI 工具。而 HTTP 模式一旦開出去,就是一個「讓 AI 呼叫的 API」,必須面對所有 Web 安全議題:auth token、CORS、rate limiting、injection 防護。企業環境如果要用 HTTP 版 MCP Server,通常會擺在 VPN 後面或配合 OAuth,不會直接開到公網。

Client-Server handshake 怎麼運作

MCP 協議啟動時會走三步握手:①Client 送 initialize 請求告知自己支援的協議版本與能力;②Server 回傳自己支援的版本、可用的 tools / resources / prompts 清單;③Client 確認後進入正常運作,此後每次 AI 要呼叫工具就送 tools/call,Server 回傳結果。這個機制讓 Client 可以「自我發現」Server 有什麼工具,你改 Server 加了新 tool,不用改 Client 設定,重啟就認得。


🚀 TypeScript 實作

安裝

npm init -y
npm install @modelcontextprotocol/sdk zod

最小 MCP Server

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

// 建立 Server
const server = new McpServer({
  name: "my-company-tools",
  version: "1.0.0",
});

// 定義工具:查詢公司產品
server.tool(
  "search_products",
  "搜尋公司產品目錄,回傳符合條件的商品",
  {
    keyword: z.string().describe("搜尋關鍵字"),
    category: z.enum(["electronics", "clothing", "food"])
      .optional()
      .describe("商品類別"),
    max_price: z.number().optional().describe("最高價格(台幣)"),
  },
  async ({ keyword, category, max_price }) => {
    // 你的實際搜尋邏輯(呼叫內部 API 或查資料庫)
    const results = await searchProductsFromDB(keyword, category, max_price);

    return {
      content: [{
        type: "text",
        text: JSON.stringify(results, null, 2),
      }],
    };
  }
);

// 定義工具:建立訂單
server.tool(
  "create_order",
  "建立新訂單",
  {
    product_id: z.string().describe("商品 ID"),
    quantity: z.number().min(1).describe("數量"),
    customer_name: z.string().describe("客戶名稱"),
  },
  async ({ product_id, quantity, customer_name }) => {
    const order = await createOrderInDB(product_id, quantity, customer_name);
    return {
      content: [{
        type: "text",
        text: `訂單建立成功!訂單編號:${order.id}`,
      }],
    };
  }
);

// 定義資源:公司政策文件
server.resource(
  "company://policies/return",
  "退貨政策",
  "text/plain",
  async () => ({
    contents: [{
      uri: "company://policies/return",
      text: "退貨政策:購買後 7 天內可無條件退貨...",
    }],
  })
);

// 啟動
const transport = new StdioServerTransport();
await server.connect(transport);

程式碼關鍵部分解說

  • server.tool() 的第二參數描述(description)是整個 MCP 的靈魂。AI 完全靠這段中文決定「要不要用這個工具」、「什麼情境下用」。寫得像 docstring 沒用,要寫得像 onboarding SOP——告訴 AI「這是做什麼、什麼時候用、會回傳什麼」。
  • z.enum()z.number().min(1) 這類 schema 約束很重要。AI 會看到這些限制並自動遵守,省掉你在業務邏輯裡重複驗證。善用 Zod 的 refine、transform 可以把輸入預處理也丟給 Schema 層。
  • 回傳格式永遠是 content: [{ type: "text", text: ... }]。如果要回傳結構化資料,建議用 JSON.stringify(obj, null, 2) 包起來——AI 解析 JSON 的能力很強,不需要你先幫它拆成人類語言。
  • StdioServerTransport 決定了這是 stdio 版。改成 HTTP 版只要換成 SSEServerTransport,其他程式碼不變。

開發過程常見錯誤

  • Cannot find module '@modelcontextprotocol/sdk':npm install 裝到錯的 package,正確套件名是 @modelcontextprotocol/sdk(有 scope)。
  • ZodError: Required:schema 要求必填但 AI 沒傳——多半是 description 寫得太模糊,AI 不知道該填什麼。
  • Claude Desktop 看不到你的 Server:99% 是設定檔路徑寫錯,或是 command 用了相對路徑(必須用絕對路徑)。
  • TypeScript 編譯後跑不起來:別忘了 tsc 編譯後 claude_desktop_config.json 要指向 .js 檔(不是 .ts)。

如何擴充這個骨架

加新工具只要再呼叫一次 server.tool()。想回傳圖片?用 type: "image" 搭配 base64。要做 streaming 回應?看 SDK 的 progressCallback。要讓 tool 之間共享狀態(例如快取),在 Server 物件外包一層 closure 即可。


🐍 Python 實作

安裝

pip install mcp

最小 MCP Server

from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent
import json

server = Server("my-company-tools")

# 定義工具列表
@server.list_tools()
async def list_tools():
    return [
        Tool(
            name="search_products",
            description="搜尋公司產品目錄",
            inputSchema={
                "type": "object",
                "properties": {
                    "keyword": {
                        "type": "string",
                        "description": "搜尋關鍵字"
                    },
                    "max_price": {
                        "type": "number",
                        "description": "最高價格(台幣)"
                    }
                },
                "required": ["keyword"]
            }
        ),
        Tool(
            name="get_order_status",
            description="查詢訂單狀態",
            inputSchema={
                "type": "object",
                "properties": {
                    "order_id": {
                        "type": "string",
                        "description": "訂單編號"
                    }
                },
                "required": ["order_id"]
            }
        )
    ]

# 處理工具呼叫
@server.call_tool()
async def call_tool(name: str, arguments: dict):
    if name == "search_products":
        results = search_products_from_db(
            arguments["keyword"],
            arguments.get("max_price")
        )
        return [TextContent(
            type="text",
            text=json.dumps(results, ensure_ascii=False)
        )]

    elif name == "get_order_status":
        status = get_order_from_db(arguments["order_id"])
        return [TextContent(
            type="text",
            text=f"訂單 {arguments['order_id']} 狀態:{status}"
        )]

# 啟動
async def main():
    async with stdio_server() as (read, write):
        await server.run(read, write, server.create_initialization_options())

import asyncio
asyncio.run(main())

Python 版本關鍵部分解說

  • @server.list_tools()@server.call_tool() 是兩階段 handshake 的實作。Claude 連上來先問「你有什麼 tool?」(list_tools),決定要呼叫時再送 call_tool。這跟 TypeScript 版 server.tool() 一次註冊完不同,Python 版更貼近原始 JSON-RPC 訊息。
  • inputSchema 用標準 JSON Schema 格式。沒有 Zod,得自己寫 JSON Schema dict,所以「required」、「properties」這些欄位要手刻。若熟悉 結構化輸出 概念,這塊會很好理解。
  • asyncio.run(main()) + stdio_server() async context manager 是 MCP Python SDK 的標準啟動模式。別試圖改成同步版本,底層 JSON-RPC 是 async IO。

Python 版常見錯誤

  • ModuleNotFoundError: No module named 'mcp':新版套件叫 mcp,舊教學可能寫 model-context-protocol,別裝錯。
  • TypeError: async ... takes 0 positional arguments but 1 was given:@server.call_tool() 裝飾的函式簽章必須是 (name: str, arguments: dict),改名不行。
  • JSON serialization error:回傳的資料含 datetime、Decimal 這類非 JSON-native type,要先轉 str/float。
  • Windows 路徑反斜線:Python 版常被 Windows \ 路徑搞壞,記得用 raw string 或 forward slash。

擴充:回傳結構化資料

上面範例用 TextContent 包 JSON 字串。要回傳圖片、檔案,改用 ImageContentEmbeddedResource。要動態載入 tool list(例如根據使用者權限給不同工具),list_tools 函式內部做條件判斷即可——MCP SDK 不限制 tool 清單必須是靜態的。


⚙️ 在 Claude Desktop 中使用

寫好 MCP Server 後,在 Claude Desktop 設定檔中註冊:

macOS

// ~/Library/Application Support/Claude/claude_desktop_config.json
{
  "mcpServers": {
    "my-company-tools": {
      "command": "node",
      "args": ["/path/to/your/server/index.js"]
    }
  }
}

Windows

// %APPDATA%\Claude\claude_desktop_config.json
{
  "mcpServers": {
    "my-company-tools": {
      "command": "node",
      "args": ["C:\\path\\to\\your\\server\\index.js"]
    }
  }
}

在 Cursor 中使用

// .cursor/mcp.json(專案根目錄)
{
  "mcpServers": {
    "my-company-tools": {
      "command": "node",
      "args": ["./mcp-server/index.js"]
    }
  }
}

Log 在哪裡?出問題第一步去哪看

Claude Desktop 的 MCP log 位置:

  • macOS:~/Library/Logs/Claude/mcp-server-{server-name}.log
  • Windows:%APPDATA%\Claude\logs\mcp-server-{server-name}.log

Server 啟動失敗、工具執行出錯、schema 驗證失敗,全都寫在這裡。開發時建議另開一個 terminal 跑 tail -f 監控。

macOS 常見權限問題

macOS 的 Security & Privacy 可能擋住你的 MCP Server 存取「文件」、「桌面」、「磁碟完整取用權」。症狀是 Server 明明有跑,但讀檔都回傳 permission denied。到「系統設定 > 隱私權與安全性 > 完整磁碟取用權」把 Claude Desktop 勾起來,或是把你的 Server 執行路徑(nodepython3)加進清單。另一個坑是 commandnode 但 Claude Desktop 環境變數沒有 PATH,找不到 node——解法是直接用絕對路徑 /usr/local/bin/node/opt/homebrew/bin/node

怎麼確認 MCP Server 真的有被載入

重啟 Claude Desktop 後,最可靠的方法有三:

  1. 看設定頁:Claude Desktop 右下角設定圖示 → 「Developer」分頁,會顯示已載入的 MCP Server 與狀態(🟢 綠點表示 connected)。
  2. 直接問 AI:「你現在有哪些可用的工具?」Claude 會列出已載入的 tool 清單,沒看到你的就是沒載入。
  3. 看 log:上面那個 log 檔如果完全空,代表 Server 根本沒被 spawn——通常是設定檔 JSON 語法錯或路徑錯。

🧪 測試你的 MCP Server

用 MCP Inspector

npx @modelcontextprotocol/inspector node ./index.js

Inspector 提供一個 Web UI,讓你可以:

  • 查看所有 Tools 和 Resources
  • 手動呼叫工具並查看回應
  • 偵錯 JSON-RPC 通訊

沒裝 Inspector 的手動測試流程

Inspector 是最方便,但有時你想快速測一下也可以用 pipe 直接戳。開另一個 terminal,執行:

echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | node ./index.js

會看到 Server 回傳 JSON。想測 tool 呼叫,送 {"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"search_products","arguments":{"keyword":"手機"}}}。這個方式對 CI/CD 自動化測試特別實用——直接 pipe 進去比對輸出。

Integration testing 策略

把 MCP Server 的 tool handler 抽成純 function(與 MCP 協議解耦),單元測試只測 function 本身。整合測試時另起一個 MCP Client SDK 去連自己的 Server,呼叫 tool 後驗證回應。Anthropic SDK 就有內建 MCP Client,可以寫 MCPClient.connect(spawn('node', ['./index.js'])) 做 e2e 測試。

Deploy 前用 Claude 實測

最後一步一定要用真正的 Claude Desktop 跑。重點測試三件事:①AI 有沒有在「應該用工具」的時機主動呼叫(description 寫得好不好)、②AI 傳進來的參數值合不合理、③多輪對話時 AI 會不會誤用上一輪的結果。這一步會逼你改 description 至少三輪才會穩定。實務經驗可參考 AI Agent 實作教學API 整合指南


🛡️ MCP Server 安全實務

MCP Server 等於「給 AI 一支可以操作你系統的遙控器」,安全性不能事後補。以下 6 項開發時就要想清楚:

1. 不要回傳敏感欄位。 AI 的回應可能出現在 Log、對話截圖、分享連結中。查使用者資料時,明確過濾掉 password_hashapi_keycredit_cardssn 這類欄位。資料庫查詢用 SELECT name, email 而不是 SELECT *,原則上「AI 不需要看到的,就不該傳給它」。

2. Input validation——不要相信 Claude 傳進來的參數。 AI 不是惡意,但它會「幻想」出你沒定義的參數。用 Zod / JSON Schema 嚴格檢查型別、範圍、enum,拒絕任何不符合的輸入。字串類參數特別小心 SQL injection 與 path traversal——即使是 AI 傳進來的,也要當成 untrusted input。

3. Rate limiting——防止 Claude 進入無窮迴圈。 AI 跑錯邏輯時可能一秒內呼叫同個 tool 100 次,打爆你的資料庫或第三方 API 額度。建議在 Server 內加 per-tool rate limit(例如每分鐘 30 次),超過就回傳錯誤訊息讓 AI 知道「不要再試了」。

4. Read-only vs Write 操作明確區分。 查詢類工具(list、get、search)跟變更類工具(create、update、delete)要在命名與權限上區分清楚。更好的做法是 write 類工具加一個 dry_run: boolean 參數,讓 AI 先預覽要改什麼、再由人類確認送出——這對企業落地非常關鍵。

5. Audit log——記錄每一次 tool call。 寫入 timestamp、tool name、arguments、結果、耗時,存到獨立 log 檔或集中 log 系統。出問題時才能追「AI 是什麼時候、用什麼參數、做了什麼事」。出事沒 log 跟沒做一樣。

6. 環境變數 vs 硬編碼。 資料庫連線字串、API token、加密金鑰一律從環境變數讀,絕不寫死在程式碼。Claude Desktop 設定檔可以指定 env 欄位,把敏感值注進 Server 程序。程式碼可以進 git,.env 絕不進 git。


📋 MCP Server 開發 Checklist

  • ✅ Tool 的 description 要清楚——AI 靠此決定要不要用
  • ✅ Parameter 的 description 要精確——AI 靠此填入參數
  • ✅ 用 enum 約束可列舉的參數值
  • ✅ 有錯誤處理——Tool 失敗時回傳有意義的錯誤訊息
  • ✅ 敏感操作加確認——刪除、修改等操作要小心
  • ✅ 用 Inspector 測試過所有工具
  • ✅ 在 Claude Desktop 或 Cursor 中實際測試

❓ FAQ

MCP 跟普通 REST API 差在哪?

REST API 要 AI 先「知道」API 格式才會用——你得把 endpoint、schema、認證方式塞進 prompt 裡。MCP 反過來:Server 主動自我介紹,AI 透過 tools/list 自動發現能力。而且 MCP 是雙向 JSON-RPC 而非單向 HTTP,支援 streaming、progress 回報、取消。一個 MCP Server 可以被所有支援 MCP 的 AI 使用——不用為每個 AI 各寫一次。可以想成「REST = 給工程師用的 API、MCP = 給 AI 用的 API」。

非 TypeScript / Python 能寫 MCP Server 嗎?

可以。MCP 是協議不是函式庫——只要你能收送 JSON-RPC 訊息都能寫。官方目前除了 TypeScript、Python SDK,還有 Go、Rust、Kotlin、C# 的社群 SDK。若用沒有 SDK 的語言,自己處理 stdio 讀寫 JSON-RPC 2.0 格式也能跑,大概 100-200 行就能做最小版。

Claude Desktop 收不到 MCP Server 怎麼辦?

按順序檢查:①設定檔 JSON 語法對不對(用 jsonlint 驗)、②command 是否用絕對路徑、③完全退出 Claude Desktop(不是關視窗,要 Cmd+Q)再重開、④看 ~/Library/Logs/Claude/mcp-server-*.log 有沒有錯誤訊息、⑤直接在 terminal 用相同指令跑你的 Server,確認能啟動。80% 問題都是 path 錯或忘了完全重啟。

MCP Server 可以部署到雲端嗎?

可以,用 HTTP / SSE transport 版本。主流做法是 Docker 化後部署到 Cloud Run、Fly.io 或 ECS,前面加 API gateway 做認證。要注意雲端版的安全模型跟本地完全不同——必須加 OAuth / API key、HTTPS、rate limit、IP allowlist。企業建議擺在 VPN 內部,不要直接開到公網。

怎麼把現有的 REST API 包成 MCP Server?

概念上就是寫一個「代理 Server」,每個你想暴露給 AI 的 endpoint 對應一個 MCP tool。Tool handler 內部用 fetch / requests 呼叫你的 REST API,把結果轉成 MCP 格式回傳。重點是不要把所有 endpoint 一對一包過來——AI 不需要 200 個工具,只需要 10-15 個精選的高價值 action。挑選原則是「AI 問使用者常問的問題時,最常需要的那幾個動作」。

MCP 協議版本相容性?

MCP 協議目前穩定在 2024-11-05 版本(到 2026 Q1 為止),Client 與 Server 在 handshake 時會協商版本。SDK 通常會向下相容最近兩個版本。實務上只要固定住 SDK 版本、在 CI 跑整合測試,版本問題不大。Anthropic 承諾破壞性變更至少提前一個月公告。

企業環境要怎麼管多個 MCP Server?

大公司通常會有 5-20 個 MCP Server(每個系統一個)。管理方式:①集中用 config management(Ansible / Puppet) 推設定檔、②內部包一個「MCP Gateway」當單一入口,背後路由到各 Server、③用 container 化打包,統一 deploy、④維護中央 tool registry 記錄每個 Server 提供什麼 tool、誰負責維護。可搭配 Claude CodeClaude Code 基礎教學 做開發流程整合。

哪些 AI 支援 MCP?

Claude Desktop(原生最完整)、Cursor(AI IDE)、Windsurf、以及越來越多的 Agent 框架。OpenAI 和 Google 尚未正式支援但在評估中。

📚 延伸閱讀