)
1. SpringBoot2 老项目接入 MCP 的真实困境如果你手上跑着一个 2019 年前后上线的 SpringBoot2 项目JDK 还锁在 8 或 11现在老板突然说「我们要支持 MCP让 AI 能调用我们的业务接口」你大概率会先去看官方 java-sdk然后发现最低要求 JDK17Spring-AI 的 MCP 模块同样卡在 17。升级 JDK 意味着要动整个依赖树、改掉一堆反射和字节码相关的库风险高、周期长救急场景根本等不起。这就是 SolonMCPsolon-ai-mcp存在的意义。它是 Solon 生态里的一个扩展模块能内嵌到 SpringMVC、SpringBoot2、SpringBoot3、JFinal、Vert.x 等框架里支持 Java8、11、17、21。你不需要把项目迁到 Solon只需要引入一个 jar写几个组件类就能在原有 SpringBoot2 的 Web 容器里挂出一个符合 MCP 协议的 SSE 端点。对老项目来说这是成本最低的落地路径。但真正让人头疼的往往不是协议本身而是模型侧的 Key 管理。一个 MCP 服务背后可能要调多个模型本地 Ollama 跑一个、线上再挂一个、测试环境又换一个。每个模型一套 Base URL、一套 API Key散落在 application.yml、环境变量、启动脚本里改一次配置要翻五个地方。我试过在一个项目里同时维护三套 Key结果上线当天把测试 Key 打到了生产排查了半小时才发现是配置文件覆盖顺序的问题。所以这篇内容解决两件事第一用 SolonMCP 在 SpringBoot2 里把 MCP 服务端跑起来第二用 TaoToken 做统一 Key 和 API 通道把多模型配置收敛成一份配合 application.yml 和 config.toml 双份骨架最后用 curl 和日志两步验证链路是否真的通了。适合正在做 MCP 接入、被 JDK 版本和多 Key 配置卡住的 Java 后端。2. TaoToken 统一 Key 的前置准备与通道说明在动手写代码之前先把模型侧的通道理清楚。MCP 服务端本身只负责暴露工具Tool、资源Resource、提示Prompt真正去调 LLM 的是客户端或者 MCP 客户端作为工具集挂到 ChatModel 上的那一层。也就是说你的 SpringBoot2 项目里会存在两个方向的调用一个是外部 AI 客户端通过 SSE 连到你的 MCP 服务端另一个是你的服务端或测试客户端去调模型 API。后者就是 Key 管理的主战场。TaoToken 在这里扮演的是统一入口的角色。你不需要为每个模型厂商单独维护一套鉴权逻辑而是把 Base URL 指向同一个 API 地址用同一个 Key 去请求不同模型。官网入口是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 地址是 https://taotoken.net/api 注意 API 路径后面不加 UTM 参数配置里写干净地址就行。具体操作上你需要先拿到 Key。进入控制台创建 API Key路径是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 创建完在 API Keys 页面复制地址是 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。这个 Key 就是你后面所有配置里唯一需要填的凭证。模型 ID 怎么确定如果你只是做连通性验证随便选一个对话模型即可比如常见的通用对话模型 ID。真正接入生产时建议先在模型对话页面确认模型可用地址是 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite 输入一句话看是否有正常返回。确认没问题再写进配置文件避免把时间浪费在排查「到底是 Key 错了还是模型名写错了」上。这里有个容易踩的坑很多人把 Base URL 写成 https://taotoken.net/api/v1 或者带斜杠结尾结果客户端拼接路径时出现双斜杠或者路径错位报 404。正确做法是 Base URL 只写到 /api具体路径由客户端 SDK 自己拼。另外 Key 不要硬编码在代码里提交到 Git用环境变量或者外部配置文件注入后面 config.toml 骨架里会体现这一点。如果你后续要做长期编码或者 Agent 类场景可以考虑 Coding Plan入口是 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 它更适合持续性的模型调用需求。但本篇聚焦的是救急接入和连通性验证先把链路跑通再说。3. application.yml 与 config.toml 双份可复制配置骨架这一节是全文的核心操作部分。SpringBoot2 项目里SolonMCP 的启动依赖一个 mcpserver.yml 配置文件通过--cfgmcpserver.yml指定而 SpringBoot 本身的配置在 application.yml。同时如果你用 config.toml 来管理模型侧的 Key 和 Base URL就能做到「业务配置」和「模型通道配置」分离。下面给出三份骨架路径和原文保持一致。先看 Maven 依赖。在你的 pom.xml 里加入 solon-ai-mcp注意版本号按实际最新稳定版填dependency groupIdorg.noear/groupId artifactIdsolon-ai-mcp/artifactId version3.x.x/version /dependency然后是 SpringBoot 的 application.yml这里主要放 Web 容器和 MCP 端点相关的基础配置server: port: 8080 spring: application: name: springboot2-mcp-demo # MCP 服务端相关Solon 侧读取 solon: app: name: mcp-server接着是 mcpserver.yml这个文件放在 resources 根目录Solon 启动时会加载它。里面可以配置 MCP 服务端的名称、SSE 端点前缀等solon.app: name: mcp-server group: demo solon.logging: appender: console: level: INFO最后是 config.toml用来收敛模型侧的 Base URL、Key 和 Model ID。这份文件建议放在项目外部或者通过环境变量覆盖不要提交到仓库[llm] base_url https://taotoken.net/api api_key sk-你的TaoTokenKey model_id 你的模型ID [llm.options] timeout 60 max_tokens 2048三件套对照关系要记牢Base URL 填 https://taotoken.net/api Key 填控制台复制的那个Model ID 填你在模型对话页面验证过的那个。这三个值缺一不可任何一个写错都会在验证阶段报错。如果你用的是 Cline MCP 或者 Claude Code 这类客户端去连你的 MCP 服务端配置里同样要写全这三件套。比如 Cline 的 MCP 配置里SSE 地址填http://localhost:8080/mcp/demo1/sse而它背后调模型时用的 Base URL 和 Key 则来自你给它的模型配置。这里容易混淆的是MCP 服务端的地址和模型 API 的地址是两个不同的东西前者是你自己 SpringBoot 应用暴露的后者是 TaoToken 提供的。另外如果你在 SpringBoot2 里用Configuration托管 Solon 生命周期注意PostConstruct里启动 Solon 的时机。原文示例里是在McpServerConfig的start()方法里调用Solon.start(...)然后在PreDestroy里Solon.stopBlock(...)。这个顺序不能乱否则会出现 Solon 容器还没起来、MCP 端点就已经被访问的情况表现为 404 或者连接被拒。4. 验证请求与成功结果curl 加日志两步确认配置写完代码编译通过接下来最关键的一步是验证链路真的通了。不要凭感觉说「应该没问题」要用可观测的手段确认。这里给两步先 curl 打 SSE 端点再看日志确认工具注册成功。第一步启动你的 SpringBoot2 应用。如果你是在 IDE 里直接跑HelloApp的 main 方法确认控制台没有报 Solon 启动失败。启动成功后用 curl 请求 MCP 的 SSE 端点curl -N -H Accept: text/event-stream http://localhost:8080/mcp/demo1/sse-N参数关闭缓冲让你能实时看到 SSE 推送。如果链路正常你会看到类似这样的输出event: endpoint data: /mcp/demo1/message?sessionIdxxxxx这说明 MCP 服务端的 SSE 通道已经建立客户端可以通过返回的 message 地址发送 JSON-RPC 请求。如果 curl 卡住没有任何输出或者直接返回 404说明 Filter 注册或者端点路径有问题回到McpServerConfig里检查FilterRegistrationBean的addUrlPatterns(/mcp/*)是否和你的sseEndpoint匹配。第二步看应用日志。SolonMCP 在postStart()之后会打印工具、资源、提示的注册信息。你需要在日志里找到类似这样的行McpServerEndpointProvider started: demo1 Tool registered: getWeather Resource registered: config://app-version Prompt registered: askQuestion如果日志里只有started但没有具体的 Tool 注册大概率是McpServerEndpoint注解没被识别或者AnnotationUtils.findAnnotation那一步返回了 null。原文里特别提醒了「如果有代理的话需要用 AnnotationUtils 获取注解」这是因为 Spring 的 AOP 代理会让getClass()拿到代理类而不是原始类直接读注解会读不到。用AnnotationUtils.findAnnotation能穿透代理这是 SpringBoot2 里非常典型的一个坑。两步都通过之后你可以进一步用 MCP 客户端测试工具调用。写一个简单的McpClientTest用McpClientProvider.builder().apiUrl(http://localhost:8080/mcp/sse).build()连上去然后调用callToolAsText(getWeather, map)期望返回「晴14度」。如果这一步也通了说明从 SpringBoot2 到 SolonMCP 再到工具执行的完整链路没有问题。再往上一层把 MCP 客户端作为 ChatModel 的工具集使用。原文示例里用 Ollama 做本地 LLMChatModel.of(apiUrl).provider(ollama).model(qwen2.5:1.5b).defaultToolsAdd(toolProvider).build()然后chatModel.prompt(杭州今天的天气怎么样).call()。如果你把这里的 apiUrl 换成 TaoToken 的地址provider 和 model 换成对应的值就能用统一 Key 走通「LLM 决策调用哪个工具」的完整流程。这一步的日志里会看到模型返回的 tool_calls 字段以及后续的工具执行结果回填。5. 本篇常见错误排查对照接入过程中最容易撞上的几个报错这里按真实日志对照给出排查方向。第一个401 Unauthorized。这个几乎都是 Key 的问题。检查 config.toml 里的api_key是否和控制台复制的一致注意有没有多余空格或者换行。如果你用环境变量注入确认变量名拼写正确且 SpringBoot 启动时确实读到了。还有一种情况是 Key 被禁用或者额度耗尽去控制台确认状态。第二个local proxy failed 或者连接超时。这个报错通常出现在客户端侧说明请求根本没到达 TaoToken 的 API 地址。检查 Base URL 是否写成了https://taotoken.net/api不要带多余路径。如果你在公司内网确认网络策略允许访问外部 API。注意这里不要引入任何网络代理相关的配置直接用标准 HTTP 客户端即可。第三个reading choices 相关报错。这个一般出现在解析模型返回时说明返回的 JSON 结构和你预期的字段不匹配。常见原因是 Model ID 写错了请求被路由到了一个不兼容的接口。回到模型对话页面确认模型 ID然后检查 config.toml 里的model_id是否一致。第四个OAuth 相关报错。如果你用的是 Claude Code 或者某些需要 OAuth 的客户端报错提示 token 无效或者授权失败检查你的客户端配置里 Base URL 和 Key 是否填在了正确的位置。有些客户端把模型配置和 MCP 配置分开别填串了。第五个SSE 端点返回 404。回到McpServerConfig确认FilterRegistrationBean的addUrlPatterns和McpServerEndpoint的sseEndpoint路径能对上。比如sseEndpoint /mcp/demo1/sse那 Filter 至少要覆盖/mcp/*。另外确认SolonServletFilter的包路径导入正确别导成了别的同名类。第六个工具注册了但调用返回空。检查ToolMapping方法的参数有没有加Param(description ...)以及编译时是否开启了-parameters参数。如果没有-parametersJava 反射拿不到参数名MCP 协议里参数映射就会失败。原文里专门提醒了这一点建议在 pom.xml 的 compiler 插件里加上parameterstrue/parameters。第七个SpringBoot2 启动时报 Solon 相关类冲突。Solon 和 Spring 都有Component之类的同名注解原文里特别强调「注意这个注解别用错了solon 里也有同名的」。在 MCP 端点类上用 Spring 的Component不要用 Solon 的。导入包的时候看清楚org.springframework.stereotype.Component。6. 后续接入与长期使用建议链路跑通之后接下来要考虑的是怎么把它用稳。MCP 服务端的工具方法建议按业务域拆分多个McpServerEndpoint类每个类负责一组相关工具这样日志里排查问题时能快速定位是哪个端点出的错。工具方法的返回值尽量用简单类型或者标准 JSON避免返回复杂的嵌套对象导致客户端解析失败。Key 管理上config.toml 只是其中一种方式。如果你有多个环境建议用 Spring 的 profile 机制配合外部配置文件把 dev、test、prod 的 Key 分开。但无论怎么分Base URL 和 Model ID 的对照关系要写清楚避免出现「Key 是生产的、模型是测试的」这种错配。如果你后续要做更复杂的 Agent 场景比如让模型自主规划多步工具调用可以关注 Coding Plan 相关的接入方式入口是 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里面有各语言 SDK 的示例Java 侧可以参考 HTTP 客户端的调用方式。最后提醒一点MCP 服务端暴露的工具方法本质上就是你的业务接口。不要为了图方便把直接操作生产数据库的方法挂上去也不要在工具方法里做没有鉴权的敏感操作。MCP 协议本身不负责权限控制这层要你自己在 SpringBoot 的拦截器或者工具方法内部做。救急可以快但边界要清楚。