给 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,自己实现逻辑、管理状态、处理复杂的多步交互。