回到頂部

🔌 MCP Server 開發教學

從零打造你的 MCP Server——讓 AI 連接你的工具和資料。

🔌 為什麼要自己寫 MCP Server?

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

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

什麼場景需要自己寫

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

🏗️ MCP Server 架構

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

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

MCP Server 提供三種能力

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

🚀 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);

🐍 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())

⚙️ 在 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"]
    }
  }
}

🧪 測試你的 MCP Server

用 MCP Inspector

npx @modelcontextprotocol/inspector node ./index.js

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

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

📋 MCP Server 開發 Checklist

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

❓ FAQ

MCP Server 和普通 API 有什麼不同?

MCP Server 透過標準化的 JSON-RPC 協議和 AI 溝通,不需要 AI 知道你的 API 格式。你定義工具的 schema 和描述,AI 自動知道怎麼使用。而且一個 MCP Server 可以被所有支援 MCP 的 AI 使用——不用為每個 AI 各寫一次。

MCP Server 要部署到哪裡?

目前主流是本地 stdio 方式(MCP Host 直接啟動你的 Server 程序)。也支援 SSE(HTTP),可以部署到遠端伺服器。本地方式最簡單,適合個人和開發階段;SSE 方式適合團隊共用。

哪些 AI 支援 MCP?

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

📚 延伸閱讀