所有入門單元免費開放 · 課程內容持續更新中

用 Python 寫一個 MCP Server

動手寫一個「讀書筆記」MCP Server,提供新增筆記與搜尋筆記兩個工具。先用 MCP Inspector 不經過 AI 測試它,再接到 Claude Code 和 Claude Desktop,讓你在對話裡隨口就能記筆記、找筆記。

第 13.2 堂約 18 分鐘

上一堂課結尾,請你想一個「最想讓 AI 幫你存取的東西」。這堂課我們先一起做一個很生活化的例子:讀書筆記。

想像你在跟 Claude 聊一本書,聊到一半說:「幫我記下來:好習慣要讓它顯而易見。」Claude 就自動存起來。過了兩個月,你問:「我以前記過哪些跟習慣有關的筆記?」它就找給你。

做完之後,你會有一個任何支援 MCP 的 App 都能用的小工具。程式不到七十行,我們一段一段來。

先看整體長什麼樣

Claude Code 透過 stdin 送訊息給 notes_server.py,notes_server.py 透過 stdout 回覆,並讀寫 notes.json 檔案。下方提醒:stdout 是專用管道,Server 不能用 print,要用 logging 寫到 stderr

你寫的是中間那支程式。啟動它、和它對話,都是 Host 的工作。

還記得上一堂的 stdio 嗎?Host 會把你的 Server 當作子程式啟動,透過「標準輸入」送請求、從「標準輸出」讀回覆。這件事會影響我們寫程式的一個小習慣,等一下會提到。

建立專案

我們用 uv 管理這個專案。uv 是一個 Python 的專案與套件管理工具,可以幫你建立獨立的環境、安裝套件,MCP 官方教學也是用它。還沒安裝的話,在 Windows 的 PowerShell 執行:

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

macOS 或 Linux 則是:

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

裝好後重新開一個終端機,然後建立專案:

uv init reading-notes
cd reading-notes
uv add "mcp[cli]"

mcp 是 MCP 官方的 Python 套件,[cli] 會順便裝上開發用的指令工具。

完整程式

在 reading-notes 資料夾裡建立 notes_server.py:

import json
import logging
from datetime import date
from pathlib import Path

from mcp.server import MCPServer

# logging 預設寫到 stderr,不會干擾 MCP 訊息
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

mcp = MCPServer("reading-notes")

# 筆記檔放在這支程式旁邊,不管從哪裡啟動都找得到
NOTES_FILE = Path(__file__).parent / "notes.json"


def load_notes() -> list[dict]:
    if not NOTES_FILE.exists():
        return []
    return json.loads(NOTES_FILE.read_text(encoding="utf-8"))


def save_notes(notes: list[dict]) -> None:
    NOTES_FILE.write_text(
        json.dumps(notes, ensure_ascii=False, indent=2), encoding="utf-8"
    )


@mcp.tool()
def add_note(book: str, note: str) -> str:
    """新增一則讀書筆記。使用者說「幫我記下來」或分享讀書心得時使用。

    Args:
        book: 書名,不含書名號,例如:原子習慣
        note: 筆記內容,保留使用者的原意,一到三句話
    """
    notes = load_notes()
    notes.append({
        "book": book.strip(),
        "note": note.strip(),
        "date": date.today().isoformat(),
    })
    save_notes(notes)
    logger.info("新增了一則《%s》的筆記", book)
    return f"已記下《{book}》的筆記,目前共有 {len(notes)} 則。"


@mcp.tool()
def search_notes(keyword: str, limit: int = 5) -> str:
    """用關鍵字搜尋讀書筆記,會比對書名和筆記內容。
    使用者想回顧某本書、某個主題,或問「我記過什麼」時使用。
    回傳最新的幾則結果,每則包含書名、日期與內容;找不到時會明確說明。

    Args:
        keyword: 要找的字詞,例如:習慣,或一個書名
        limit: 最多回傳幾則,預設 5,最多 20
    """
    limit = max(1, min(limit, 20))
    kw = keyword.strip().lower()
    hits = [
        n for n in load_notes()
        if kw in n["book"].lower() or kw in n["note"].lower()
    ]
    if not hits:
        return f"找不到和「{keyword}」有關的筆記。"
    newest_first = hits[::-1][:limit]
    return "\n".join(
        f"《{n['book']}》({n['date']}):{n['note']}" for n in newest_first
    )


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

逐段拆解

MCPServer("reading-notes"):建立一個 Server,括號裡是它的名字。

@mcp.tool():這個裝飾器把下面的函式登記成一個工具。和上個單元比較一下,你不用再手寫工具說明書的字典了,套件會自動幫你產生:

你寫的 變成說明書裡的
函式名稱 add_note 工具的 name
docstring(函式下方三個引號的說明) 工具的 description
型別提示 book: str、limit: int = 5 參數規格,有預設值的就是選填
Args: 底下每個參數的說明 每個參數的說明

所以「定義工具」那堂課學到的原則全部適用:docstring 就是說明書,要寫清楚做什麼、什麼時候用、參數怎麼給、會回傳什麼。這是整支程式最值得花時間的地方。

limit = max(1, min(limit, 20)):模型給的參數不能完全信任,上個單元的 safe_path 也是同樣的精神。這裡把數量限制在合理範圍,避免一次回傳幾千則塞爆上下文。

logger.info 而不是 print:這就是前面說的小習慣。

mcp.run(transport="stdio"):用 stdio 方式啟動,等待 Host 傳訊息過來。

開發的四個步驟

開發 MCP Server 的四個步驟:1 寫工具函式,2 用 Inspector 測試,3 接上 Host,4 真實對話

先排除程式的問題,再排除說明書的問題,卡住時比較好找原因。

用 MCP Inspector 測試

MCP Inspector 是官方的測試工具,會開一個網頁,讓你不經過 AI、直接按按鈕呼叫工具。它需要 Node.js(上一堂裝過了)。在 reading-notes 資料夾裡執行:

npx @modelcontextprotocol/inspector uv run notes_server.py

inspector 後面接的,就是「啟動你的 Server 的指令」。瀏覽器打開後:

  1. 確認畫面顯示已連線。
  2. 切到 Tools 分頁,列出工具,應該看到 add_note 和 search_notes。點開看看說明,是不是就是你寫的 docstring?
  3. 點 add_note,填入書名和筆記,按執行,應該看到「已記下……」。
  4. 再用 search_notes 搜尋剛剛的關鍵字,確認找得到。

資料夾裡也會多出一個 notes.json,打開就能看到你的筆記。

另一個選擇是 uv run mcp dev notes_server.py,也會幫你開啟 Inspector。哪個能用就用哪個。

接到 Claude Code

測試沒問題,就可以接上真正的 Host 了。在 reading-notes 資料夾裡執行(把路徑換成你的資料夾實際位置,Windows 也可以用 / 斜線):

claude mcp add --transport stdio reading-notes -- uv --directory C:/Users/你的名字/reading-notes run notes_server.py

上一堂學過,-- 後面是啟動 Server 的指令。--directory 告訴 uv 要到哪個資料夾執行,所以 Claude Code 不管在哪裡啟動,都找得到你的程式。

用 claude mcp list 確認已連線,然後打開 Claude Code 聊聊看:

我最近在讀《原子習慣》,幫我記下來:好習慣要讓它顯而易見,壞習慣要讓它隱而不現。

Claude 會詢問是否允許使用 add_note,同意後就記下來了。再問:

我記過哪些跟習慣有關的筆記?

這一步檢查的是說明書:模型有沒有自己判斷該用 search_notes?關鍵字給得對不對?如果它選錯工具或關鍵字很奇怪,回去改 docstring,往往比改程式有效。

接到 Claude Desktop

Claude Desktop 是透過設定檔 claude_desktop_config.json 加入 Server。在 Windows 可以用 VS Code 打開它:

code $env:AppData\Claude\claude_desktop_config.json

macOS 的位置是 ~/Library/Application Support/Claude/claude_desktop_config.json。檔案不存在就新建一個,內容是:

{
  "mcpServers": {
    "reading-notes": {
      "command": "uv",
      "args": [
        "--directory",
        "C:\\Users\\你的名字\\reading-notes",
        "run",
        "notes_server.py"
      ]
    }
  }
}

存檔後完全關閉 Claude Desktop 再重新打開。JSON 裡的 Windows 路徑要用兩個反斜線,或改用 /。如果顯示找不到 uv,把 command 換成 uv 的完整路徑,可以在終端機用 where uv(macOS 用 which uv)查到。

這就是 MCP 的威力:同一支程式,兩個 App 都能用,你一行都沒改。

你可能會卡在這裡

  • Inspector 連不上:先單獨執行 uv run notes_server.py,看有沒有錯誤訊息。沒有錯誤時它會安靜地等待,這是正常的,按 Ctrl+C 結束。
  • Claude Code 顯示連線失敗:最常見的是 --directory 的路徑打錯。用 claude mcp remove reading-notes 刪掉後重新加。
  • 改了程式卻沒生效:Host 用的還是先前啟動的 Server。在 Claude Code 裡重開一個對話,或重新啟動 Claude Desktop。
  • 中文變成亂碼:確認讀寫檔案都有加 encoding="utf-8",json.dumps 有加 ensure_ascii=False。
可以這樣問 AI

這是我用 Python mcp 套件寫的讀書筆記 MCP Server(附上 notes_server.py)。請幫我新增一個 list_books 工具,列出所有書名和每本書的筆記數量。docstring 要說清楚用途、使用時機與回傳格式,並沿用現有的 load_notes。不要使用 print。

行動練習
  1. 照著步驟建立 reading-notes 專案,用 Inspector 分別呼叫兩個工具,確認 notes.json 有被寫入。
  2. 把 Server 接到 Claude Code,用聊天的方式新增三則不同書的筆記,再請它搜尋其中一個主題。
  3. 故意把 search_notes 的 docstring 改成只有「搜尋」兩個字,重新連線後問同樣的問題,觀察模型的行為有沒有變。記得改回來。
  4. 用上面的提示詞請 AI 幫你加上 list_books,先用 Inspector 測,再在 Claude Code 裡問「我讀過哪些書?」
  5. 挑戰:把上一堂寫下的點子,做成你自己的第一個 MCP Server,就算只有一個工具也很好。

讀完了嗎?

標記完成後,單元與專案的進度會自動更新(只存在這台裝置的瀏覽器)。

卡住了?讓 AI 當你的家教複製這段提問範本,貼到 Claude 或其他 AI 工具,再補上你卡住的地方。

留言與討論

卡關了、有心得,或發現內容需要更新?都歡迎留言。留言使用 GitHub 帳號登入。

留言區即將開放。現在可以先到 GitHub 討論區 發問,或看看 交流頁 的其他管道。