給 Claude Code 接入內部工具:用 MCP 打通公司內部系統

用 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、做資料轉換、或者加商業邏輯的場景。

這篇文章兩種都講,先從簡單的開始。

方式一:用 mcp-server-fetch 快速接入

最輕量的方案是用官方的 @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 請求,拿到回應,解析給你看。

但這種方式有明顯的限制:

  • 每次都要告訴 Claude 完整的 URL 和請求格式
  • 沒有參數驗證,Claude 可能拼錯路徑
  • 認證資訊需要每次傳,或者寫在 prompt 裡(不安全)
  • 沒辦法組合多個 API 呼叫

適合臨時用,不適合長期方案。

方式二:寫專用 MCP Server

當你需要反覆使用某個內部系統,寫一個專用的 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 — 查看最近的部署日誌

用 TypeScript 寫 MCP Server

先初始化專案:

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

設定 Claude Code

{
  "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 對標註了副作用的操作會自動請求使用者確認。比如建立工單、發訊息、更新設定。

高風險寫入操作建議不接。刪除資源、觸發回滾、修改權限——這些操作的後果嚴重且不可逆,留在手動操作更安全。

如果一定要接寫入操作,至少做兩件事:

  1. 工具描述裡明確寫「這是寫入操作,會影響正式環境」
  2. 在 MCP Server 裡加必要的安全檢查(比如不允許部署到 production 命名空間)

常見內部系統的接入思路

系統 暴露的工具 注意事項
部署平台(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 放哪裡

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 錯誤追蹤

用一個真實的案例串起來。假設要把 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,自己實作邏輯、管理狀態、處理複雜的多步互動。