乐于分享
好东西不私藏

把 MCP 理解成「AI 世界的 USB-C」之后,一切都清晰了

把 MCP 理解成「AI 世界的 USB-C」之后,一切都清晰了

  • 先说说我踩的坑
  • MCP 核心概念
  • 直接用 HTTP 不香吗?
  • 使用Spring AI接入MCP
    • 1. 加依赖
    • 2. 找个 MCP 服务接入
    • 3. 写个客户端测试
  • 自己写了个 MCP 服务
  • 我的感受

把 MCP 理解成「AI 世界的 USB-C」之后,一切都清晰

说实话,很早之前看到模型上下文协议这个词,我是懵的。不就是让 AI 调个工具吗,搞这么复杂干嘛?

MCP这个概念很早就发布了,当时也没有怎么去用过,后来踩了几个坑,才发现这东西确实有点场景

先说说我踩的坑

让 AI 查个天气、调个文件,每次都得写一套对接代码。后端服务要写一套,IDE 插件开发又要写一套。问题是——这些代码本质上做的是同一件事:把请求发出去,把结果拿回来

有人会说:封个 SDK 不就好了吗?

是啊,我以前也这么想。直到我发现了一个蛋疼的事实:JAVA SDK 写好了,Node.js 的同事用不了。Go 团队想调?再写一套吧。

SDK 的问题就在这儿:语言强绑定。

MCP 就不一样了。你用 Python 写一个天气服务,Node.js、Rust、Go 都能直接调。为啥?因为 MCP 规定了一套统一的对话标准——不管你用什么语言写的,只要按这个标准来,大家都能聊。

说到这里,我突然想到了一个类比:

MCP = AI 世界的 USB-C。

以前各家电脑的充电口都不一样,苹果用 Lightning,安卓用 Micro-USB。现在 Type-C 一统江湖,一个充电器走天下。MCP 就是想让 AI 工具生态也有这么一个统一接口。

image-20260419180936121.png

MCP 核心概念

先说几个关键词,混个脸熟:

  • MCP 主机:需要调用外部资源的 AI 应用,比如 Claude Desktop、IDE 插件
  • MCP 客户端:内置在主机里,负责和服务器一对一连接
  • MCP 服务器:暴露具体能力的程序,比如能读文件、能查 GitHub、能操作数据库

这里容易搞混的是主机和客户端的关系。主机 ≠ 客户端,但主机里跑着客户端

打个比方:

主机 = 电脑 客户端 = 电脑里的浏览器

电脑是一台完整的设备,浏览器是装在电脑里的一个软件。MCP 也是这个逻辑。

直接用 HTTP 不香吗?

说实话,也能用。调个天气接口,HTTP 请求一发,完事。

但问题是:你真的只需要调一次吗?

如果你同时在用 Claude Desktop、自己的聊天机器人、终端工具,每个都想查天气——用 HTTP 的话,你得为每个地方单独写代码。但用 MCP,你写一次,到处都能调。

所以我的理解是:

  • 直接用 HTTP:一次性脚本,不需要复用,就是图个快
  • 用 MCP:想复用、想分享、想构建工具生态

一句大白话总结:HTTP 天气接口是原材料,MCP 天气服务是预制菜。原材料可以自己炒(但每次都要切菜调味),预制菜直接加热就能吃(但你得有个微波炉——MCP 客户端)。


使用Spring AI接入MCP

下面是我的实操记录,不保证最优解,但保证是真实踩过的坑。

1. 加依赖

<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-mcp-server-spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-mcp-client-webflux-spring-boot-starter</artifactId></dependency>

2. 找个 MCP 服务接入

这里插一句,我找公共 MCP 服务的几个地方:

  • 魔搭社区:https://www.modelscope.cn/mcp
  • 阿里云百炼:https://bailian.console.aliyun.com
  • mcp.so:https://mcp.so

配置其实不复杂,就是一个 JSON 文件,告诉系统「去哪找这个服务」。

image-20260419181833169.png

引入MCP服务 mcp-servers-config.json

 {"mcpServers": {"filesystem": {"command""npx","args": ["-y","@modelcontextprotocol/server-filesystem@2025.3.28","/Users/yanglingcong/Desktop/IdeaProjects/my-ai-rag-knowledge /test","/Users/yanglingcong/Desktop/IdeaProjects/my-ai-rag-knowledge /test"      ]    },"weather": {"command""npx","args": ["-y","@h1deya/mcp-server-weather"      ],"env": {"OPENWEATHERMAP_API_KEY""xxx"      }    },"amap-maps": {"args": ["-y","@amap/amap-maps-mcp-server"      ],"command""npx","env": {"AMAP_MAPS_API_KEY""xxx"      }    }  }}
  • 别人写好了 MCP 天气服务,发布到 npm(JavaScript 的包仓库)。
  • 这里安装命令是npx,npx 会自动下载并运行这个服务,不用关心它装在哪里,免安装运行

3. 写个客户端测试

@Beanpublic ChatClient.Builder chatClientBuilder(OpenAiChatModel openAiChatModel){returnnew DefaultChatClientBuilder(openAiChatModel, ObservationRegistry.NOOP, (ChatClientObservationConvention) null);}

然后写个测试方法:

import com.alibaba.fastjson.JSON;import jakarta.annotation.Resource;import lombok.extern.slf4j.Slf4j;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.ai.chat.client.ChatClient;import org.springframework.ai.openai.OpenAiChatOptions;import org.springframework.ai.reader.tika.TikaDocumentReader;import org.springframework.ai.tool.ToolCallbackProvider;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Value;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.test.context.junit4.SpringRunner;@Slf4j@RunWith(SpringRunner.class)@SpringBootTestpublicclassMCPTest{@Resourceprivate ChatClient.Builder chatClientBuilder;@Autowiredprivate ToolCallbackProvider tools;@Testpublicvoidtest_tool(){        String userInput = "有哪些工具可以使用";var chatClient = chatClientBuilder                .defaultTools(tools)                .defaultOptions(OpenAiChatOptions.builder()                        .model("qwen-plus")                        .build())                .build();        System.out.println("\n>>> QUESTION: " + userInput);        System.out.println("\n>>> ASSISTANT: " + chatClient.prompt(userInput).call().content());    }}

跑起来之后,模型能自动发现并调用 MCP 服务里的工具。这个过程挺神奇的——我只需要说「帮我查下佛山的天气」,剩下的模型自己搞定。

image-20260419183521007.png

工具都可以操作使用,申请一个高德地图API,可以调用MCP服务

@Testpublicvoidtest_workflow(){        String userInput = "帮我查下佛山的天气";var chatClient = chatClientBuilder                .defaultTools(tools)                .defaultOptions(OpenAiChatOptions.builder()                        .model("qwen-plus")                        .build())                .build();        System.out.println("\n>>> QUESTION: " + userInput);        System.out.println("\n>>> ASSISTANT: " + chatClient.prompt(userInput).call().content());    }
image-20260419185509785.png

整体流程大概是:

用户说:「帮我查下佛山的天气」    ↓模型分析 → 哦,需要调 weather 工具    ↓Spring AI 自动触发高德地图 MCP server    ↓高德 API 返回数据    ↓模型把数据整合成自然语言    ↓你看到:「佛山今天晴,温度 25°C...」

自己写了个 MCP 服务

光用别人的不过瘾,我试着写了一个对接 Gitee 的 MCP 服务。

核心就三步:

第一步:引入POM配置

<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-mcp-server-spring-boot-starter</artifactId></dependency>

第二步:写个 Service,用 @Tool 注解标记方法

java服务类

package knowledge.server;import lombok.extern.slf4j.Slf4j;import org.springframework.ai.tool.annotation.Tool;import org.springframework.ai.tool.annotation.ToolParam;import org.springframework.beans.factory.annotation.Value;import org.springframework.stereotype.Component;import org.springframework.web.client.RestTemplate;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;@Component@Slf4jpublicclassGitService{privatefinal RestTemplate restTemplate;@Value("${gitee.access-token:}")private String accessToken;privatestaticfinal String GITEE_API_BASE = "https://gitee.com/api/v5";publicGitService(RestTemplate restTemplate){this.restTemplate = restTemplate;    }/**     * 获取仓库提交列表     */@Tool(name = "get_commits", description = "获取Gitee仓库的提交列表,返回最近的提交记录")public Map<String, Object> getCommits(            @ToolParam(description = "仓库所有者(用户名或组织)") String owner,            @ToolParam(description = "仓库名称") String repo,            @ToolParam(description = "分支名称,默认为master") String branch) {        log.info("获取提交列表: owner={}, repo={}, branch={}", owner, repo, branch);try {            String url = GITEE_API_BASE + "/repos/{owner}/{repo}/commits?sha={branch}&per_page=20&access_token={token}";@SuppressWarnings("unchecked")            List<Map<String, Object>> commits = restTemplate.getForObject(url, List.classownerrepobranchaccessToken);            List<Map<String, Object>> result = new ArrayList<>();if (commits != null) {for (Map<String, Object> commit : commits) {                    Map<String, Object> item = new HashMap<>();                    item.put("sha", commit.get("sha"));                    item.put("message", commit.get("message"));                    item.put("timestamp", commit.get("timestamp"));@SuppressWarnings("unchecked")                    Map<String, Object> author = (Map<String, Object>) commit.get("author");if (author != null) {                        Map<String, Object> authorMap = new HashMap<>();                        authorMap.put("name", author.get("name"));                        authorMap.put("email", author.get("email"));                        authorMap.put("date", author.get("date"));                        item.put("author", authorMap);                    }@SuppressWarnings("unchecked")                    Map<String, Object> committer = (Map<String, Object>) commit.get("committer");if (committer != null) {                        Map<String, Object> committerMap = new HashMap<>();                        committerMap.put("name", committer.get("name"));                        committerMap.put("email", committer.get("email"));                        committerMap.put("date", committer.get("date"));                        item.put("committer", committerMap);                    }                    item.put("url""https://gitee.com/" + owner + "/" + repo + "/commits/" + commit.get("sha"));                    result.add(item);                }            }            Map<String, Object> response = new HashMap<>();            response.put("success"true);            response.put("commits", result);            response.put("count", result.size());            response.put("owner", owner);            response.put("repo", repo);            response.put("branch", branch);return response;        } catch (Exception e) {            log.error("获取提交列表失败", e);            Map<String, Object> errorResponse = new HashMap<>();            errorResponse.put("success"false);            errorResponse.put("error", e.getMessage());return errorResponse;        }    }}

第三步:注册成工具

@Beanpublic ToolCallbackProvider gitTools(GitService gitService){return MethodToolCallbackProvider.builder()            .toolObjects(gitService)            .build();}

@Tool 注解的意思是「这个方法可以被大模型调用」,而 ToolCallbackProvider 负责把它注册进 Spring AI 的工具系统。

打包成 jar 之后,配到 mcp-servers-config.json 里就能用了:

"mcp-server-git": {"command""java路径","args": ["-jar""你的jar包路径"],"env": {"GITEE_ACCESS_TOKEN""你的token"  }}

通过 Maven install 生成 jar 包:

image-20260420215846019.png

Gitee 的 Access Token 在平台设置里生成:

image-20260420220206522.png

测试了一下,让 AI「帮我获取 xxx 仓库的提交记录」,模型自己拆解意图、调用工具、返回结果:

image-20260420220304053.png

整个过程行云流水。

我的感受

以前各个 AI 工具互相割裂,一个工具一套 API,学习成本高、维护成本更高。有了 MCP,至少在工具调用这个层面,大家有了一个共同语言。

但MCP 不是银弹,如果场景是:

  • 后端服务之间的直接调用,没有 AI 模型什么事 → 用 SDK 吧,MCP 反而麻烦
  • 就是想快速调一个天气接口,一次性的 → 直接 HTTP 请求,三行代码搞定

MCP 的价值在于:当想让多个 AI 客户端复用同一套工具时,它才能发挥优势。 没有这个需求,强行上 MCP 就是给自己找事。