1. 为什么要在 Spring Boot 3.x 里同时用上 MCP 和 Ollama
如果你是一个 Java 后端,最近大概率被两件事反复刷屏:一个是本地大模型 Ollama,一个是让模型能真正“动手干活”的 MCP 协议。前者解决“模型跑在自己机器上、数据不出内网”的问题,后者解决“模型只会聊天、不会调用你系统里的接口”的问题。把这两个东西塞进 Spring Boot 3.x,就能得到一个既能本地推理、又能调用工具的企业级 AI 应用骨架。
先说清楚这三个词分别是什么。Ollama 是一个本地大模型运行时,一条ollama run llama3.2:3b就能把模型拉下来跑起来,对外暴露兼容 HTTP 的 API,默认监听11434端口。MCP(Model Context Protocol)是一套让模型和外部工具、数据源对话的协议,你可以把它理解成“给模型用的 USB 接口”——工具方按协议暴露能力,模型方按协议发现并调用。Spring Boot 3.x 则是承载这一切的容器,用 Spring AI 把 Ollama 的对话能力和 MCP 的工具能力粘在一起。
那为什么还要引入 TaoToken 统一 Key 接入?因为本地小模型在工具调用时有个绕不开的坑:参数字段容易“幻觉”。我实测 llama3.2:3b 在解析中文人名、订单号这类实体时,经常把参数值拼错,导致 MCP 服务端收到脏数据。生产环境里更稳的做法是双通道——日常对话走本地 Ollama 省钱省延迟,关键的工具调用决策走 TaoToken 统一 API 通道(https://taotoken.net/api),用更强的模型保证 function calling 的参数准确率。TaoToken 在这里的角色是统一 Key 和统一入口,你不用为每个模型厂商单独维护一套鉴权和 Base URL。
这套组合适合谁?适合已经会用 Spring Boot 写 REST 接口、想快速把 AI 能力接进现有系统的 Java 开发者;适合对数据合规有要求、希望推理尽量本地化的团队;也适合想研究 MCP 工具调用链路、但不想一上来就啃 Python 生态的同学。整条链路不需要 GPU,一台 8G 内存的开发机就能跑通 llama3.2:3b,工具调用部分用 TaoToken 兜底。
下面我会按“先跑通本地模型 → 再接入 MCP 工具 → 最后用统一 Key 验证完整调用链”的顺序,把每一步的配置和命令都给全。你照着敲,遇到报错可以直接跳到第 5 节对照排查。
2. 前置准备:Ollama 部署与 TaoToken 统一 Key 获取
2.1 用 Docker 把 Ollama 跑起来
最省事的方式是 Docker。先拉镜像,再挂载一个本地目录存模型,这样容器删了模型还在:
docker pull ollama/ollama:latest docker run -d --name ollama \ -p 11434:11434 \ -v $PWD/ollama:/root/.ollama \ ollama/ollama:latest容器起来后进容器拉模型。这里选llama3.2:3b,体积约 2GB,支持 tools 调用,适合本地先跑通链路:
docker exec -it ollama ollama pull llama3.2:3b docker exec -it ollama ollama list如果你不想用 Docker,Linux 下也可以脚本安装:
curl -fsSL https://ollama.com/install.sh | sh ollama serve ollama pull llama3.2:3b验证 Ollama 是否正常,直接打它的 tags 接口:
curl http://127.0.0.1:11434/api/tags返回 JSON 里能看到llama3.2:3b就说明模型就绪。这里提醒一句:deepseek-r1:1.5b这类小模型不支持 tools 调用,别拿它测工具链路,会一直返回纯文本。
2.2 拿 TaoToken 统一 Key
本地小模型负责省钱,但工具调用的参数准确性得靠更强的模型兜底。去 TaoToken 控制台创建一个 API Key,这个 Key 同时能访问多个模型,省得你为每家单独配鉴权。
创建入口在控制台的 API Keys 页面:https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite。创建后复制那串sk-开头的 Key,先存到环境变量里,别硬编码进代码:
export TAOTOKEN_API_KEY="sk-你的key"TaoToken 的 API 基地址是https://taotoken.net/api,注意这个地址不带任何查询参数,直接作为 OpenAI 兼容协议的 base_url 使用。模型 ID 可以在模型对话页面确认,比如gpt-4o-mini、claude-3-5-sonnet这类,具体以控制台展示为准。
2.3 Spring Boot 3.x 项目骨架
用 start.spring.io 生成项目,依赖勾选 Spring Web 和 Spring AI 的 Ollama starter。生成后build.gradle里补上 MCP client 依赖:
dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.ai:spring-ai-starter-model-ollama' implementation 'org.springframework.ai:spring-ai-starter-mcp-client' testImplementation 'org.springframework.boot:spring-boot-starter-test' }Spring AI 的版本管理建议用 BOM 统一,避免 starter 之间版本错位。到这里前置就齐了:Ollama 在 11434 跑着,TaoToken Key 在环境变量里,项目骨架能编译。
3. 可复制配置:application.yml 与 MCP 工具注册
3.1 application.yml 完整配置
把配置从 properties 换成 yml,结构更清晰。下面这份可以直接抄,注意base-url和模型名按你实际情况改:
spring: application: name: springboot-mcp-ollama-demo ai: ollama: base-url: http://localhost:11434 chat: model: llama3.2:3b options: temperature: 0.3 mcp: client: name: spring-ai-mcp-client version: 1.0.0 type: sync toolcallback: enabled: true sse: connections: server1: url: http://localhost:9800 openai: base-url: https://taotoken.net/api api-key: ${TAOTOKEN_API_KEY} chat: model: gpt-4o-mini这里有两个模型通道:spring.ai.ollama是本地对话通道,spring.ai.openai指向 TaoToken 的统一入口,用于工具调用兜底。temperature调到 0.3 是为了让工具调用的参数更稳定,别用默认的 0.7。
3.2 MCP 工具注册与 ChatClient 装配
MCP 客户端的核心是把远端 MCP 服务暴露的工具,转成 Spring AI 能识别的ToolCallback,再挂到ChatClient上。下面这个ChatService把两件事都做了:
@Service public class ChatService { private final ChatClient localChatClient; private final ChatClient toolChatClient; public ChatService(OllamaChatModel ollamaChatModel, OpenAiChatModel openAiChatModel, List<McpSyncClient> mcpSyncClientList) { ToolCallbackProvider toolCallbackProvider = new SyncMcpToolCallbackProvider(mcpSyncClientList); for (ToolCallback cb : toolCallbackProvider.getToolCallbacks()) { System.out.println("registered tool: " + cb.getToolDefinition().name()); } this.localChatClient = ChatClient.builder(ollamaChatModel).build(); this.toolChatClient = ChatClient.builder(openAiChatModel) .defaultToolCallbacks(toolCallbackProvider) .build(); } public String chat(String question) { return localChatClient.prompt().user(question).call().content(); } public String chatWithTools(String question) { return toolChatClient.prompt() .system("你是一个工具调用助手,需要查询数据时必须调用工具,不要编造。") .user(question) .call() .content(); } }关键点在于SyncMcpToolCallbackProvider会把所有已连接的 MCP 服务端工具聚合起来,defaultToolCallbacks一挂,模型在对话时就能自动决定要不要调工具。启动时打印registered tool是为了确认工具真的注册进来了,如果这里没输出,说明 MCP 连接没建立。
3.3 一个最小 MCP 服务端
工具调用要有服务端配合。下面是一个基于 SSE 传输的最小 MCP 服务端示例,暴露一个“按姓名查用户”的工具:
@SpringBootApplication public class McpServerApplication { public static void main(String[] args) { SpringApplication.run(McpServerApplication.class, args); } @Bean public ToolCallback userQueryTool() { return FunctionToolCallback.builder("queryUserByName", (String name) -> { Map<String, Object> user = new HashMap<>(); user.put("name", name); user.put("level", "VIP"); user.put("balance", 1280); return user.toString(); }) .description("根据用户姓名查询用户信息,参数为姓名") .inputType(String.class) .build(); } }服务端跑在 9800 端口,客户端 yml 里的server1.url就指向它。工具名queryUserByName要和模型调用时生成的 name 对得上,否则会报“tool not found”。
4. 验证请求:curl 打通工具调用链路
4.1 先验证本地 Ollama 对话
在写 Java 之前,先用 curl 确认 Ollama 本身能对话:
curl http://127.0.0.1:11434/api/chat -d '{ "model": "llama3.2:3b", "messages": [{"role": "user", "content": "用一句话介绍 Spring Boot"}], "stream": false }'返回 JSON 的message.content里有正常回答,说明本地通道没问题。
4.2 验证 TaoToken 统一通道
再用同一个 Key 打 TaoToken 的 OpenAI 兼容接口,确认统一通道可用:
curl https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-4o-mini", "messages": [{"role": "user", "content": "只回复两个字:收到"}] }'如果返回choices[0].message.content是“收到”,说明 Key 和 Base URL 都对。这一步很关键,因为工具调用最终走的是这条通道,通道不通后面全白搭。
4.3 验证完整工具调用链
启动 MCP 服务端(9800)和 Spring Boot 主应用后,写一个 CommandLineRunner 触发带工具的对话:
@Bean public CommandLineRunner runner(ChatService chatService) { return args -> { String r1 = chatService.chat("介绍一下你自己"); System.out.println("local chat: " + r1); String r2 = chatService.chatWithTools("查询姓名为'林俊杰'的用户信息"); System.out.println("tool chat: " + r2); }; }启动日志里应该先看到registered tool: queryUserByName,然后tool chat的输出里包含 VIP、1280 这些只有工具才能返回的数据。如果模型直接编了个答案而没调工具,说明工具没挂上或者模型不支持 function calling。
你也可以用 curl 直接打 Spring Boot 暴露的接口来验证,前提是你加了一个 Controller:
curl "http://localhost:8080/chat/tools?q=查询姓名为林俊杰的用户信息"返回内容里出现工具返回的字段,就证明“模型决策 → 工具调用 → 结果回填 → 模型总结”这条链路完整跑通了。
5. 本篇常见报错排查
5.1 401 Unauthorized
打 TaoToken 接口返回 401,九成是 Key 没读到。检查环境变量是否真的导出到当前 shell:
echo $TAOTOKEN_API_KEY如果为空,说明export只在另一个终端生效。另外注意 yml 里写的是${TAOTOKEN_API_KEY},Spring 启动时如果读不到会直接报占位符解析失败,而不是 401,所以看到 401 基本是 Key 值本身错了或者带了多余空格。
5.2 local proxy failed / connection refused
这个报错通常出现在 Ollama 通道。先确认容器在跑:
docker ps | grep ollama curl http://127.0.0.1:11434/api/tags如果 curl 不通,检查端口映射是不是-p 11434:11434。如果你把 Ollama 放在另一台机器,base-url要改成那台机器的 IP,别写 localhost。
5.3 reading choices 相关解析异常
日志里出现reading choices或 JSON 解析失败,多半是模型返回了非标准结构。本地小模型在工具调用时偶尔会返回半截 JSON,导致 Spring AI 反序列化失败。解决办法是把工具调用通道切到 TaoToken 的稳定模型,本地通道只做纯对话。另外确认spring.ai.openai.chat.model填的是支持 function calling 的模型 ID。
5.4 OAuth / 鉴权头缺失
如果 MCP 服务端要求鉴权,客户端连接时会报 OAuth 或 401。SSE 连接可以在 yml 里补 header:
spring: ai: mcp: client: sse: connections: server1: url: http://localhost:9800 headers: Authorization: Bearer ${MCP_SERVER_TOKEN}没有鉴权需求就删掉这段,别留空 header,否则某些版本会报解析错误。
5.5 工具注册了但模型不调用
启动日志有registered tool,但模型还是自己编答案。两个原因:一是模型不支持 tools,换llama3.2:3b或走 TaoToken;二是 system prompt 没强调“必须调用工具”。把 system 消息写死成“需要查询数据时必须调用工具,禁止编造”,命中率会明显提升。我试过在 llama3.2:3b 上不加这句,十次有六次直接编。
6. 把统一 Key 接入长期编码与 Agent 工作流
跑通上面这条链路后,你会发现真正费时间的不是写代码,而是反复调模型、换模型、对参数。TaoToken 的价值在于一个 Key 覆盖多个模型,工具调用用强模型、日常对话用本地模型,成本和质量都能兼顾。
如果你打算把这套东西做成长期跑的编码助手或 Agent,建议直接上 Coding Plan,它按订阅方式提供稳定的模型调用额度,适合持续性的开发场景:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite。模型对话调试入口在https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite,接入文档在https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite,API Key 管理还是那个https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite。
最后给一个实用技巧:把 Ollama 的base-url和 TaoToken 的base-url都做成配置项,用 Spring Profile 区分 dev 和 prod。dev 环境全走本地,prod 环境工具调用走 TaoToken,这样本地开发不烧额度,上线又能保证参数准确率。MCP 服务端的工具描述一定要写清楚参数类型和含义,模型能不能正确调用,一半取决于你的 description 写得够不够具体。