首页/Agent/把内部工具接进 Claude:一份 MCP 集成实录
Agent 已验证可复现

把内部工具接进 Claude:一份 MCP 集成实录

这篇是一份实录:从零写一个 MCP server,把我们内部的工单系统接给 Claude,让它能查工单、也能建工单。代码可直接跑。

可复现前提: Node ≥ 20,@modelcontextprotocol/sdk ≥ 1.0。示例中的内部 API 已替换成一个 mock server,npm run mock 即可启动,不需要内网权限。

一、先想清楚:暴露什么,不暴露什么

写代码前我列了一张表,这一步比写代码重要:

能力 是否暴露 理由
查询工单 只读,风险低
创建工单 是,但要确认 有副作用,加一步人确认
修改工单状态 是,但限本人 越权风险
删除工单 不可逆,坚决不给

不可逆操作一律不暴露,这是我们组现在的硬规矩。

二、最小可用的 server

servers/tickets.tstypescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

const server = new McpServer({ name: "tickets", version: "1.0.0" });

server.tool(
  "search_tickets",
  "按关键词与状态检索内部工单",
  {
    query: z.string().describe("检索关键词"),
    status: z.enum(["open", "closed", "all"]).default("open"),
    limit: z.number().int().min(1).max(50).default(10),
  },
  async ({ query, status, limit }) => {
    const res = await fetch(`${process.env.TICKET_API}/search`, {
      method: "POST",
      headers: {
        "content-type": "application/json",
        authorization: `Bearer ${process.env.TICKET_TOKEN}`,
      },
      body: JSON.stringify({ query, status, limit }),
    });
    if (!res.ok) {
      return {
        content: [{ type: "text", text: `检索失败: ${res.status} ${await res.text()}` }],
        isError: true,
      };
    }
    const data = await res.json();
    return { content: [{ type: "text", text: JSON.stringify(data.items, null, 2) }] };
  },
);

await server.connect(new StdioServerTransport());

二·一、工具描述比实现更重要

这是我花了最多时间调的地方。第一版描述我写的是"检索工单",模型经常在该用它的时候不用。改成下面这样之后命中率明显上升:

代码text
按关键词与状态检索内部工单。当用户提到工单号、bug 编号、
"那个问题"、"上次报的issue"时都应该调用本工具。
返回工单标题、状态、负责人与最后更新时间。

把"什么时候该调我"写进描述里,比反复调 system prompt 有效得多。

三、错误处理:别让模型猜

三条经验:

  • 错误必须返回 isError: true,否则模型会把错误信息当成正常结果继续推理。
  • 错误文本要人类可读,500 Internal Error 这种模型没法处理,工单 ID 不存在,请确认编号 它就会去追问用户。
  • 超时一定要自己设。默认不设超时的话,内网抖动会让整个会话卡死。

四、鉴权:不要用共享 token

我们最开始用了一个服务级 token,所有人调都是同一个身份。两周后就出事了:有人通过 Claude 改了别人负责的工单。

现在的做法是每个人一个个人 token,写在本地 ~/.praxis/tokens,MCP server 启动时读取:

代码bash
# 每人执行一次,token 只存在本机
praxis auth login
# 校验:应输出你自己的工号
praxis auth whoami

五、复现步骤

代码bash
git clone git@internal:praxis/mcp-tickets-demo.git
cd mcp-tickets-demo && npm i
npm run mock          # 启动 mock 工单服务
npm run register      # 写入 ~/.claude/mcp.json
claude "帮我查一下最近 open 状态、关键词 login 的工单"

预期:Claude 会调用 search_tickets,返回 3 条 mock 工单。跑不通的话贴报错到评论区,我基本都遇到过。

4.8/ 5
6 位同事评分
这篇实践你能照着复现吗?给它打个分:
每人一次,可随时修改

评论与复现反馈 4

登录 后可以评论、评分,并把复现结果反馈给作者。
评审员 已复现2 天前

这条我也验证了,效果显著。我们把工具描述从 12 字扩到 60 字左右,误选率大概降了一半。

评审员 已复现1 天前

鉴权那一节建议置顶。我们之前也是共享 token,出过一次类似的事。个人 token 方案我照着接了,praxis auth whoami 能正确返回工号。

6 小时前

跑到 npm run register 这一步报 EACCES,是需要先创建 ~/.claude/ 目录吗?

评审员5 小时前

是的,老版本 CLI 不会自动建。mkdir -p ~/.claude 之后再跑一次就好,我把这句加进 README 了。

相关实践

全部 →