1. 从单模型调用到 Agent 生态:企业级 Java 项目到底卡在哪
很多 Java 团队在 2024 年都做过同一件事:用 Spring Boot 包一层大模型接口,写个/chat端点,前端接上就上线了。第一版 Demo 通常很顺利,但一旦业务方提出“帮我查一下库存再决定要不要补货”“把这份合同的关键条款抽出来存进数据库”这类需求,代码就开始失控——每个工具都要手写 Function Calling 的 JSON Schema,模型换一家就要重写一遍参数映射,工具逻辑散落在各个 Service 里,测试环境还经常因为 Key 额度耗尽直接 500。
这个卡点的本质不是模型不够聪明,而是模型与外部工具之间缺少一个标准协议。Spring AI 解决的是“Java 侧统一调用不同模型”的问题,而 MCP(Model Context Protocol)解决的是“模型如何标准化地发现和调用工具”的问题。两者结合,才构成企业级 Agent 生态的基石。
我试过在一个供应链项目里把这两层拆开:Spring AI 负责模型抽象和对话编排,MCP 负责工具注册与发现。结果是,新增一个“查询物流轨迹”的工具,只需要在 MCP Server 里加一个方法,主应用一行代码都不用改,重启后模型自动就能调用。这种解耦带来的工程收益,比单纯换个更强的模型大得多。
本文面向已经写过 Spring Boot、想把手上的 AI 功能升级成 Agent 的 Java 开发者。你会拿到可复制的application.yml、MCP Server 注册代码、以及一套用 TaoToken 统一 Key 接入多模型的配置方案。核心检索词先明确:Spring AI 集成 MCP 构建企业级 Agent,适合谁?适合那些不想被单一模型厂商绑定、又需要工具编排能力的后端团队。
在动手之前,先把三个概念对齐,不然后面配置容易懵:
Spring AI是 Spring 官方出的 AI 开发框架,把 ChatModel、EmbeddingModel、VectorStore 都抽象成接口,换模型只改配置不改代码。它的高层入口是ChatClient,支持流式输出、结构化映射和 Advisor 拦截链。
MCP是一个基于 JSON-RPC 2.0 的开放协议,定义了 Host、Client、Server 三个角色。Server 对外暴露 Tools(可执行函数)、Resources(只读数据)、Prompts(模板),Client 负责连接和调用。传输层支持 Stdio(本地进程)和 SSE(远程 HTTP)。
Agent 生态在这里指的是:一个主应用(Host)通过 MCP Client 连接多个 MCP Server,每个 Server 封装一类业务能力(库存、通知、日志),模型根据用户意图自动选择工具并编排调用顺序。
理解了这三层,后面的配置就是填空题。下面从 TaoToken 接入层开始,一步步把环境搭起来。
2. TaoToken 统一 Key 接入:多模型切换与 MCP 工具链的前置配置
企业级 Agent 的第一个现实问题是:你不可能只用一家模型。库存查询用便宜的快模型,合同分析用推理强的模型,代码生成又换一个。如果每个模型都单独申请 Key、单独配 Base URL,配置管理会变成灾难。TaoToken 在这里扮演的是统一接入层的角色——一个 Key、一个 Base URL,通过 Model ID 切换不同模型。
先明确地址,后面配置里会反复用到:
- 官网入口:https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=
- API 端点:https://taotoken.net/api(这个不加 UTM,直接用于代码配置)
2.1 获取 Key 与确认 Model ID
登录后进入控制台,在 API Keys 页面创建一个新 Key。建议按环境分开:开发环境一个、生产环境一个,方便单独吊销。创建后立刻复制保存,页面刷新后不再显示完整 Key。
模型对话页面可以直接测试哪些 Model ID 可用。常见的几个:
| 用途 | Model ID 示例 | 特点 |
|---|---|---|
| 工具编排/意图识别 | claude-3-5-sonnet | 函数调用稳定,适合 Agent 主循环 |
| 高并发轻量任务 | gpt-4o-mini | 成本低,响应快 |
| 复杂推理 | claude-3-5-sonnet / gpt-4o | 多步推理强 |
注意:Model ID 必须和 TaoToken 文档里列出的完全一致,大小写和连字符都不能错。我踩过的坑是把claude-3-5-sonnet写成了claude-3.5-sonnet,结果请求直接返回模型不存在。
2.2 Spring AI 的 OpenAI 兼容配置
Spring AI 对 OpenAI 协议有原生支持,而 TaoToken 的 API 是 OpenAI 兼容的,所以直接用spring-ai-openai-spring-boot-starter即可。在pom.xml里加依赖:
<dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-openai-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-mcp-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency>Spring AI 的版本管理建议用 BOM,避免各模块版本不一致:
<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-bom</artifactId> <version>1.0.0-M5</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>2.3 application.yml 完整配置
这是本篇最核心的可复制片段,路径和参数都按实际项目结构写:
spring: ai: openai: base-url: https://taotoken.net/api api-key: ${TAOTOKEN_API_KEY} chat: options: model: claude-3-5-sonnet temperature: 0.3 embedding: options: model: text-embedding-3-small mcp: client: enabled: true name: enterprise-agent-host version: 1.0.0 connections: inventory-service: type: sse url: http://localhost:8081/mcp/sse notification-service: type: sse url: http://localhost:8082/mcp/sse几个关键点解释:
base-url指向 TaoToken 的 API 端点,不要带尾部斜杠。api-key用环境变量注入,不要硬编码在 yml 里,生产环境用配置中心或 K8s Secret。
temperature设 0.3 是因为 Agent 场景需要稳定的工具选择,太高会导致模型在“查库存”和“查物流”之间随机跳。
mcp.client.connections下面每个条目就是一个 MCP Server 连接。type: sse表示走远程 HTTP,适合企业内部微服务架构。如果是本地调试,可以改成type: stdio并指定command。
2.4 多模型切换策略
如果需要在运行时切换模型,不要改 yml 重启。用 Spring AI 的ChatClient构建多个实例:
@Configuration public class ChatClientConfig { @Bean public ChatClient fastChatClient(OpenAiChatModel chatModel) { return ChatClient.builder(chatModel) .defaultOptions(OpenAiChatOptions.builder() .withModel("gpt-4o-mini") .withTemperature(0.1) .build()) .build(); } @Bean public ChatClient reasoningChatClient(OpenAiChatModel chatModel) { return ChatClient.builder(chatModel) .defaultOptions(OpenAiChatOptions.builder() .withModel("claude-3-5-sonnet") .withTemperature(0.3) .build()) .build(); } }这样在业务代码里按场景注入不同的 ChatClient,意图识别用 fast,复杂推理用 reasoning。TaoToken 的统一 Key 让这两个模型共用一套鉴权,不需要维护两份配置。
前置配置到这里就完成了。下一步是写 MCP Server,把业务能力暴露出去。
3. 可复制配置:MCP Server 注册与 Spring AI 工具编排代码
这一节交付两样东西:一个能跑的 MCP Server(暴露库存查询工具),以及主应用里把 MCP 工具接入 ChatClient 的配置。所有代码都可以直接复制到项目里改包名使用。
3.1 MCP Server 端:定义工具与 SSE 端点
先建一个独立的 Spring Boot 模块,叫inventory-mcp-server。它的职责很单一:把库存相关的业务方法注册成 MCP Tool。
@Configuration public class McpServerConfig { @Bean public McpServer mcpServer(McpServerTransport transport) { return McpServer.using(transport) .serverInfo("Inventory-Manager", "1.0.0") .capabilities(ServerCapabilities.builder() .tools(true) .resources(true) .build()) .build(); } @Bean public McpServer.ToolRegistration getStockTool() { return new McpServer.ToolRegistration( new Tool("get_stock", "查询指定商品的实时库存数量,参数 productId 为商品编码", Map.of("productId", "string")), args -> { String productId = (String) args.get("productId"); int stock = inventoryRepository.findStock(productId); return "商品 " + productId + " 当前库存为 " + stock + " 件"; } ); } @Bean public McpServer.ToolRegistration listLowStockTool() { return new McpServer.ToolRegistration( new Tool("list_low_stock", "列出所有低于安全库存阈值的商品,无需参数", Map.of()), args -> { List<String> lowStock = inventoryRepository.findLowStock(); return String.join("\n", lowStock); } ); } }工具描述(Tool 的第二个参数)非常重要。模型是根据这段自然语言来决定调用哪个工具的。描述里要写清楚“什么时候用”和“参数是什么”,不要只写“查询库存”四个字。
SSE 端点通过 Controller 暴露:
@RestController public class McpSseController { private final McpServer mcpServer; public McpSseController(McpServer mcpServer) { this.mcpServer = mcpServer; } @GetMapping(value = "/mcp/sse", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux<ServerSentEvent<String>> sse() { return mcpServer.sseTransport(); } }启动后访问http://localhost:8081/mcp/sse,如果看到持续的事件流输出,说明 Server 端就绪。
3.2 主应用端:MCP Client 自动发现与 ChatClient 集成
主应用的配置在 2.3 节已经写了。现在写 Java 配置类,把 MCP 工具自动注册到 ChatClient:
@Configuration public class AgentConfig { @Bean public ChatClient agentChatClient(ChatClient.Builder builder, McpClient mcpClient) { return builder .defaultSystem("你是一个企业供应链助手,可以调用工具查询库存和发送通知。" + "在回答前先判断是否需要调用工具获取实时数据。") .defaultAdvisors(new McpToolAdvisor(mcpClient)) .build(); } }McpToolAdvisor是 Spring AI 提供的 Advisor,它会在启动时通过 MCP 协议的list_tools接口拉取所有可用工具,并转换成 Spring AI 的 Function 定义。当用户提问时,Advisor 拦截请求,把工具列表注入到模型的上下文中。
Controller 层:
@RestController @RequestMapping("/agent") public class AgentController { private final ChatClient agentChatClient; public AgentController(ChatClient agentChatClient) { this.agentChatClient = agentChatClient; } @GetMapping("/ask") public String ask(@RequestParam String prompt) { return agentChatClient.prompt(prompt) .call() .content(); } }3.3 多 Server 工具聚合
如果连接了多个 MCP Server,McpToolAdvisor会自动聚合所有工具。但要注意工具名冲突:如果两个 Server 都暴露了get_stock,后注册的会覆盖前面的。建议在工具命名时加业务前缀,比如inventory_get_stock、logistics_get_track。
如果需要手动控制工具选择范围,可以按 Server 分别创建 Advisor:
@Bean public ChatClient inventoryOnlyClient(ChatClient.Builder builder, McpClient inventoryClient) { return builder .defaultAdvisors(new McpToolAdvisor(inventoryClient)) .build(); }这样在需要严格限制工具权限的场景(比如面向外部用户的接口),可以只挂载必要的 Server。
配置部分到此完整。接下来验证请求是否真的跑通了。
4. 验证请求与成功结果:从 curl 到 Agent 多步调用联调
配置写完不代表能用。这一节给出从底层到上层的三层验证方法,每层都有明确的成功标志。
4.1 第一层:验证 TaoToken 接入是否通
先用 curl 直接打 TaoToken 的 API,排除 Spring AI 配置问题:
curl -X POST https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "claude-3-5-sonnet", "messages": [{"role": "user", "content": "回复 OK 两个字母"}], "max_tokens": 10 }'成功标志:返回 JSON 里choices[0].message.content包含 “OK”。如果返回 401,检查 Key 是否复制完整、是否有多余空格。如果返回 404,检查base-url是否写成了https://taotoken.net/api/v1(Spring AI 会自动拼/v1/chat/completions,所以 base-url 不要带/v1)。
4.2 第二层:验证 MCP Server 工具发现
启动 inventory-mcp-server 后,用 MCP Inspector 或者直接 curl SSE 端点:
curl -N http://localhost:8081/mcp/sse成功标志:看到event: endpoint和后续的 JSON-RPC 消息流。如果连接被拒绝,检查端口是否被占用、Controller 是否注册成功。
更直接的验证是看主应用启动日志。Spring AI 在启动时会打印发现工具的数量:
McpToolAdvisor: discovered 2 tools from connection inventory-service - get_stock: 查询指定商品的实时库存数量 - list_low_stock: 列出所有低于安全库存阈值的商品如果日志里工具数量是 0,说明 SSE 连接没建立成功,回到 4.1 检查网络。
4.3 第三层:验证 Agent 自动工具调用
这是最关键的一步。启动主应用,发一个需要工具调用的请求:
curl "http://localhost:8080/agent/ask?prompt=帮我查一下商品A102还有多少库存"成功标志:返回内容里包含真实库存数字,比如“商品 A102 当前库存为 42 件”。同时,在 inventory-mcp-server 的日志里能看到get_stock被调用的记录。
如果模型直接回复“我无法查询实时库存”,说明工具没有正确注入。检查McpToolAdvisor是否加到了 ChatClient 的defaultAdvisors里。
4.4 多步调用验证
测试一个需要跨 Server 编排的场景:
curl "http://localhost:8080/agent/ask?prompt=检查低库存商品并通知采购部"预期行为:Agent 先调用list_low_stock获取低库存列表,然后调用 notification-service 的send_email工具。成功标志是 notification-service 日志里出现邮件发送记录,且返回给用户的回复里包含“已通知采购部”之类的确认信息。
如果只调用了第一个工具就停了,通常是模型的 function calling 能力不够。换claude-3-5-sonnet或gpt-4o重试,gpt-4o-mini在多步编排上偶尔会提前终止。
三层验证都通过后,说明 Spring AI + MCP + TaoToken 的链路完全打通。接下来处理实际运行中会遇到的报错。
5. 本篇常见错排查:401、local proxy failed、reading choices、OAuth 对照表
这一节的报错都来自真实项目日志,按出现频率排序。每个都给出原因和修复方法。
5.1 401 Unauthorized
HTTP 401 Unauthorized: {"error":{"message":"Invalid API key"}}原因通常是三种:Key 复制时带了换行符、环境变量没注入成功、或者 Key 被吊销了。
排查步骤:先在终端echo $TAOTOKEN_API_KEY确认变量有值且无空格。然后在 Spring Boot 启动日志里搜索api-key,确认 Spring AI 读到的值和你预期一致。如果用的是 IDEA 运行,检查 Run Configuration 里的 Environment variables 是否配置了。
修复:重新生成 Key,用export TAOTOKEN_API_KEY=sk-xxx设置后重启应用。生产环境用 K8s Secret 挂载,不要写在 yml 里。
5.2 local proxy failed / Connection refused
java.net.ConnectException: Connection refused: localhost:8081这是 MCP Client 连不上 MCP Server。原因:Server 没启动、端口不对、或者 SSE 端点路径写错了。
排查:先curl http://localhost:8081/mcp/sse确认 Server 活着。如果 Server 在 Docker 里,localhost要换成容器名或宿主机 IP。如果 Server 启动慢,Client 启动时会连接失败,可以在 yml 里加spring.ai.mcp.client.connections.xxx.timeout: 10000延长超时。
5.3 reading choices 相关报错
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type `java.util.ArrayList` from Object value这个报错通常出现在模型返回的 function call 参数格式和 Spring AI 期望的不一致时。TaoToken 返回的是标准 OpenAI 格式,但如果 Model ID 写错导致路由到了非兼容端点,就会解析失败。
排查:确认model字段的值在 TaoToken 文档的模型列表里。用 4.1 的 curl 命令直接测试该 Model ID,看返回结构是否标准。
修复:换回文档里明确标注“OpenAI 兼容”的模型。如果必须用某个特殊模型,检查是否需要额外的response_format参数。
5.4 OAuth / token 过期类报错
OAuth2AuthenticationException: invalid_token如果你在 MCP Server 的 SSE 端点上加了 Spring Security OAuth2 保护,Client 端需要配置 token 传递。Spring AI 的 MCP Client 支持在连接配置里加 header:
spring: ai: mcp: client: connections: inventory-service: type: sse url: http://localhost:8081/mcp/sse headers: Authorization: Bearer ${MCP_SERVER_TOKEN}注意:这里的 token 是 MCP Server 的鉴权 token,和 TaoToken 的 API Key 是两回事。不要混用。
5.5 工具调用参数类型不匹配
IllegalArgumentException: argument type mismatchMCP Tool 定义时Map.of("productId", "string")声明的是 JSON Schema 类型,但模型可能传过来的是数字。在 Tool 的执行逻辑里做一次类型转换:
String productId = String.valueOf(args.get("productId"));不要假设模型一定按你声明的类型传参。防御性转换是必要的。
5.6 三件套检查清单
任何 MCP 接入问题,先对照这三项:
| 检查项 | 正确值 | 常见错误 |
|---|---|---|
| Base URL | https://taotoken.net/api | 多了 /v1 或尾部斜杠 |
| API Key | 环境变量注入 | 硬编码、带空格、已吊销 |
| Model ID | 文档列出的完整 ID | 大小写错误、连字符写成点 |
这三项确认无误后,再去看 MCP 层的连接和工具注册。大部分问题在第一步就能定位。
6. 语义一致 CTA:把 Agent 跑起来之后往哪走
配置跑通、工具调用验证通过之后,下一步通常是两件事:一是把更多业务系统封装成 MCP Server,二是把 Agent 接入实际的生产流量。
如果你还在调试接入层,建议先把 API Key 管理和接入文档过一遍,确认生产环境的 Key 轮换和权限隔离方案。API Keys 页面可以按环境创建多个 Key,接入文档里有不同语言的完整示例。
想先验证模型在具体业务场景下的表现,可以直接在模型对话页面测试不同 Model ID 的工具调用稳定性。同一个 prompt 换不同模型跑几轮,观察工具选择是否一致,这比在代码里反复重启调试快得多。
如果这个 Agent 要长期跑在编码或运维场景里,比如自动分析日志、生成修复建议、调用内部工具链,那 Coding Plan 的额度模型比按次计费更适合。长期高频的工具编排调用,用套餐能省掉不少成本核算的麻烦。
回到工程本身,Spring AI + MCP 的组合最大的价值不是“能调用工具”,而是工具的定义和 Agent 的编排彻底解耦。库存团队维护 inventory-mcp-server,通知团队维护 notification-mcp-server,AI 主应用只负责对话和编排。任何一方升级,其他方不需要重新编译。这种架构在多人协作的企业环境里,比把所有工具写在一个项目里可维护得多。
最后一个实操建议:MCP Tool 的描述字段值得反复打磨。模型选错工具,90% 的情况不是模型不行,而是描述写得太模糊。把“查询库存”改成“根据商品编码查询实时库存数量,适用于用户询问某商品是否有货的场景”,工具选择的准确率会有明显提升。这个细节在文档里通常不会强调,但实际项目里影响很大。