用 MCP 把公司內部系統接入 Claude Code,從通用 fetch 到自己寫專用 Server,涵蓋部署平台、監控、工單等場景的接入方法和工具設計原則。
上一篇講了接入資料庫。資料庫有標準協定,接起來相對直接。但大多數團隊的日常工作還依賴一堆內部系統:部署平台、監控面板、工單系統、內部 API、設定中心。
這些系統通常沒有現成的 MCP Server,但它們幾乎都提供了 HTTP API。這篇文章講怎麼用 MCP 把這些內部工具接入 Claude Code,讓它直接幫你查監控、看部署狀態、操作工單。
接入內部工具有兩種方式:
方式一:用通用 HTTP MCP Server
社群有一些通用的 MCP Server,可以把任意 REST API 包裝成 MCP 工具。你寫一份 API 描述檔,它幫你轉成 Claude 能呼叫的工具。適合 API 結構簡單、不需要複雜邏輯的場景。
方式二:自己寫一個 MCP Server
用 TypeScript 或 Python 的 MCP SDK 寫一個專用 Server,完全控制工具的定義、參數驗證、錯誤處理。適合需要組合多個 API、做資料轉換、或者加商業邏輯的場景。
這篇文章兩種都講,先從簡單的開始。
最輕量的方案是用官方的 @anthropic-ai/mcp-server-fetch,它讓 Claude 可以直接發 HTTP 請求。設定極簡:
{
"mcpServers": {
"fetch": {
"command": "npx",
"args": ["-y", "@anthropic-ai/mcp-server-fetch"]
}
}
}
設定好之後,Claude 就能直接呼叫內部 API 了:
幫我查一下部署平台 https://deploy.internal.com/api/v1/services/user-service 的目前狀態
Claude 會發 GET 請求,拿到回應,解析給你看。
但這種方式有明顯的限制:
適合臨時用,不適合長期方案。
當你需要反覆使用某個內部系統,寫一個專用的 MCP Server 是更好的選擇。下面用一個實際場景示範:接入公司的部署平台。
假設你的部署平台提供以下 API:
GET /api/v1/services — 列出所有服務GET /api/v1/services/:name/status — 查看服務狀態POST /api/v1/services/:name/deploy — 觸發部署GET /api/v1/services/:name/logs — 查看最近的部署日誌先初始化專案:
mkdir mcp-deploy && cd mcp-deploy
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node
npx tsc --init
核心程式碼 src/index.ts:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const API_BASE = process.env.DEPLOY_API_URL!;
const API_TOKEN = process.env.DEPLOY_API_TOKEN!;
async function api(path: string, method = "GET", body?: unknown) {
const res = await fetch(`${API_BASE}${path}`, {
method,
headers: {
Authorization: `Bearer ${API_TOKEN}`,
"Content-Type": "application/json",
},
body: body ? JSON.stringify(body) : undefined,
});
if (!res.ok) {
throw new Error(`API error: ${res.status} ${await res.text()}`);
}
return res.json();
}
const server = new McpServer({
name: "deploy-platform",
version: "1.0.0",
});
// 列出所有服務
server.tool("list_services", "列出部署平台上的所有服務及其狀態", {}, async () => {
const data = await api("/api/v1/services");
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
});
// 查看單一服務狀態
server.tool(
"service_status",
"查看指定服務的目前部署狀態、版本號和健康檢查結果",
{ name: z.string().describe("服務名稱,如 user-service") },
async ({ name }) => {
const data = await api(`/api/v1/services/${name}/status`);
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
}
);
// 查看部署日誌
server.tool(
"deploy_logs",
"查看指定服務最近的部署日誌",
{
name: z.string().describe("服務名稱"),
limit: z.number().optional().default(10).describe("回傳筆數,預設 10"),
},
async ({ name, limit }) => {
const data = await api(`/api/v1/services/${name}/logs?limit=${limit}`);
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
}
);
// 觸發部署
server.tool(
"trigger_deploy",
"觸發指定服務的部署。這是一個寫入操作,會實際影響正式環境",
{
name: z.string().describe("服務名稱"),
version: z.string().describe("要部署的版本號或 git ref"),
},
async ({ name, version }) => {
const data = await api(`/api/v1/services/${name}/deploy`, "POST", {
version,
});
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
}
);
const transport = new StdioServerTransport();
server.connect(transport);
編譯執行:
npx tsc
{
"mcpServers": {
"deploy": {
"command": "node",
"args": ["/path/to/mcp-deploy/dist/index.js"],
"env": {
"DEPLOY_API_URL": "https://deploy.internal.com",
"DEPLOY_API_TOKEN": "your-api-token"
}
}
}
}
把 token 放在 .claude/settings.local.json(不提交 git),URL 放在 .claude/settings.json(提交 git,團隊共用)。
設定完之後,對話就變得自然了:
user-service 現在什麼狀態?
→ Claude 呼叫 service_status("user-service")
→ 回傳:執行中,版本 v2.3.1,最後部署 2 小時前,健康檢查全部通過
最近幾次部署有沒有失敗的?
→ Claude 呼叫 deploy_logs("user-service", 20)
→ 分析日誌,告訴你第 3 次部署回滾了,原因是健康檢查逾時
把 user-service 部署到 v2.3.2
→ Claude 呼叫 trigger_deploy("user-service", "v2.3.2")
→ 因為工具描述裡標註了「寫入操作」,Claude 會先跟你確認
這是個需要認真考慮的問題。
讀取操作放心接。查狀態、看日誌、搜工單,這些操作無副作用,Claude 做錯了也沒損失。
寫入操作分兩種情況:
低風險寫入操作可以接,但在工具描述裡標註清楚。Claude 對標註了副作用的操作會自動請求使用者確認。比如建立工單、發訊息、更新設定。
高風險寫入操作建議不接。刪除資源、觸發回滾、修改權限——這些操作的後果嚴重且不可逆,留在手動操作更安全。
如果一定要接寫入操作,至少做兩件事:
| 系統 | 暴露的工具 | 注意事項 |
|---|---|---|
| 部署平台(K8s / Kamal) | 查服務狀態、查日誌、觸發部署 | 寫入操作加確認 |
| 監控系統(Grafana / Datadog) | 查看指標、查告警歷史 | 控制查詢時間範圍,避免拉太多資料 |
| 工單系統(Jira / Linear) | 搜尋工單、建立工單、更新狀態 | 建立工單是寫入操作,但低風險 |
| 內部文件(Notion / Confluence) | 搜尋文件、讀取頁面內容 | 注意分頁,單次不要拉太多 |
| 設定中心(Consul / etcd) | 讀取設定、比較環境差異 | 只做讀取,不要接寫入操作 |
| CI/CD(GitHub Actions / Jenkins) | 查看建置狀態、觸發建置 | 觸發建置算中風險寫入操作 |
寫 MCP 工具和寫 API 不一樣。API 是給工程師用的,工具是給 AI 用的。有幾個原則值得注意:
工具名稱要直白
✗ get_svc_stat — Claude 不一定能猜對縮寫含義
✓ service_status — 一看就知道做什麼
描述要寫給 AI 看
工具描述不是給人看的文件,是給 Claude 判斷「什麼時候該呼叫這個工具」的依據。描述裡要說清楚:這個工具做什麼、回傳什麼、什麼時候該用。
✗ "取得服務狀態"
✓ "查看指定服務的目前部署狀態、版本號和健康檢查結果。當使用者問某個服務是否正常執行時使用"
參數用 zod 定義清楚
帶 .describe() 的參數,Claude 才知道該填什麼。沒有描述的參數,Claude 只能根據名字猜。
回傳結構化資料
MCP 工具回傳的是文字,但盡量回傳格式化的 JSON。Claude 處理結構化資料比處理純文字準確得多。
粒度適中
不要把一個複雜流程塞進一個工具。也不要把一個簡單查詢拆成三個工具。原則是:一個工具完成一個獨立的、有意義的操作。
MCP Server 的程式碼可以放幾個位置:
專案倉庫內(建議起步方案)
your-project/
├── .claude/settings.json
├── mcp-servers/
│ └── deploy/
│ ├── src/index.ts
│ ├── package.json
│ └── tsconfig.json
└── ...
好處是程式碼和設定在一起,團隊 clone 下來裝完相依套件就能用。
獨立倉庫
當 MCP Server 要跨多個專案使用時,放獨立倉庫,發佈成 npm 套件或 Docker 映像。
{
"mcpServers": {
"deploy": {
"command": "npx",
"args": ["-y", "@yourcompany/mcp-deploy-server"]
}
}
}
全域安裝
對於公司範圍通用的 MCP Server(比如接入公司統一認證、統一日誌平台),全域安裝然後設定在 ~/.claude/settings.json 裡。
MCP Server 開發時最常見的問題是「Claude 沒呼叫我的工具」或「呼叫了但報錯」。
確認 Server 啟動了
重啟 Claude Code 後,輸入 /mcp 查看已連線的 MCP Server 清單。如果你的 Server 不在清單裡,檢查 command 和 args 是否正確。
單獨測試 Server
MCP Server 用 stdio 通訊,可以直接在終端測試:
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | node dist/index.js
如果回傳了工具清單,說明 Server 本身沒問題。
看 Claude 的工具呼叫日誌
Claude Code 會顯示每次工具呼叫的輸入和輸出。如果參數傳錯了,通常是工具描述或參數定義不夠清楚,Claude 理解錯了。
用一個真實的案例串起來。假設要把 Sentry 接入 Claude Code,讓它能直接查線上錯誤。
server.tool(
"search_errors",
"在 Sentry 中搜索最近的錯誤。用於排查線上問題、查看錯誤趨勢",
{
query: z.string().describe("搜索關鍵詞,如錯誤訊息、函式名"),
hours: z.number().optional().default(24).describe("查看最近多少小時的錯誤"),
},
async ({ query, hours }) => {
const since = new Date(Date.now() - hours * 3600000).toISOString();
const data = await api(
`/api/0/projects/${ORG}/${PROJECT}/issues/?query=${encodeURIComponent(query)}&start=${since}&sort=date`
);
const summary = data.map((issue: any) => ({
title: issue.title,
count: issue.count,
firstSeen: issue.firstSeen,
lastSeen: issue.lastSeen,
link: issue.permalink,
}));
return {
content: [{ type: "text", text: JSON.stringify(summary, null, 2) }],
};
}
);
server.tool(
"error_details",
"查看 Sentry 中某個錯誤的詳細資訊,包括堆疊和最近一次事件",
{ issueId: z.string().describe("Sentry issue ID") },
async ({ issueId }) => {
const [issue, latest] = await Promise.all([
api(`/api/0/issues/${issueId}/`),
api(`/api/0/issues/${issueId}/events/latest/`),
]);
return {
content: [
{
type: "text",
text: JSON.stringify(
{
title: issue.title,
count: issue.count,
users: issue.userCount,
stacktrace: latest.entries?.find(
(e: any) => e.type === "exception"
),
},
null,
2
),
},
],
};
}
);
接好之後,排查線上問題的對話變成這樣:
最近 4 小時有沒有新的 500 錯誤?
→ Claude 搜尋 Sentry
→ 發現 3 個新 issue,最嚴重的一個影響了 120 個使用者
→ 自動拉堆疊,定位到是某個空指標例外
→ 在程式碼裡找到對應位置,給出修復方案
從發現問題到定位程式碼,整個過程在一次對話裡完成。
這篇講了怎麼用 MCP 接入內部工具。核心思路是:內部系統有 HTTP API → 寫一個 MCP Server 包裝一下 → Claude 就能直接用。
這篇的所有範例都是在包裝已有的 API——部署平台、Sentry 本身就有介面,MCP Server 只是做了一層轉發和適配。下一篇會講一個不同的場景:當你需要的能力根本沒有現成 API 時,怎麼從零建構一個 MCP Server,自己實作邏輯、管理狀態、處理複雜的多步互動。