要把 REST API 轉成 MCP 伺服器,可以在 API 前方加入一個小型轉換層。這一層註冊工作層級的工具、驗證輸入、呼叫既有端點,再回傳範圍明確的結構化結果。REST API 與資料模型都不必改變。
先從一項唯讀工作開始。在確認端點邊界對代理是否合理前,不要為每個端點都生成一項工具。
從工作開始,而不是端點清單
OpenAPI 文件是很實用的服務清單,但機械式的一對一端點轉工具,往往會產生雜亂的工具集。代理要回答「訂單 1842 到哪裡了?」時,需要的是訂單狀態動作,不需要自行串接內部查詢、貨運資料取得及狀態碼轉換等多項工具。
先寫下工作、完成它所需的最少輸入,以及最精簡且有用的結果,再把這份合約對應到處理函式內的一個或多個 REST 呼叫。搭配閱讀可靠 MCP 工具設計,可進一步了解命名與工具邊界。
註冊具型別的 MCP 工具
目前的 MCP TypeScript SDK 提供 McpServer、registerTool 及 createMcpHandler。以下範例將 GET /orders/:id 包裝成唯讀的 get-order-status 工具。
import { createMcpHandler, McpServer } from '@modelcontextprotocol/server';
import * as z from 'zod/v4';
const OrderStatus = z.object({
orderId: z.string(),
status: z.enum(['processing', 'shipped', 'delivered', 'cancelled']),
updatedAt: z.string(),
});
const handler = createMcpHandler(({ authInfo }) => {
const server = new McpServer({ name: 'orders', version: '1.0.0' });
server.registerTool(
'get-order-status',
{
description: 'Get the current status of one order the caller can access.',
inputSchema: z.object({
orderId: z.string().min(1).describe('The order identifier, such as 1842'),
}),
outputSchema: OrderStatus,
},
async ({ orderId }) => {
const response = await fetch(
`${process.env.ORDERS_API_URL}/orders/${encodeURIComponent(orderId)}`,
{
headers: {
Authorization: `Bearer ${process.env.ORDERS_API_TOKEN}`,
'X-Caller-ID': authInfo?.clientId ?? 'unknown',
},
},
);
if (!response.ok) {
return {
isError: true,
content: [{
type: 'text',
text: `Order lookup failed with HTTP ${response.status}`,
}],
};
}
const order = OrderStatus.parse(await response.json());
return {
structuredContent: order,
content: [{ type: 'text', text: JSON.stringify(order) }],
};
},
);
return server;
});
export default handler;
輸出結構描述讓用戶端取得穩定的結果形式;文字版本則與尚未支援結構化工具結果的用戶端相容。MCP 工具規格建議在有結構化內容時同時回傳兩者。
在處理函式前驗證身分
createMcpHandler 不會替你驗證 bearer token。應在請求抵達處理函式前驗證呼叫者,再透過 authInfo 傳入已驗證的身分。上游 REST 服務仍須授權該身分,或授權範圍更窄的服務憑證。呼叫者 ID 標頭有助於追蹤來源,但它本身不是授權機制。
供應商 token 要留在伺服器端,不能出現在工具引數、工具結果、提示詞或用戶端可見的錯誤中。對寫入工具,應把憑證範圍限制在允許的動作,並要求破壞性或財務操作取得核准。MCP 基礎設施安全指南說明這道權限邊界。
回傳代理能採取行動的錯誤
單一 HTTP 狀態很少能告訴代理下一步該做什麼。應將已知失敗轉成簡短、穩定的原因:
| REST 失敗 | MCP 結果 | 代理動作 |
|---|---|---|
400,訂單 ID 無效 |
invalid_order_id |
重試前修正引數 |
401 或 403 |
not_authorized |
停止並要求不同的權限範圍 |
404 |
order_not_found |
檢查 ID 或詢問使用者 |
429 |
rate_limited 及重試時間 |
等待一次再重試 |
5xx |
upstream_unavailable |
只有操作安全時才重試 |
對變更操作,應接受冪等鍵並轉傳給 REST API。若伺服器已完成寫入但回應遺失,重試包裝層無法讓不具冪等性的端點變安全。
透過 MCP 用戶端測試工具
測試註冊後的合約,而不是直接呼叫 REST 處理函式。涵蓋一次成功呼叫,以及會改變代理行為的失敗類型:
- 結構描述會拒絕缺漏或無效輸入;
- 呼叫者無法取得其他呼叫者的訂單;
- 上游逾時會回傳範圍明確且不含密鑰的錯誤;
- 以同一個冪等鍵重複寫入只產生一次效果;
- 破壞性動作會停在核准邊界。
SDK 的 HTTP 服務指南也要求本機伺服器驗證 Host 與 Origin,避免 DNS rebinding。其框架工廠會在 localhost 啟用這些防護;直接使用 Node 伺服器則必須自行加入。
部署前先驗證一項唯讀工具
選擇能回答一個常見使用者問題的端點,定義其輸入與輸出結構描述、透過真實 MCP 用戶端呼叫,並檢查完整工具結果。只有在授權及失敗測試都通過後才部署。當實際觀察到某項工作無法由第一份合約清楚完成時,再加入下一項工具。
如果第一項工具通過這些檢查,下方提示詞會將部署交給代理。今天完成一項工具就夠了。