MCP (Model Context Protocol)

一、MCP是什么

MCP 是 Anthropic 2024 年底提出的开放协议,解决的问题是:AI 应用要接入外部工具和数据源时,每接一个就要写一套适配代码,碎片化严重。

核心概念

  • MCP Server:对外暴露能力的服务端,封装了具体的工具、资源、提示词模板
  • MCP Client:大模型应用(如 Claude Desktop、Cursor、各种 Agent 框架),通过协议连接 Server

三种原语

  • Tools:可执行的操作(等价于 Function Call)
  • Resources:可读取的数据上下文(文件、数据库记录等)
  • Prompts:可复用的提示词模板

传输层
支持 stdio(本地进程)和 HTTP/SSE(远程服务)

二、MCP 做的事

定义一套标准化的客户端 - 服务端通信协议,让任何支持 MCP 的 AI 客户端(Claude Desktop、Cursor、豆包等)都能即插即用地连接任何 MCP Server(文件系统、数据库、GitHub、飞书、Postgres……),不用为每个工具单独写集成。

三、类比

MCP 之于 AI Agent,就像 USB 之于外设 ,统一接口,热插拔。

四、MCP 的典型应用形态

飞书生态:lark-doc、lark-sheets、lark-calendar、lark-im、lark-mail、lark-wiki 等, 这些本质上是把飞书 OpenAPI 封装成一组 Tool 暴露给模型,用户授权后即可读写飞书文档、发消息、建日程。这正是 MCP 的典型应用形态:一个 MCP Server 对接飞书,暴露一批 Tool 给任意 AI 客户端。

我的理解是:

  • MCP是一个程序(MCP Server),定义了一套API给第三方调用(MCP Client)。
  • MCP Server 通过@Tool封装调用内部系统接口
  • 有点类似于JDBC

五、具体实例

5.1 需求描述

现在新开发一个AI Agent 系统,需要对接公司内部已经上线运行的系统,要求不能修改公司内部系统的源码,但可以通过HTTP+授权token访问接口,怎么做?

5.2 分析

这是典型的无侵入式对接场景,内部系统是 "黑盒",你只能在外面包一层。核心思路是:建一个独立的 MCP Server 适配层,把内部系统的 HTTP API 封装成标准化的 MCP Tools,权限、限流、审计全部收敛在这一层。

5.3 整体架构
整体架构

关键点:内部系统一行代码不改,所有适配逻辑都在你新建的 MCP Server 里。

5.4 权限处理设计(核心难点)

不能改内部系统,但内部系统有权限控制。你需要在 MCP Server 适配层解决"谁能调什么"和 "调用时用什么身份 "两个问题。

两种权限模型


两种权限模型

重点说明一下模型B

  • Agent 只拿到 "当前用户的身份凭据",权限判定永远由内部系统自己执行。Agent / 适配层是 "信使",不是 "裁判"。权限规则和数据不出内部系统域,Agent 侧只是透传身份、按需做工具可见性裁剪。

  • 情况 A:共用同一身份源(最常见、最简单),Agent 应用和内部系统都对接公司同一个 SSO(单点登录)/LDAP。

  • 情况 B:身份源不同(需要账号关联),Agent 用户ID ──► internal_user_mapping 表 ──► 内部系统用户ID

5.5 MCP 工具层设计(@Tool)

这是最关键的一层 ,把内部系统的能力有选择地暴露为 MCP 工具。不是所有内部 API 都要暴露,要做工具粒度的设计。

MCP 工具层设计
5.5 完整调用链路
完整调用链路

六、样例代码

6.1 本地 Tools(Function Calling)

这是最基础的形态,工具定义在本地,直接通过 @Tool 注解注册给 ChatClient。

1、定义工具类

package com.example.localtools;

import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Service;

@Service
public class WeatherTools {

    /**
     * 【概念解释】
     * @Tool 注解告诉 Spring AI:这个方法是一个可被大模型调用的工具。
     * - description 会被发送给大模型,帮助它理解"什么时候该调用这个工具"
     * - 方法参数会自动生成 JSON Schema,作为 Function Calling 的 inputSchema
     * - 方法返回值会作为 tool execution result 回传给大模型继续推理
     *
     * 这就是 Function Calling 的完整闭环:模型决定调用 → Spring AI 执行 → 结果回传模型
     */
    @Tool(description = "根据城市名称获取当前天气信息")
    public String getWeather(
            @ToolParam(description = "城市名称,如 Beijing、Shanghai") String city) {
        // 模拟调用天气 API
        return switch (city) {
            case "Beijing", "北京" -> "北京:28°C,晴,空气质量优";
            case "Shanghai", "上海" -> "上海:32°C,多云,有阵雨";
            case "Guangzhou", "广州" -> "广州:35°C,雷阵雨";
            default -> "暂无 " + city + " 的天气数据";
        };
    }

    @Tool(description = "根据城市和日期获取未来天气预报")
    public String getForecast(
            @ToolParam(description = "城市名称") String city,
            @ToolParam(description = "日期,格式 YYYY-MM-DD") String date) {
        return city + " 在 " + date + " 的预报:30°C,多云转晴";
    }
}

2、使用工具

package com.example.localtools;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.stereotype.Service;

@Service
public class WeatherAssistant {

    private final ChatClient chatClient;

    public WeatherAssistant(ChatClient.Builder builder, WeatherTools weatherTools) {
        this.chatClient = builder
                // 【关键】把工具实例注册给 ChatClient
                // Spring AI 会扫描 @Tool 注解,生成 Function Calling 所需的工具定义
                .defaultTools(weatherTools)
                .build();
    }

    public String ask(String question) {
        return chatClient.prompt()
                .user(question)
                .call()
                .content();
    }
}

3、运行效果

// 输入:"北京今天天气怎么样?适合出门吗?"
// 内部流程:
// 1. 大模型判断需要调用 getWeather 工具,生成参数 {"city": "Beijing"}
// 2. Spring AI 拦截 tool call,执行 WeatherTools.getWeather("Beijing")
// 3. 返回 "北京:28°C,晴,空气质量优" 给大模型
// 4. 大模型基于工具结果生成最终回答:"北京今天28°C晴天,空气质量优,非常适合出门!"

这就是 Tools/Function Calling:模型在推理过程中决定调用外部函数,框架负责执行并回传结果。

6.2 MCP Server把工具暴露为标准协议服务

现在把同样的天气工具包装成 MCP Server。关键区别:工具不再直接绑定某个 AI 应用,而是通过 MCP 协议对外暴露,任何 MCP 客户端(Claude Desktop、Cursor、另一个 Spring AI 应用)都能连接使用。

1、依赖

<!-- mcp-server/pom.xml -->
<dependencies>
    <!-- MCP Server 启动器(STDIO 模式,适合本地/桌面工具) -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-starter-mcp-server</artifactId>
    </dependency>
    <!-- 如果需要 SSE/HTTP 远程访问,用这个:
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
    </dependency>
    -->
</dependencies>

2、工具类

package com.example.mcpserver;

import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Service;

@Service
public class WeatherService {

    @Tool(description = "根据城市名称获取当前天气信息")
    public String getWeather(
            @ToolParam(description = "城市名称") String city) {
        return switch (city) {
            case "Beijing", "北京" -> "北京:28°C,晴,空气质量优";
            case "Shanghai", "上海" -> "上海:32°C,多云,有阵雨";
            default -> "暂无 " + city + " 的天气数据";
        };
    }

    @Tool(description = "获取城市未来天气预报")
    public String getForecast(
            @ToolParam(description = "城市名称") String city,
            @ToolParam(description = "日期 YYYY-MM-DD") String date) {
        return city + " " + date + ":30°C,多云转晴";
    }
}

3、注册为 MCP 工具

package com.example.mcpserver;

import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.tool.method.MethodToolCallbackProvider;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class McpServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(McpServerApplication.class, args);
    }

    /**
     * 【MCP 核心概念】
     * 把 WeatherService 里的 @Tool 方法包装成 ToolCallbackProvider,
     * MCP Server 自动配置会检测到这个 Bean,把其中的工具转换为 MCP Tool 规范,
     * 通过 STDIO/SSE 协议对外暴露。
     *
     * 注意:这里的工具定义和 Part 1 完全一样,
     * 区别只在于"谁来消费"——本地 Tools 由当前应用的 ChatClient 直接消费,
     * MCP Server 则通过标准协议让任何客户端都能消费。
     */
    @Bean
    public ToolCallbackProvider weatherTools(WeatherService weatherService) {
        return MethodToolCallbackProvider.builder()
                .toolObjects(weatherService)
                .build();
    }
}

4、MCP Server 配置

# application.yml
spring:
  ai:
    mcp:
      server:
        name: weather-mcp-server        # 服务名,客户端连接时会看到
        version: 1.0.0
        type: SYNC                       # SYNC 同步 / ASYNC 响应式
        instructions: "本服务器提供天气查询工具,支持当前天气和未来预报"
        capabilities:
          tool: true                     # 暴露工具能力
          resource: false                # 不暴露资源(文件/数据)
          prompt: false                  # 不暴露提示词模板

5、MCP Server 还能暴露什么(不止 Tools)
MCP 协议定义了三种原语,Tools 只是其中之一

package com.example.mcpserver;

import io.modelcontextprotocol.spec.McpSchema;
import org.springframework.ai.mcp.server.McpServerFeatures;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.List;
import java.util.Map;

@Configuration
public class McpFeaturesConfig {

    /**
     * 【MCP 原语 1: Resources】
     * 暴露可读取的数据上下文(文件、数据库记录、API 数据等)
     * 客户端可以像"读文件"一样读取这些资源,不需要 Function Calling
     */
    @Bean
    public List<McpServerFeatures.SyncResourceSpecification> systemInfoResource() {
        var resource = McpSchema.Resource.builder()
                .uri("weather://system/info")
                .name("system-info")
                .description("天气服务器的系统信息")
                .mimeType("application/json")
                .build();

        var spec = new McpServerFeatures.SyncResourceSpecification(resource,
                (exchange, request) -> {
                    var info = Map.of("version", "1.0.0", "status", "running");
                    return McpSchema.ReadResourceResult.builder()
                            .contents(List.of(
                                    McpSchema.TextResourceContents.builder()
                                            .uri(request.uri())
                                            .text(info.toString())
                                            .mimeType("application/json")
                                            .build()))
                            .build();
                });
        return List.of(spec);
    }

    /**
     * 【MCP 原语 2: Prompts】
     * 暴露可复用的提示词模板,客户端可以直接拉取使用
     */
    @Bean
    public List<McpServerFeatures.SyncPromptSpecification> weatherPrompts() {
        var prompt = McpSchema.Prompt.builder()
                .name("weather-summary")
                .description("生成天气摘要的提示词模板")
                .arguments(List.of(
                        McpSchema.PromptArgument.builder()
                                .name("city")
                                .description("城市名称")
                                .required(true)
                                .build()))
                .build();

        var spec = new McpServerFeatures.SyncPromptSpecification(prompt,
                (exchange, request) -> {
                    String city = (String) request.arguments().get("city");
                    var msg = McpSchema.PromptMessage.builder()
                            .role(McpSchema.Role.USER)
                            .content(McpSchema.TextContent.builder()
                                    .text("请用简洁的语言总结 " + city + " 的天气情况,并给出穿衣建议。")
                                    .build())
                            .build();
                    return McpSchema.GetPromptResult.builder()
                            .messages(List.of(msg))
                            .description("城市天气摘要提示词")
                            .build();
                });
        return List.of(spec);
    }

    // 【MCP 原语 3: Tools】就是上面 WeatherService 里 @Tool 注解的方法
    // 由 ToolCallbackProvider 自动转换,不需要手动写 Specification
}

这就是 MCP 比单纯 Function Calling 更丰富的地方:它不仅标准化了工具调用,还定义了资源读取、提示词模板、日志通知、进度通知等一整套协议。

6.3 MCP Client连接远程 MCP Server 并使用工具

现在写一个客户端应用,连接上面的 MCP Server,让大模型通过 MCP 协议调用远程工具。

依赖

<!-- mcp-client/pom.xml -->
<dependencies>
    <!-- MCP Client 启动器 -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-starter-mcp-client</artifactId>
    </dependency>
    <!-- 大模型 -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-starter-model-openai</artifactId>
    </dependency>
</dependencies>

2、配置 MCP 连接

# application.yml
spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
    mcp:
      client:
        enabled: true
        name: weather-mcp-client
        version: 1.0.0
        type: SYNC
        request-timeout: 30s
        # 【关键】配置要连接的 MCP Server
        # 这里用 STDIO 方式(启动一个子进程运行 Server)
        stdio:
          connections:
            weather-server:
              command: java
              args:
                - "-jar"
                - "../mcp-server/target/mcp-server-1.0.0.jar"
        # 如果 MCP Server 是远程 SSE 服务,用这个:
        # sse:
        #   connections:
        #     weather-server:
        #       url: http://localhost:8080/sse

3、使用 MCP 工具

package com.example.mcpclient;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.mcp.client.sync.SyncMcpToolCallbackProvider;
import org.springframework.stereotype.Service;

@Service
public class WeatherChatService {

    private final ChatClient chatClient;

    /**
     * 【MCP Client 核心概念】
     * SyncMcpToolCallbackProvider 是 Spring AI 自动配置的 Bean,
     * 它会:
     * 1. 连接所有配置的 MCP Server
     * 2. 通过 MCP 协议的 tools/list 发现远程工具
     * 3. 把远程工具包装成本地的 ToolCallback
     * 4. 当大模型发起 Function Calling 时,通过 MCP 协议转发到远程 Server 执行
     *
     * 【与本地 Tools 的对比】
     * - 本地 Tools:ChatClient 直接调用 JVM 内的方法
     * - MCP Tools:ChatClient 调用 ToolCallback → MCP 协议序列化 → 网络/STDIO 传输 → 远程 Server 执行 → 结果返回
     *
     * 但对大模型来说,两者完全一样——都是 Function Calling,
     * 模型不需要知道工具是本地的还是远程的。
     */
    public WeatherChatService(ChatClient.Builder builder,
                               SyncMcpToolCallbackProvider mcpToolCallbackProvider) {
        this.chatClient = builder
                // 把 MCP 发现的所有远程工具注册给 ChatClient
                .defaultToolCallbacks(mcpToolCallbackProvider.getToolCallbacks())
                .build();
    }

    public String chat(String question) {
        return chatClient.prompt()
                .user(question)
                .call()
                .content();
    }
}

4、启动类和测试接口

package com.example.mcpclient;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class McpClientApplication {

    private final WeatherChatService weatherChatService;

    public McpClientApplication(WeatherChatService weatherChatService) {
        this.weatherChatService = weatherChatService;
    }

    public static void main(String[] args) {
        SpringApplication.run(McpClientApplication.class, args);
    }

    @GetMapping("/chat")
    public String chat(@RequestParam String q) {
        return weatherChatService.chat(q);
    }
}

6.4 完整调用链路对比

本地 Tools 调用链

用户提问
  → ChatClient 发送给大模型(携带本地工具定义)
  → 大模型返回 tool_call(函数名 + 参数)
  → Spring AI 直接调用 JVM 内的 WeatherTools.getWeather()
  → 结果作为 tool message 回传大模型
  → 大模型生成最终回答

MCP 工具调用链

用户提问
  → ChatClient 发送给大模型(携带 MCP 发现的工具定义)
  → 大模型返回 tool_call(函数名 + 参数)
  → Spring AI 调用 MCP ToolCallback
  → MCP Client 通过协议发送 tools/call 请求(JSON-RPC over STDIO/SSE)
  → MCP Server 收到请求,执行 WeatherService.getWeather()
  → 结果通过协议返回 MCP Client
  → 结果作为 tool message 回传大模型
  → 大模型生成最终回答

注意看:从 "大模型返回 tool_call" 之后,本地和 MCP 的区别只是执行位置和传输方式不同。对大模型而言,两者都是标准的 Function Calling。

6.5 核心关系总结
┌─────────────────────────────────────────────────────┐
│                   大模型 (LLM)                        │
│         看到的都是 Function Calling 工具定义           │
└──────────────┬──────────────────────┬───────────────┘
               │                      │
    ┌──────────▼─────────┐  ┌─────────▼──────────────┐
    │  本地 ToolCallback  │  │  MCP ToolCallback       │
    │  (直接调 JVM 方法)  │  │  (通过 MCP 协议转发)    │
    └──────────┬─────────┘  └─────────┬──────────────┘
               │                      │
    ┌──────────▼─────────┐  ┌─────────▼──────────────┐
    │  WeatherTools       │  │  MCP Protocol           │
    │  (当前应用内)        │  │  (STDIO / SSE / HTTP)  │
    └────────────────────┘  └─────────┬──────────────┘
                                        │
                              ┌─────────▼──────────────┐
                              │  MCP Server             │
                              │  → WeatherService       │
                              │  → Resources            │
                              │  → Prompts              │
                              └────────────────────────┘
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容