用 Python 開發 MCP Server

在前一篇文章中,我們學會了如何使用現有的 MCP Server 來擴展 Claude 的能力。但如果現有的 Server 無法滿足你的需求,或者你想為團隊打造專屬的工具整合,那就需要自己動手開發 MCP Server 了。本篇將帶你使用 Python 搭配官方的 MCP SDK 與 FastMCP 框架,從零開始建構一個功能完整的 MCP Server。

為什麼選擇 Python 開發 MCP Server

MCP Server 可以使用多種語言開發,官方提供了 TypeScript 和 Python 兩套 SDK。選擇 Python 有幾個明確的優勢:

  • 語法簡潔直覺:Python 的 decorator 語法讓定義 Tool 和 Resource 變得非常優雅,幾行程式碼就能完成一個工具
  • 豐富的生態系:Python 擁有大量的資料處理、機器學習、網路爬蟲等套件,開發 MCP Server 時可以直接整合這些能力
  • FastMCP 框架:官方 SDK 內建的 FastMCP 框架大幅簡化了開發流程,用類似 Flask 的寫法就能建立 Server
  • 廣泛的使用者基礎:Python 是全球最受歡迎的程式語言之一,團隊成員更容易上手維護
  • 快速原型開發:Python 的動態特性讓你能快速驗證想法,適合敏捷開發流程

環境準備與安裝

在開始之前,請確認你的開發環境已準備就緒。MCP Python SDK 需要 Python 3.10 以上的版本,建議使用 uv 作為套件管理工具,因為它速度更快且是 MCP 官方推薦的方案。

安裝 uv 套件管理工具

# macOS / Linux
curl -LsSf https://astral.sh/uv/install.sh | sh

# Windows
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

# 驗證安裝
uv --version

建立專案與安裝 SDK

# 建立專案目錄
mkdir my-mcp-server
cd my-mcp-server

# 初始化 Python 專案
uv init

# 安裝 MCP Python SDK(內含 FastMCP)
uv add "mcp[cli]"

# 如需額外的 HTTP 功能
uv add httpx

安裝完成後,你的 pyproject.toml 會自動加入 mcp 作為依賴項。MCP Python SDK 的核心模組就是 FastMCP,它是整個開發體驗的基礎。

認識 FastMCP 框架

FastMCP 是 MCP Python SDK 的核心框架,它的設計理念類似 Python 的 Web 框架(如 Flask、FastAPI),透過 decorator 來註冊各種功能。FastMCP 最初由社群開發,後來被納入官方 SDK,現已成為 Python 開發 MCP Server 的標準方式。

一個最基本的 MCP Server 只需要幾行程式碼:

from mcp.server.fastmcp import FastMCP

# 建立 MCP Server 實例
mcp = FastMCP("my-server")

# 定義一個簡單的 Tool
@mcp.tool()
def hello(name: str) -> str:
    """向指定的人打招呼"""
    return f"你好,{name}!歡迎使用 MCP Server。"

# 啟動 Server
if __name__ == "__main__":
    mcp.run()

這段程式碼展示了 FastMCP 的核心概念:建立實例、用 decorator 註冊功能、啟動伺服器。FastMCP 會自動根據函式的型別標註(type hints)和 docstring 產生 MCP 協定所需的 schema,你不需要手動撰寫 JSON Schema。

MCP Server 的三大核心功能

MCP 協定定義了三種主要的功能類型,每種都有不同的用途和使用場景:

功能類型說明類比使用場景
Tools可被 AI 呼叫的函式,會產生副作用或執行運算類似 POST API執行查詢、發送通知、操作資料庫
Resources提供資料給 AI 讀取,類似檔案或 API 端點類似 GET API讀取設定檔、載入資料集、取得狀態
Prompts預定義的提示詞範本,引導 AI 執行特定任務類似訊息範本程式碼審查、報告產生、文件摘要

定義 Tools(工具)

Tools 是 MCP Server 最常用的功能,它讓 AI 能夠呼叫你定義的函式來執行各種操作。定義 Tool 的關鍵在於提供清楚的型別標註和 docstring,因為 FastMCP 會將這些資訊轉換為 AI 可以理解的描述。

from mcp.server.fastmcp import FastMCP
import httpx
import json

mcp = FastMCP("weather-server")

@mcp.tool()
async def get_weather(city: str, unit: str = "celsius") -> str:
    """查詢指定城市的天氣資訊
    
    Args:
        city: 城市名稱,例如 "Taipei" 或 "Tokyo"
        unit: 溫度單位,可選 "celsius" 或 "fahrenheit",預設 celsius
    """
    async with httpx.AsyncClient() as client:
        response = await client.get(
            f"https://api.weatherapi.com/v1/current.json",
            params={"q": city, "key": "YOUR_API_KEY"}
        )
        data = response.json()
        temp = data["current"]["temp_c"] if unit == "celsius" else data["current"]["temp_f"]
        condition = data["current"]["condition"]["text"]
        return f"{city} 目前天氣:{condition},溫度 {temp}°{'C' if unit == 'celsius' else 'F'}"

@mcp.tool()
def calculate_bmi(weight_kg: float, height_cm: float) -> str:
    """計算身體質量指數(BMI)
    
    Args:
        weight_kg: 體重(公斤)
        height_cm: 身高(公分)
    """
    height_m = height_cm / 100
    bmi = weight_kg / (height_m ** 2)
    if bmi < 18.5:
        category = "體重過輕"
    elif bmi < 24:
        category = "正常範圍"
    elif bmi < 27:
        category = "過重"
    else:
        category = "肥胖"
    return f"BMI: {bmi:.1f}({category})"

上面的範例展示了幾個重要的 Tool 開發技巧:

  • 型別標註是必要的:FastMCP 透過型別標註來產生 JSON Schema,告訴 AI 每個參數的型別
  • docstring 就是描述:函式的 docstring 會成為 Tool 的說明文字,AI 會依此判斷何時該使用這個工具
  • 支援 async:對於需要網路請求的 Tool,建議使用 async 函式來提升效能
  • 預設值會變成可選參數:有預設值的參數在 Schema 中會被標記為 optional

定義 Resources(資源)

Resources 用來向 AI 提供可讀取的資料,它的角色類似 REST API 的 GET 端點。每個 Resource 都有一個唯一的 URI,AI 可以透過這個 URI 來存取資料。

import json
from pathlib import Path
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("data-server")

# 靜態資源:固定 URI
@mcp.resource("config://app-settings")
def get_app_settings() -> str:
    """取得應用程式的設定資訊"""
    settings = {
        "app_name": "MyApp",
        "version": "2.1.0",
        "environment": "production",
        "features": ["auth", "logging", "cache"]
    }
    return json.dumps(settings, indent=2, ensure_ascii=False)

# 動態資源:URI 中包含參數
@mcp.resource("file://logs/{date}")
def get_log_by_date(date: str) -> str:
    """讀取指定日期的 log 檔案
    
    Args:
        date: 日期格式為 YYYY-MM-DD
    """
    log_path = Path(f"/var/log/app/{date}.log")
    if log_path.exists():
        return log_path.read_text()
    return f"找不到 {date} 的 log 檔案"

# Resource Template:動態產生資源清單
@mcp.resource("db://users/{user_id}")
def get_user(user_id: int) -> str:
    """根據 ID 查詢使用者資訊"""
    # 模擬資料庫查詢
    users = {
        1: {"name": "Alice", "role": "admin"},
        2: {"name": "Bob", "role": "user"}
    }
    user = users.get(user_id)
    if user:
        return json.dumps(user, ensure_ascii=False)
    return f"找不到 ID 為 {user_id} 的使用者"

定義 Prompts(提示範本)

Prompts 是預先定義好的提示詞範本,讓使用者可以快速啟動特定的 AI 工作流程。它不像 Tool 是由 AI 自動決定呼叫,而是由使用者主動選擇觸發。

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("code-review-server")

@mcp.prompt()
def code_review(code: str, language: str = "python") -> str:
    """產生程式碼審查的提示詞
    
    Args:
        code: 需要審查的程式碼
        language: 程式語言,預設 python
    """
    return f"""請審查以下 {language} 程式碼,並提供改善建議:

```{language}
{code}
```

請從以下面向進行審查:
1. 程式碼品質與可讀性
2. 潛在的 Bug 或安全性問題
3. 效能最佳化建議
4. 是否符合 {language} 的最佳實踐"""

@mcp.prompt()
def summarize_doc(content: str, max_words: int = 200) -> str:
    """產生文件摘要的提示詞"""
    return f"請將以下內容摘要為不超過 {max_words} 字的重點整理:\n\n{content}"

完整範例:專案管理 MCP Server

接下來,我們用一個實際的專案管理工具來展示如何結合 Tools、Resources 和 Prompts 開發一個功能完整的 MCP Server。這個範例模擬了一個簡單的 TODO 管理系統。

from mcp.server.fastmcp import FastMCP
from datetime import datetime
import json

# 建立 Server 並設定相依套件
mcp = FastMCP(
    "todo-manager",
    dependencies=["httpx"]
)

# 模擬的任務資料庫
tasks: dict[int, dict] = {}
next_id = 1

# ===== Tools =====

@mcp.tool()
def add_task(title: str, priority: str = "medium") -> str:
    """新增一個待辦任務
    
    Args:
        title: 任務標題
        priority: 優先級,可選 high、medium、low
    """
    global next_id
    task = {
        "id": next_id,
        "title": title,
        "priority": priority,
        "status": "pending",
        "created_at": datetime.now().isoformat()
    }
    tasks[next_id] = task
    next_id += 1
    return f"已新增任務:{title}(ID: {task['id']})"

@mcp.tool()
def complete_task(task_id: int) -> str:
    """將指定任務標記為完成
    
    Args:
        task_id: 任務 ID
    """
    if task_id not in tasks:
        return f"找不到 ID 為 {task_id} 的任務"
    tasks[task_id]["status"] = "completed"
    return f"任務 {tasks[task_id]['title']} 已標記為完成"

@mcp.tool()
def delete_task(task_id: int) -> str:
    """刪除指定的任務
    
    Args:
        task_id: 任務 ID
    """
    if task_id not in tasks:
        return f"找不到 ID 為 {task_id} 的任務"
    removed = tasks.pop(task_id)
    return f"已刪除任務:{removed['title']}"

# ===== Resources =====

@mcp.resource("todo://tasks")
def list_all_tasks() -> str:
    """取得所有任務的清單"""
    return json.dumps(list(tasks.values()), indent=2, ensure_ascii=False)

@mcp.resource("todo://tasks/{task_id}")
def get_task_detail(task_id: int) -> str:
    """取得單一任務的詳細資訊"""
    task = tasks.get(task_id)
    if task:
        return json.dumps(task, indent=2, ensure_ascii=False)
    return f"找不到 ID 為 {task_id} 的任務"

@mcp.resource("todo://stats")
def get_task_stats() -> str:
    """取得任務統計資訊"""
    total = len(tasks)
    completed = sum(1 for t in tasks.values() if t["status"] == "completed")
    pending = total - completed
    return json.dumps({
        "total": total,
        "completed": completed,
        "pending": pending
    })

# ===== Prompts =====

@mcp.prompt()
def daily_summary() -> str:
    """產生每日任務摘要的提示詞"""
    return "請根據目前的任務清單,產生今日的工作摘要報告,包含已完成和待處理的任務。"

if __name__ == "__main__":
    mcp.run()

在 Claude Code 中測試你的 Server

開發完成後,你需要在 Claude Code 中實際測試 Server 的功能。MCP SDK 提供了多種測試方式。

使用 MCP Inspector 測試

MCP Inspector 是官方提供的視覺化測試工具,它提供了一個 Web 介面讓你可以互動式地測試每個 Tool、Resource 和 Prompt。

# 啟動 MCP Inspector
mcp dev server.py

# Inspector 預設會在 http://localhost:5173 開啟
# 你可以在介面中:
# - 查看所有已註冊的 Tools、Resources、Prompts
# - 手動輸入參數測試每個功能
# - 檢視回傳結果和錯誤訊息

直接加入 Claude Code

確認功能正常後,就可以將 Server 加入 Claude Code 使用:

# 將 Server 加入 Claude Code(本地 stdio 模式)
claude mcp add todo-manager -- uv run server.py

# 如果使用 HTTP 傳輸模式
# 先啟動 Server
uv run server.py --transport http --port 8000

# 再加入 Claude Code
claude mcp add --transport http todo-manager http://localhost:8000/mcp

# 驗證 Server 已正確載入
claude mcp list

加入成功後,你可以直接在 Claude Code 中使用自然語言與 Server 互動。例如,輸入「幫我新增一個高優先級的任務:完成 API 文件撰寫」,Claude 就會自動呼叫 add_task 工具來執行。

Python vs TypeScript:該選哪個?

MCP 官方同時提供了 Python 和 TypeScript 兩套 SDK,兩者都能開發功能完整的 MCP Server,但在開發體驗和適用場景上有一些差異:

比較項目Python SDKTypeScript SDK
框架FastMCP(decorator 風格)McpServer(method chain 風格)
型別系統動態型別 + type hints靜態型別(Zod schema)
Schema 產生自動從 type hints 和 docstring 產生需手動定義 Zod schema
套件管理uv / pipnpm / yarn
非同步支援asyncio(原生支援)Promise / async-await
適合場景資料處理、ML 整合、快速原型前端整合、Node.js 生態系
學習曲線較低(Python 開發者友善)中等(需熟悉 Zod)
程式碼量較少(decorator 自動化程度高)較多(需手動定義 schema)

簡單來說,如果你的團隊主要使用 Python,或者你需要整合資料處理和機器學習的功能,Python SDK 是更好的選擇。如果你的團隊以前端或 Node.js 為主,TypeScript SDK 會更自然。

進階技巧:Context 與 Lifespan

FastMCP 提供了一些進階功能,讓你能建構更複雜的 Server。

使用 Context 存取 MCP 功能

在 Tool 函式中,你可以透過 Context 物件來存取 MCP 的進階功能,例如回報進度、記錄日誌,或讀取其他 Resource。

from mcp.server.fastmcp import FastMCP, Context

mcp = FastMCP("advanced-server")

@mcp.tool()
async def process_data(file_path: str, ctx: Context) -> str:
    """處理大量資料並回報進度
    
    Args:
        file_path: 資料檔案路徑
    """
    # 回報進度給 Client
    await ctx.report_progress(0, 100)
    
    # 記錄日誌
    await ctx.info(f"開始處理檔案:{file_path}")
    
    # 模擬處理過程
    for i in range(1, 101):
        # 處理資料...
        if i % 10 == 0:
            await ctx.report_progress(i, 100)
    
    await ctx.info("處理完成")
    return "資料處理完成,共處理 100 筆記錄"

使用 Lifespan 管理生命週期

當你的 Server 需要管理外部連線(如資料庫連接池)時,可以使用 lifespan 來處理啟動和關閉時的初始化邏輯。

from contextlib import asynccontextmanager
from mcp.server.fastmcp import FastMCP

@asynccontextmanager
async def app_lifespan(server: FastMCP):
    """管理 Server 的生命週期"""
    # 啟動時執行:建立資料庫連線
    db = await create_db_connection()
    try:
        yield {"db": db}  # 將資源傳遞給 Tools
    finally:
        # 關閉時執行:釋放資源
        await db.close()

mcp = FastMCP("db-server", lifespan=app_lifespan)

@mcp.tool()
async def query_users(ctx: Context) -> str:
    """查詢所有使用者"""
    db = ctx.request_context.lifespan_context["db"]
    users = await db.fetch_all("SELECT * FROM users")
    return json.dumps(users, ensure_ascii=False)

部署你的 MCP Server

MCP Server 支援兩種主要的傳輸模式,各有不同的部署方式。

stdio 模式(本地部署)

stdio 是最簡單的部署方式,Server 作為本地行程執行,透過標準輸入/輸出與 Claude Code 溝通。適合個人使用或開發階段。

# server.py 的啟動方式
if __name__ == "__main__":
    mcp.run()  # 預設使用 stdio

# 加入 Claude Code
claude mcp add my-server -- uv run server.py

HTTP 模式(遠端部署)

HTTP 模式(Streamable HTTP)適合部署到雲端服務器,讓多人可以共享同一個 MCP Server。這是目前官方推薦的遠端傳輸方式。

# server.py 使用 HTTP 模式啟動
if __name__ == "__main__":
    mcp.run(transport="streamable-http", host="0.0.0.0", port=8000)

# 加入 Claude Code(連線到遠端 Server)
claude mcp add --transport http my-remote-server https://your-server.com/mcp

專案結構建議

當你的 MCP Server 功能越來越多時,良好的專案結構能讓程式碼更容易維護。以下是建議的目錄配置:

my-mcp-server/
├── pyproject.toml                                # 專案設定與依賴管理
├── README.md                                    # 專案說明文件
├── src/
│   └── my_mcp_server/
│       ├── __init__.py
│       ├── server.py                                 # Server 主程式與 FastMCP 實例
│       ├── tools/                                       # Tools 定義
│       │   ├── __init__.py
│       │   ├── task_tools.py
│       │   └── data_tools.py
│       ├── resources/                               # Resources 定義
│       │   ├── __init__.py
│       │   └── data_resources.py
│       └── prompts/                                 # Prompts 定義
│           ├── __init__.py
│           └── review_prompts.py
└── tests/                                                  # 測試檔案
    ├── test_tools.py
    └── test_resources.py

常見問題與除錯技巧

在開發 MCP Server 的過程中,你可能會遇到一些常見的問題。以下整理了幾個最常見的狀況和解決方法:

  • Tool 沒有出現在 Claude 中:確認 docstring 是否完整,FastMCP 需要 docstring 才能產生描述。同時檢查 claude mcp list 確認 Server 狀態為 running
  • 參數型別錯誤:確保所有參數都有型別標註,FastMCP 無法處理沒有標註的參數。使用 intstrfloatbool 等基本型別
  • async 函式沒有正確執行:如果你的 Tool 使用了 async,確認所有呼叫鏈都是 async 的。混合使用同步和非同步可能導致執行卡住
  • Server 啟動後立即退出:檢查是否有語法錯誤或 import 失敗。使用 mcp dev server.py 來查看詳細的錯誤訊息
  • Resource URI 無法存取:確認 URI 格式正確,動態參數需要與函式參數名稱一致。例如 file://logs/{date} 對應函式參數 date

總結

使用 Python 開發 MCP Server 是擴展 Claude Code 能力最靈活的方式。透過 FastMCP 框架,你可以用簡潔的 decorator 語法快速定義 Tools、Resources 和 Prompts,不需要手動處理複雜的 MCP 協定細節。

本篇涵蓋了從環境建置、核心功能開發到測試部署的完整流程。你已經學會了如何建立 FastMCP 實例、定義三種核心功能類型、使用 Context 和 Lifespan 處理進階場景,以及如何在 Claude Code 中測試和部署你的 Server。

下一篇我們將進一步探討如何用 TypeScript 開發 MCP Server,並深入比較兩種語言在 MCP 開發上的實際體驗差異。如果你的需求偏向資料處理或 Python 生態系整合,現在就可以開始動手打造你的第一個 Python MCP Server 了。