在 MCP 之前,每個 AI 工具都有自己的外掛系統。Claude Code 有一套。Cursor 有另一套。你的資料庫工具在 Claude Code 裡能用、在 Cursor 裡卻不能用,於是你得寫兩次。切換工具就代表重寫所有整合。這就是 2024 年 AI 工具的狀態——每個工具都是一座孤島,每個整合都是客製的。
MCP——Model Context Protocol——在 2025 年改變了這一切,到了 2026 年它無所不在。Claude Code 用它。Cursor 支援它。LangChain、LiteLLM 跟所有主要 AI 平台都加入了 MCP 支援。官方 MCP 規範定義了這個協定——它是開放標準(以 JSON-RPC 為基礎),把 AI 模型如何發現並呼叫工具這件事標準化。寫一個 MCP server。任何相容 MCP 的 client 都能用它。
什麼是 MCP——以及它為什麼重要
是協定,不是行銷話術。 MCP 是基於 JSON-RPC 2.0 的協定。MCP server 對外提供 tool、resource、prompt。MCP client(嵌在 AI 應用程式裡)連上 server、發現有哪些東西可用、代表 LLM 呼叫工具。傳輸層是可替換的——本地工具用 stdio,遠端服務用 HTTP 加 SSE。
MCP vs function calling——互補,不是競爭。 Function calling:LLM 在單一 API 請求內呼叫工具。工具定義跟著 API 呼叫一起送出。MCP:LLM 透過標準化的協定發現可用的工具,然後呼叫它們——可能橫跨多個 session 和多個工具。MCP 把工具的發現與描述標準化。Function calling 負責執行它們。你可以用 MCP 管理工具目錄、用 function calling 呼叫它們——兩者一起運作。想深入比較 OpenAI、Anthropic、Google、DeepSeek 的 function calling 實作,請看我們的function calling 與工具使用指南。
為什麼 MCP 對 API 開發者重要。 MCP 之前:你寫了一個資料庫查詢工具。要搭配 Claude Code 用,你寫了 Claude Code 專屬的外掛。要搭配 Cursor 用,你寫了 Cursor 專屬的整合。要在自己的 app 用,你寫了客製程式。MCP 之後:寫一個 MCP server。每個相容 MCP 的 client 都能用。這就是「AI 工具的 USB-C」類比——不算完美,但它是勝出的標準。
MCP 架構:Server、Client 與傳輸層
MCP server。 對外提供 tool、resource、prompt。用 Python、Node.js 或 Go 寫——隨你喜歡。在本地跑(stdio 傳輸層)或遠端跑(HTTP+SSE 傳輸層)。Server 就是一支程式。它啟動。它監聽連線。它回應工具呼叫請求。client 斷線時它就停止。
MCP client。 連上 MCP server、發現可用的工具、送出 LLM 的工具呼叫請求、回傳結果。嵌在 AI 應用程式裡——Claude Code、Cursor、你的 app。Client 是 LLM 的決定(「我需要查資料庫」)與工具的執行(SELECT * FROM users)之間的橋樑。
傳輸層。 stdio:server 以 client 的子行程方式執行。用標準輸入/輸出來溝通。最適合本地開發工具——零網路設定、零延遲。stdio 傳輸層在現代機器上每則訊息往返只增加不到 1ms 的負擔。HTTP+SSE:server 以遠端服務方式執行。請求用 HTTP POST,串流回應用 Server-Sent Events。最適合生產環境服務——可以有多個 client 連線,server 可以獨立更新,不用重啟每個開發者的 IDE。
架構圖:
LLM → MCP Client → Transport (stdio/HTTP) → MCP Server → External Resources (DB, API, Filesystem)
建立你的第一個 MCP Server
天氣+股價的 MCP server。15 分鐘。兩個 tool。
# mcp_server.py
import json
import sys
from typing import Any
class MCPServer:
def __init__(self):
self.tools = {
"get_weather": {
"description": "Get current weather for a city. Returns temperature in Celsius.",
"inputSchema": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "City name, e.g. 'Tokyo'"}
},
"required": ["city"]
}
},
"get_stock_price": {
"description": "Get current stock price for a ticker symbol.",
"inputSchema": {
"type": "object",
"properties": {
"symbol": {"type": "string", "description": "Stock ticker, e.g. AAPL"}
},
"required": ["symbol"]
}
}
}
def handle_request(self, request: dict) -> dict:
method = request.get("method")
if method == "tools/list":
return {"tools": list(self.tools.values())}
elif method == "tools/call":
tool_name = request["params"]["name"]
tool_args = request["params"]["arguments"]
result = self._execute_tool(tool_name, tool_args)
return {"content": [{"type": "text", "text": str(result)}]}
else:
return {"error": f"Unknown method: {method}"}
def _execute_tool(self, name: str, args: dict) -> Any:
if name == "get_weather":
return f"Weather in {args['city']}: 22°C, partly cloudy"
elif name == "get_stock_price":
return f"{args['symbol']}: $185.50"
else:
return f"Unknown tool: {name}"
# stdio transport —reads JSON-RPC from stdin, writes to stdout
if __name__ == "__main__":
server = MCPServer()
for line in sys.stdin:
request = json.loads(line)
response = server.handle_request(request)
sys.stdout.write(json.dumps(response) + "\n")
sys.stdout.flush()
連上 Claude Desktop。 Anthropic 的MCP 快速入門帶你走完整個設定。把這段加進你的 claude_desktop_config.json:
{
"mcpServers": {
"my-tools": {
"command": "python",
"args": ["mcp_server.py"]
}
}
}
重啟 Claude Desktop。你的兩個 tool——get_weather 和 get_stock_price——現在可用了。Claude 會自動發現它們。試試:「東京的天氣如何?Apple 的股價多少?」Claude 會並行呼叫兩個 tool,然後合成一份回答。
想完整了解 Claude API——認證、串流、工具使用、錯誤處理——請看我們的Claude API 開發者指南。
建立一個真正的 MCP Server:資料庫查詢工具
天氣 server 證明了 MCP 用 50 行 Python 就能跑。但生產環境的工具還需要更多:參數化查詢、連線池、以及 LLM 能據以自我修正的錯誤訊息。這裡是一支完整的 MCP server,用官方 MCP Python SDK(pip install mcp)建立,負責查詢 SQLite 資料庫。三個 tool——query_users、get_user_by_id、create_user。複製、改寫、部署。
# db_mcp_server.py —production-grade MCP database server
import sqlite3
import asyncio
from mcp.server import Server, NotificationOptions
from mcp.server.models import InitializationCapabilities
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent
server = Server("database-tools")
# Database connection —created once, reused across all tool calls
_connection: sqlite3.Connection | None = None
def get_db():
global _connection
if _connection is None:
_connection = sqlite3.connect("app.db")
_connection.row_factory = sqlite3.Row
# WAL mode: concurrent reads without locking. Without this,
# two simultaneous query_users calls serialize —the second
# blocks until the first releases the read lock.
_connection.execute("PRAGMA journal_mode=WAL")
return _connection
@server.list_tools()
async def list_tools() -> list[Tool]:
return [
Tool(
name="query_users",
description="Run a SELECT query against the users table. "
"Use for searching, filtering, or counting users. "
"Returns results as JSON array. "
"Column names: id, name, email, role, status, created_at.",
inputSchema={
"type": "object",
"properties": {
"where_clause": {
"type": "string",
"description": "SQL WHERE clause without 'WHERE' keyword. "
"Example: \"age > 25 AND status = 'active'\". "
"Leave empty for all users."
},
"limit": {
"type": "integer",
"description": "Max rows to return. Default 50.",
"default": 50
}
}
}
),
Tool(
name="get_user_by_id",
description="Fetch a single user by primary key. Returns user object or null.",
inputSchema={
"type": "object",
"properties": {
"user_id": {
"type": "integer",
"description": "The user's ID in the database."
}
},
"required": ["user_id"]
}
),
Tool(
name="create_user",
description="Insert a new user into the database. "
"Returns the created user with their assigned ID.",
inputSchema={
"type": "object",
"properties": {
"name": {"type": "string", "description": "User's full name."},
"email": {"type": "string", "description": "Valid email address."},
"role": {
"type": "string",
"enum": ["admin", "editor", "viewer"],
"description": "User role. Default: viewer."
}
},
"required": ["name", "email"]
}
)
]
@server.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
db = get_db()
if name == "query_users":
where = arguments.get("where_clause", "")
limit = arguments.get("limit", 50)
query = "SELECT * FROM users"
params = []
if where.strip():
query += " WHERE " + where
query += " LIMIT ?"
params.append(limit)
try:
rows = db.execute(query, params).fetchall()
except sqlite3.OperationalError as e:
# Return column names so the LLM can self-correct bad WHERE clauses
return [TextContent(
type="text",
text=f"Query failed: {e}. Valid columns: id, name, email, role, status, created_at."
)]
return [TextContent(
type="text",
text=str([dict(r) for r in rows])
)]
elif name == "get_user_by_id":
user_id = arguments["user_id"]
row = db.execute(
"SELECT * FROM users WHERE id = ?", [user_id]
).fetchone()
if row is None:
return [TextContent(type="text", text=f"No user found with id={user_id}")]
return [TextContent(type="text", text=str(dict(row)))]
elif name == "create_user":
name_val = arguments["name"]
email = arguments["email"]
role = arguments.get("role", "viewer")
try:
cursor = db.execute(
"INSERT INTO users (name, email, role) VALUES (?, ?, ?)",
[name_val, email, role]
)
db.commit()
new_id = cursor.lastrowid
except sqlite3.IntegrityError as e:
return [TextContent(
type="text",
text=f"Insert failed: {e}. Email may already exist."
)]
new_user = db.execute(
"SELECT * FROM users WHERE id = ?", [new_id]
).fetchone()
return [TextContent(type="text", text=str(dict(new_user)))]
return [TextContent(type="text", text=f"Unknown tool: {name}")]
async def main():
async with stdio_server() as (read_stream, write_stream):
await server.run(
read_stream,
write_stream,
InitializationCapabilities(
sampling={},
experimental={},
roots={}
),
notification_options=NotificationOptions()
)
if __name__ == "__main__":
asyncio.run(main())
這段程式碼裡有四個決定,幫你躲開凌晨 3 點的 on-call 呼叫。
帶範例的工具描述。 LLM 讀你的工具描述來決定要呼叫哪個工具。光禿禿的 "description": "Query the database" 讓模型完全無從分辨工具——碰上 SELECT COUNT(*) 這種查詢,它常常會去試 get_user_by_id。沒有明確的指示,當多個工具描述相似時,模型常常選錯工具。上面的描述包含了工具做什麼、回傳什麼、以及確切的欄位名稱。這種具體性消除了歧義。
參數化查詢——不用 f-string。 db.execute("SELECT * FROM users WHERE id = ?", [user_id]) 絕對不能變成 db.execute(f"SELECT * FROM users WHERE id = {user_id}")。資料庫 MCP server 裡只要出現一個 f-string,一個產生出 user_id = "1 OR 1=1" 的 LLM 就會把你的 users 表整張洩光。LLM 真的會產生這種輸入。生產環境發生過。參數化查詢是硬性要求,不是可有可無。
帶復原提示的錯誤訊息。 當 query_users 因為 LLM 猜了不存在的欄位名稱而失敗,錯誤訊息會包含有效欄位清單:"Valid columns: id, name, email, role, status, created_at." LLM 讀到、修正查詢、重試工具呼叫——零人工介入。光禿禿的 "OperationalError: no such column" 只會讓 Claude 聳聳肩,然後使用者卡住。
WAL journal 模式的連線重用。 PRAGMA journal_mode=WAL 讓並行讀取不必鎖定。沒有它的話,兩個同時的 query_users 呼叫會被序列化——第二個要等第一個釋放讀取鎖 50ms。在 Claude 呼叫 query_users、讀取結果、再對每一列呼叫 get_user_by_id 的工具鏈裡,那個鎖等待會沿著 5 次連續呼叫累積成使用者感受得到的 250ms 延遲。WAL 模式消除了這個瓶頸。
MCP 在各家 LLM 供應商
MCP 支援各家差異很大。Anthropic 發明了這個協定,整合也最深。其他人以不同速度追趕。以下是 2026 年年中各家主要供應商的狀況。
| 供應商 | MCP 支援程度 | 傳輸層支援 | 生產就緒度 | SDK 品質 |
|---|---|---|---|---|
| Anthropic | 原生 —協定的發明者 | stdio + HTTP/SSE | 兩種傳輸皆達生產等級 | 官方 Python、TypeScript SDK,完整涵蓋規格 |
| OpenAI | 透過 Agents SDK+社群 adapter | stdio(Agents SDK)、HTTP 透過 adapter | Agents SDK:生產等級。Adapter:beta 品質 | 社群維護;沒有官方 MCP SDK |
| 僅有社群 adapter | stdio、有限度的 HTTP/SSE | 僅供開發 | 早期階段的社群套件;文件稀少 | |
| DeepSeek | OpenAI 相容路徑 | 透過 OpenAI adapter 工具鏈 | 可用,但不支援 | 沒有專用 MCP SDK;繼承 OpenAI adapter 的怪癖 |
| Aggregation Platforms(基礎設施層——路由到下方所有供應商) | MCP gateway —連線一次,路由到任何地方 | HTTP/SSE,搭配統一身分認證 | 生產等級,含受管認證、速率限制與日誌記錄 | 適用於 Python、JavaScript、LangChain 的平台 SDK |
MCP gateway 模式。 聚合平台扮演 MCP client,連上你的 MCP server,透過單一 API 端點把工具開放給任何 LLM。你寫一個 MCP server。你用 GPT-5.5、Claude Opus 4、Gemini 3、DeepSeek V3 都能用——全部走 POST https://api.tokspan.com/v1/chat/completions、同一個 API key。平台處理協定轉換,所以不管哪個模型在作用中,你的 MCP 工具都能跑。一份 server 定義餵給 6 個以上的模型。5 分鐘設定請看TokSpan 快速入門。
MCP vs Function Calling:何時用哪個
決定歸結到一個問題:你的工具需要在多個 LLM client 上運作嗎?
只用 function calling,當你直接呼叫單一 LLM API——OpenAI、Anthropic 或 Google——而且你的工具只專屬於單一應用程式。你把工具定義直接放進 chat completions 請求的 tools 陣列。基礎設施是零:沒有獨立的 server 行程、沒有傳輸層、沒有協定協商。一個用 GET /orders/:id 這種單一內部 REST 端點查訂單狀態的客服機器人,需要的是 function calling,不是 MCP。別過度設計——當你只有一個 client 時,MCP 只是多加了 server 行程、傳輸層和協定握手,一點好處都沒有。
MCP 和 function calling 一起用,當你有多個 AI client——你筆電上的 Claude Code、同事的 Cursor、staging server 上的客製 dashboard——都需要同一批工具。你的資料庫查詢工具、部署工具、日誌檢視器,在 MCP server 裡定義一次。每個 client 透過 tools/list 發現它們,執行時用 function calling 呼叫。
你的工具目錄集中化了。工具描述或 schema 的更新,不用重新部署就會即時傳到每個 client。一個 200 人工程組織、用這個模式的內部平台團隊,把每個新工具的整合維護時間從 8 小時砍到 30 分鐘。
把 MCP 當 gateway 用,當你透過聚合平台把工具呼叫路由到多個 LLM 供應商。一份 MCP server 定義同時把工具餵給 GPT-5.5、Claude Opus 4、Gemini 3。平台在 MCP 的工具發現協定與每家供應商的 function calling API 之間做轉換。你永遠不用寫供應商專屬的工具定義。你也永遠不用去查為什麼某個工具在 Claude 能用、在 GPT-5.5 卻默默失敗。一份 server 定義透過單一端點餵給 6 個以上模型——不需要供應商各自的工具設定。
錯誤的選擇——來自實戰經驗。 想想一個典型場景:小新創的開發者花了兩週,只為了三個只在 Claude Code 用的內部工具,架設 HTTP/SSE 傳輸層、認證 middleware、連線池的 MCP server。Function calling 兩小時就搞定了。兩週的工程時間,換來一套只服務過一個 client 的基礎設施。先從 function calling 開始。等你碰上第二個 client 再遷移到 MCP——不要更早。
生產環境的 MCP:認證、速率限制與錯誤處理
stdio 是開發用的。HTTP+SSE 是生產用的。 stdio 傳輸層把你的 MCP server 當成子行程跑——沒有認證、沒有網路安全、一個 server 行程只能接一個 client。放在你筆電的 claude_desktop_config.json 沒問題。拿來服務整個組織 50 個開發者就錯了。HTTP+SSE 傳輸層讓你把 server 部署成負載平衡器後面的獨立服務,有正式的認證、監控、獨立的部署循環。
認證 middleware。 截至 2026 年年中,MCP HTTP 傳輸層規範還沒有定義標準的認證機制。你要自己加。常見模式:在任何 MCP 訊息被處理之前,先驗證 Authorization: Bearer <key> 標頭裡的 API key。TokSpan 基礎的部署,平台會在 API gateway 層處理認證——你的 MCP server 在 https://api.tokspan.com/v1/ 收到的是已經過認證的請求。如果你要自架,就在 MCP handler 前面加上認證 middleware。
工具鏈第三層的過期 token——tool_a 呼叫 tool_b、tool_b 呼叫 tool_c——會產生一個難解的 JSON-RPC 錯誤,要花 45 分鐘才能追回認證層。先把認證弄對,再談其他。完整的安全檢查清單請看我們的生產環境安全檢查清單。
# Auth middleware for MCP HTTP server (FastAPI pattern)
from fastapi import FastAPI, Request, HTTPException
app = FastAPI()
VALID_TOKENS = {"mcp-secret-abc123", "mcp-prod-xyz789"} # Replace with DB lookup
@app.middleware("http")
async def auth_middleware(request: Request, call_next):
if request.url.path.startswith("/mcp/"):
token = request.headers.get("Authorization", "").removeprefix("Bearer ")
if token not in VALID_TOKENS:
raise HTTPException(status_code=401, detail="Invalid MCP API key")
return await call_next(request)
速率限制防止被 LLM 放大的事故。 只要一個描述鬆散的 tool——沒有 LIMIT 強制的 run_sql——LLM 就會因為使用者問了「把全部都給我看」而在凌晨 2 點產生掃描 1,000 萬列的查詢。伺服器端的 token bucket 速率限制器把 query_users 限制在每分鐘 60 次、create_user 限制在每分鐘 10 次。跨多個 server 實例做分散式速率限制用 Redis——每個 API key、每個工具名稱一組 INCR+EXPIRE。
沒有速率限制,一個過於熱情的 LLM prompt 就會變成生產資料庫故障的根本原因。你會在凌晨 3 點被 on-call 叫醒。你會追回一份你花 15 分鐘寫、六個 sprint 前就忘掉的工具定義。
能幫 LLM 復原的錯誤處理。 不要回傳 "Error: something went wrong."。LLM 會讀你的錯誤訊息再試一次。回傳三樣東西:錯誤類型、失敗的特定參數、以及有效的替代方案。這個錯誤是死重:"Invalid input." 這個能讓 Claude 一次重試就自我修正:"create_user failed: role must be one of [admin, editor, viewer]. Received: 'superadmin'." 這兩種錯誤訊息的差別,就是一條能自我修復的工具鏈,和一個盯著轉圈圈、最後放棄去開 ticket 的卡住使用者之間的差別。
常見的 MCP 陷阱:實際會壞在哪
這裡是 MCP 開發者第一個月的四個失敗模式。只要在部署前知道,每個都能避免。
模糊的工具描述。 這是誤選工具 bug 的第一大原因。"description": "Fetch data" 對 LLM 一點資訊都沒有。Claude Opus 4 會去猜要用哪個工具,而當多個工具描述重疊時,它常常猜錯。寫工具描述,就像你在跟一個從沒看過你 codebase 的開發者解釋這個工具一樣。包括工具做什麼、回傳什麼、有效輸入的範例、以及什麼時候不要用它。
上面資料庫 server 的 query_users 描述並不冗長——它精確到剛好消除歧義。
stdio buffer 限制。 stdio 傳輸層用 OS 的 pipe。Linux 預設 pipe buffer:64KB。你的 query_large_dataset 回傳 2MB 的 JSON。write() 呼叫就卡住了。client 掛住。你盯著轉圈圈 30 秒,然後 kill -9 那個行程。
任何可能回傳超過 64KB 的工具,都要實作回應分塊——用 page 和 page_size 參數把大結果切成頁。或者改用 HTTP 傳輸層,回應大小限制可以在 server framework 層設定。對每個工具設定 512KB 的硬性回應上限。MCP server 裡的任何工具,都不該回傳超過 LLM 在單一 context window 裡能有效處理的資料量。
跨 server 的工具名稱衝突。 你連上兩個 MCP server:一個來自資料庫團隊(get_status——複製延遲),一個來自維運團隊(get_status——部署健康度)。兩個都開放一個叫 get_status 的工具。Claude Code 會加上 server 名稱前綴:database-tools_get_status 和 ops-tools_get_status。但有些 MCP client,包括最近的 Cursor 版本,會默默挑選最早回應 tools/list 的那個 server。解法簡單到丟人、而且大家都沒在做:從第一天就把每個工具名稱命名空間化。db_query_users。ops_deploy_service。logs_search_errors。兩個字元的前綴,擋掉一整類的靜默失敗。
Schema 驗證的漏洞。 你的 schema 說 "type": "integer",LLM 還是會送 "user_id": "42"(字串)。你的 schema 說 "minimum": 1,它還是會送 "limit": -5。你的 schema 說 "enum": ["admin", "editor", "viewer"],它還是會送 "role": "superadmin"。你的 call_tool handler 必須驗證每個引數、在安全的地方強制轉型(int("42") —42)、對其他一切回傳具體的驗證錯誤。永遠別假設 LLM 會尊重你的 JSON Schema。它不會。你的 server 是介於幻覺出來的工具引數和生產資料庫之間的最後一道防線。
常見問題
我已經在用 function calling 了,還需要 MCP 嗎?
兩者是互補,不是二選一。Function calling 在 API 呼叫內執行工具。MCP 把工具發現與描述在應用程式之間標準化。用 MCP 定義你的工具目錄——關於有哪些工具、怎麼呼叫它們的單一事實來源。執行時用 function calling 實際呼叫那些工具。MCP 是介面層。Function calling 是執行層。如果你剛好只有一個 client 應用程式、三個工具,跳過 MCP,直接用 function calling。你會知道何時需要 MCP:就在你發現自己在 Claude Code 設定檔和自己的 app 的 tools 陣列之間複製貼上工具定義的那一刻。
可以用 MCP 搭配 OpenAI 的模型嗎?
可以——透過 adapter 或 OpenAI Agents SDK。原生支援比 Anthropic 不成熟,但對常見的工具模式堪用。如果你用支援 MCP gateway 的聚合平台,平台會處理轉換:你的 MCP server 搭配 GPT-5.5 和 GPT-5.5-mini 都能用,你完全不用寫任何 OpenAI 專屬的 MCP 程式碼。取捨是:社群 adapter 在新 MCP 規範功能上,大約比 Anthropic 的官方 SDK 慢 3 到 6 個月。
2026 年的 MCP 可以上生產了嗎?
本地工具走 stdio 傳輸層:可以,而且 Claude Code 的 stdio client 已經過數百萬小時開發者使用的實戰考驗。遠端服務走 HTTP:可以,但附帶條件是你必須自己加認證 middleware——HTTP 傳輸層的認證標準還在演進。協定核心(JSON-RPC 訊息格式、工具發現、工具執行)是穩定的,截至撰寫時,自 2025-03-26 規範更新以來沒有破壞性變更。如果你要自架 HTTP MCP server,認證、速率限制、日誌設定預留 2 到 3 天。
MCP 跟 API 聚合平台的關係是什麼?
聚合平台可以扮演 MCP gateway:你把自己的 MCP server 連上平台一次。平台把工具呼叫路由給任何 LLM——GPT-5.5、Claude Opus 4、Gemini 3、DeepSeek V3。不用供應商各自的 MCP 設定,就拿到多模型工具使用。一個 MCP server。全部模型。想知道聚合平台實際上怎麼實作 MCP gateway 路由,請看 TokSpan 的 MCP 整合文件。
可以把多個 MCP server 連到一個 client 嗎?
可以。Claude Code、Cursor 跟大部分 MCP client 都支援同時連多個 server——用唯一名稱把每個 server 加進設定檔,所有 server 的所有工具就會出現在 LLM 的可用工具清單裡。尖銳的邊角:跨 server 的工具名稱衝突。從第一天就用命名空間前綴命名你的工具(db_query、ops_deploy)。不要依賴 client 來消歧——不是每個 client 都會,會的那幾個處理方式也不一致。
兩個 MCP server 開放同名工具會怎樣?
取決於 client——這正是問題所在。Claude Code 會加上 server 名稱產生唯一識別碼:database-tools_get_status 和 ops-tools_get_status。Cursor 默默去重——最早回應 tools/list 的 server 贏。完全不要依賴 client 端消歧。幫每個工具名稱加上 server 專屬的命名空間前綴。db_get_status 和 ops_get_status 在源頭就消掉歧義。
出問題的時候,怎麼除錯 MCP 工具呼叫?
依序檢查三個地方。第一,client 的日誌——Claude Code 把 MCP 訊息寫進自己的日誌目錄。第二,你 server 的 stderr——stdio 傳輸層把所有 stderr 輸出送到 client 的控制台(stdin 跟 stdout 是留給 MCP 協定訊息的,但 stderr 可以自由拿來記日誌)。第三,在 server 上加上結構化的 JSON 日誌:{"event": "tool_call", "tool": "query_users", "args": {...}, "duration_ms": 45, "error": null}。結構化日誌能抓到那 80% 只在 on-call 值班時冒出來的 MCP bug——那時你已經身陷事故 15 分鐘,還不知道是哪個工具呼叫失敗、為什麼失敗。
MCP 支援從工具串流回應嗎?
目前規範不支援。MCP 工具呼叫回傳單一的 content 陣列——沒有部分回應、沒有進度更新、工具執行時沒有 Server-Sent Events。對資料庫遷移或報表生成這種長時間操作,立刻回傳 job ID,再開放一個獨立的 get_job_status 工具。LLM 輪詢它。這個 job ID 加輪詢的模式,是截至 2026 年年中每家主要生產 MCP server 都在用的既有事實標準。直接的串流支援在 MCP 規範的路線圖上,但沒有承諾的釋出日期。
MCP 在 2025–2026 年統一了 AI 工具整合——一個 server、任何 client、每種模型。但標準化會引來競爭。Google 的 Agent-to-Agent Protocol(A2A)在需要超越簡單工具呼叫的 agent 間通訊的企業團隊裡漸受歡迎。OpenAI 正在打造自家整合更深、平台整合更緊的 agent SDK 生態系。2027 年值得觀察的問題,不是 MCP 會不會存活——協定本身穩固、規範也穩定——而是「一個協定統治全部」能不能撐得比產業對平台專屬整合的胃口更久,那些整合用更緊的耦合跟更好的效能,換來的是鎖定。
你在這篇文章第一節建立的那個 MCP server,只能搭配一個 client——Claude Desktop。MCP gateway 把同一個 server 連到你技術棧裡的每個 LLM,於是你寫一次的工具定義,餵給你用的每個模型。TokSpan 的 MCP 整合文件涵蓋 gateway 設定、認證設定、以及上面那一節的生產環境檢查清單——把那個 50 行的 stdio server,變成一個不用動一行工具程式碼、就能挺過供應商切換的多模型工具層。