引言

如果你和我一样,写惯了 Spring Boot 代码,最近开始搞 AI 应用开发,大概率会遇到一个灵魂拷问:我的 AI 助手要怎么调用我的工具?

靠 Function Calling?靠 Prompt 工程?还是干脆让人手工复制粘贴结果?

这些方案我都试过。结论是:都不太优雅。

直到我认真研究了 MCP 协议(Model Context Protocol),才觉得这个问题终于有了一个像样的标准答案。MCP 不是那种"听起来很美好"的协议,它是 Anthropic 真正拿出来、并且已经在生产环境中跑起来的开放协议。本文不讲理论,不念文档,直接聊我实际踩过的坑、跑通过的代码、以及它和 Function Calling 的真实对比。

一、MCP 到底是什么

MCP 协议与 AI 工具链

MCP 的定位很简单:给 AI 模型和外部工具之间定义一套标准化的通信协议。

类比一下:如果把 AI 模型当成一个"大脑",那 MCP 就是它的"手"——通过这套协议,AI 可以主动调用你写的工具,获取数据,执行操作,而不是每次都需要人肉介入。

官方描述说它是"一种开放协议,用于将 AI 助手与数据源和工具连接起来"。听起来有点抽象,我用自己的话翻译一下:MCP 就是 AI 世界的 USB 接口标准——你只要实现一端,另一端插上就能用。

目前 MCP 的生态已经有不少现成的 Server 实现(GitHub、Filesystem、数据库等),但我更关心的是:怎么自己写一个 MCP Server,因为实际业务中的工具才是关键。

1.1 核心架构

C/S 分布式架构图

MCP 采用 C/S 架构,两端角色如下:

  • **MCP Host**:AI 应用本身,比如 Claude Desktop,或者你自己写的 AI 应用**MCP Server**:暴露工具和资源的服务端,可以本地运行,也可以远程部署

通信方式支持两种:

1. stdio 模式:通过标准输入/输出通信,适合本地进程

2. HTTP + SSE 模式:支持远程调用,适合分布式场景

二、第一个 MCP Server:手把手实战

2.1 环境准备

我用的是 Node.js 生态的 @modelcontextprotocol/sdk,因为 TypeScript 写起来快,类型提示也完整。Java 同学别急,我后面有 Java 版的实现方案。

mkdir my-mcp-server && cd my-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk zod

2.2 最简 Server 代码

先上一个极简版,让大家感受 MCP Server 长什么样:

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

// 创建 MCP Server 实例
const server = new McpServer({
  name: "我的第一个MCP服务器",
  version: "1.0.0",
});

// 注册一个查询订单状态的工具
server.tool(
  "get_order_status",
  "根据订单号查询订单状态",
  {
    orderId: z.string().describe("订单ID"),
  },
  async ({ orderId }) => {
    // 这里是真实的业务逻辑
    const status = await queryOrderFromDatabase(orderId);
    return {
      content: [
        {
          type: "text",
          text: JSON.stringify(status, null, 2),
        },
      ],
    };
  }
);

// 启动服务,stdio 模式
const transport = new StdioServerTransport();
server.run(transport);

这段代码做的事情很简单:暴露了一个叫 get_order_status 的工具,AI 应用调用时会执行真实的数据库查询逻辑。

2.3 注册到 Claude Desktop

光写出来还不够,得让 AI 能发现它。在 Claude Desktop 中,需要配置 claude_desktop_config.json

{
  "mcpServers": {
    "order-service": {
      "command": "node",
      "args": ["/path/to/your/server.js"],
      "env": {
        "DB_HOST": "localhost",
        "DB_PORT": "3306"
      }
    }
  }
}

配置完成后重启 Claude Desktop,在对话中直接说"帮我查一下订单 ORD20260315 的状态",AI 就会自动调用这个工具,整个过程对用户完全透明。

三、踩坑实录一:stdio 模式下的进程存活问题

进程管理与调试排查

问题现象

本地开发时一切正常,工具调用完美。但当我把这个 MCP Server 部署到生产服务器,用 systemd 管理进程时,问题出现了:进程启动后 30 秒左右就自动退出了,没有任何错误日志。

排查过程

1. 先看 systemd 日志:journalctl -u my-mcp-server -f,看到的是 Main process exited, code=exited, status=1/FAILURE

2.怀疑是 Node.js 版本问题,换了版本,问题依旧

3.怀疑是权限问题,给足 777,问题依旧

4.最后在本地复现:直接在终端前台运行 ./server.js,发现进程正常运行。但一加上 | cat 或任何管道操作,进程就退出了

根因

MCP 的 stdio 模式对 stdin/stdout 的占用非常严格。systemd 默认会为服务配置 StandardInput=null 或者做 stdout 重定向,这些操作会劫持 stdio 通道,导致 MCP Server 的通信管道被破坏,Server 检测到异常后主动退出。

解决方案

在 systemd unit 文件中明确配置 stdio:

[Unit]
Description=My MCP Server

[Service]
ExecStart=/usr/bin/node /opt/my-mcp-server/server.js
StandardInput=null
StandardOutput=null
StandardError=journal
Restart=always
RestartSec=5

# 关键配置:防止 systemd 关闭 stdio 管道
StdPath=/dev/null

实际上更推荐的方式是:直接用 ExecStart=/usr/bin/node /opt/my-mcp-server/server.js 不带任何管道重定向,让 MCP Server 独享 stdio 通道。systemd 会自动将 stdout/stderr 写入 journal,不需要你额外配置。

四、踩坑实录二:工具返回结果太大导致截断

大数据量分页处理

问题现象

AI 调用某个查询工具时,返回了非常长的 JSON 数据(几千行),但在 AI 的回复中只看到部分数据,中间有一段明显被截断了。AI 还"自作主张"地补充了一句:"由于数据量较大,这里仅展示前 500 条。"

但我的业务逻辑需要 AI 看到完整数据来做分析!

排查过程

1. 先确认是 MCP Server 侧的问题还是 AI 侧的问题

2. 在 Server 端加了日志,确认返回的 content 确实是完整的

3. 后来翻文档发现,MCP 协议对工具返回的 content 数组中每个元素的 text 字段有长度建议,Claude 对单条消息的总长度有限制

4. 用 anthropic:200000 token 限制换算了一下,确实踩到了边界

根因

MCP Server 返回数据时没有做分页或压缩,AI 模型对单次工具调用的返回有 token 限制。对于超大型返回,Server 需要主动分页或者只返回摘要信息。

解决方案

修改 Server 端代码,增加分页逻辑:

server.tool(
  "search_orders",
  "搜索订单列表",
  {
    keyword: z.string().describe("搜索关键词"),
    page: z.number().optional().default(1).describe("页码,从1开始"),
    pageSize: z.number().optional().default(50).describe("每页条数"),
  },
  async ({ keyword, page = 1, pageSize = 50 }) => {
    const allResults = await searchOrders(keyword);
    
    // 分页处理
    const start = (page - 1) * pageSize;
    const end = start + pageSize;
    const pageData = allResults.slice(start, end);
    
    const hasMore = end < allResults.length;
    
    return {
      content: [
        {
          type: "text",
          text: JSON.stringify({
            data: pageData,
            pagination: {
              current: page,
              pageSize,
              total: allResults.length,
              hasMore,
            }
          }, null, 2),
        },
      ],
    };
  }
);

AI 这边现在会先返回分页信息,如果需要更多数据,可以让它自己再调用一次指定页码。

五、MCP Server 的资源(Resources)机制

除了工具(Tools),MCP 还有另一个核心概念:Resources(资源)

工具是"AI 可以主动执行的操作",资源是"AI 可以读取的数据"。

举例:一个 MCP Server 可以同时暴露:

// 暴露一个 Resource(只读数据)
server.resource(
  "order://{orderId}",
  "获取订单详情",
  async (uri) => {
    const orderId = uri.pathname.split("/").pop();
    const order = await getOrder(orderId);
    return {
      mimeType: "application/json",
      content: JSON.stringify(order),
    };
  }
);

// 暴露一个 Tool(可执行操作)
server.tool(
  "cancel_order",
  "取消订单",
  { orderId: z.string() },
  async ({ orderId }) => { ... }
);

AI 可以读取 Resource 来获取上下文(context),也可以调用 Tool 来执行操作。两者配合,构成了完整的 AI 工具调用能力。

六、MCP vs Function Calling:真实对比

这部分是我被问得最多的:"MCP 和 Function Calling 到底有什么区别?我已经有 Function Calling 了,还需要 MCP 吗?"

我直接上对比表:

| 维度 | Function Calling | MCP |

|------|-----------------|-----|

| 标准化程度 | 各家 AI 厂商实现不一,格式各异 | 开放统一协议,跨厂商通用 |

| 工具注册方式 | 在 AI 请求时动态传入 | Server 预注册,AI 启动时发现 |

| 状态管理 | 无状态,每次调用独立 | 支持有状态的 Resource 机制 |

| 工具发现 | 依赖 Prompt 描述 | 协议层自动发现类型和参数 |

| 传输方式 | HTTP | stdio / HTTP+SSE |

| 生态成熟度 | 成熟,各大厂商支持 | 快速发展,生态正在扩大 |

| 适用场景 | 简单调用,厂商通用 | 复杂系统,需要标准化接口 |

| 调试便利性 | 较低,需反复调 Prompt | 高,可独立测试 Server |

我的实际感受

  • 如果你只接一个 AI 模型、工具数量少于 10 个、调用逻辑简单 → Function Calling 够用,别折腾如果你是平台型产品、需要对接多种 AI 模型、工具数量多且会不断扩展 → MCP 值得投入MCP 的最大价值在于**工具的可复用性**:写一次 MCP Server,Claude 可以用、Kimi 可以用、国产大模型以后支持了也能用

七、Java 生态下的 MCP 实现

Java Spring Boot 技术栈

TypeScript 写起来爽,但很多同学的公司是 Java 技术栈。我专门研究了一下 Java 生态的实现方案。

7.1 官方 SDK 状态

截至目前(2026年Q1),MCP 官方主要维护的是 TypeScript/Python SDK,Java SDK 处于社区维护状态,功能相对有限。

7.2 方案一:直接基于 SSE/STDIO 协议解析

MCP 的协议是基于 JSON-RPC 2.0 的,理论上任何语言都可以实现。我参考了官方协议的规范文档,自己撸了一个简化版的 Java Server:

// 核心是一个 JSON-RPC 2.0 的消息处理器
// 伪代码示例,展示关键思路

public class McpServer {
    private final Map<String, ToolHandler> tools = new HashMap<>();
    
    public void registerTool(String name, ToolHandler handler) {
        tools.put(name, handler);
    }
    
    public void startStdio() throws IOException {
        BufferedReader reader = new BufferedReader(
            new InputStreamReader(System.in));
        PrintWriter writer = new PrintWriter(System.out);
        
        String line;
        while ((line = reader.readLine()) != null) {
            JsonRpcRequest request = parse(line);
            JsonRpcResponse response = handle(request);
            writer.println(serialize(response));
            writer.flush();
        }
    }
    
    private JsonRpcResponse handle(JsonRpcRequest request) {
        ToolHandler handler = tools.get(request.getMethod());
        if (handler == null) {
            return new JsonRpcResponse(request.getId(), 
                new JsonRpcError(-32601, "Method not found"));
        }
        try {
            Object result = handler.execute(request.getParams());
            return new JsonRpcResponse(request.getId(), result);
        } catch (Exception e) {
            return new JsonRpcResponse(request.getId(), 
                new JsonRpcError(-32603, e.getMessage()));
        }
    }
}

7.3 方案二:使用 Spring AI Alibaba 的 MCP 支持

如果你已经在用 Spring AI Alibaba,可以关注它的 MCP 集成。Spring AI 0.8.x+ 版本开始支持 MCP 协议,虽然功能还在完善中,但与 Spring 生态集成良好,是 Java 同学最值得期待的方案。

八、生产环境部署架构建议

MCP Server 写完了,接下来怎么部署?我的经验是分两种场景:

场景一:本地 AI 应用(Claude Desktop 等)

直接用 stdio 模式,本地启动即可。配置简单,延迟最低。

场景二:自己开发的 AI 应用(服务端)

推荐用 HTTP + SSE 模式,架构如下:

AI App (HTTP Client)
     ↓ HTTP POST /jsonrpc
MCP Server (HTTP + SSE)
     ↓
业务服务层(订单系统、数据库等)

这样 MCP Server 可以独立部署、独立扩展,和 AI 应用解耦。

九、总结

说几个核心要点:

1. MCP 的本质是标准化:它解决的不是"AI 能不能调用工具"的问题,而是"怎么让工具调用这套机制在不同的 AI 和系统之间通用"的问题

2. stdio 模式很脆弱:部署时注意进程管理和管道重定向,systemd 配置不当会导致 Server 无法正常启动

3. 工具返回要做限流:AI 对单次调用返回有 token 上限,大数据量场景必须做分页,避免数据被截断导致 AI 判断失误

4. Java 生态还在追赶:目前 Java SDK 成熟度不如 TypeScript,但如果已有 Spring AI 体系,可以重点关注其 MCP 集成进展

最后留个链接:https://www.lsn.org.cn

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐