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