
Spring AI MCP Server 的 Tool 验证用 Codex 走 TaoToken 可以少点很多次 Trae 的 MCP 面板。Key 从 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 创建Base URL 填 https://taotoken.net/api模型 ID 以模型广场当时列表为准MCP 通道仍然指向你本地那个暴露 queryUserAddress、queryUserName 的 Spring AI 进程。之前每次改完 UserServiceImpl都要把 lyj-mcp-server 从 Trae 里删掉重新填 command、args、env再挂到智能体上才能确认两个 tools 还在不在。现在把验证入口换成 CodexTaoToken 只负责让 Codex 消耗的 Token 有地方出真正的查询逻辑还是 UserServiceImpl 在本地完成。下面从 MCP Server 端配置开始把这条验证链路完整走一遍。1. 改完 UserServiceImpl 后Trae 那套 MCP 面板为什么让人烦1.1 Trae 图形化注册的固定动作太多原文的验证方式很直接Spring AI 项目启动后把一段 JSON 手动注册到 Trae 的 MCP Server 里成功后会显示 2 个可用 tools再把 MCP Server 挂到智能体最后在对话框里查“张三住址”和“学号 N001 的姓名”。这套流程第一次跑很顺问题出在迭代阶段。只要 queryUserAddress 或 queryUserName 的实现改一行哪怕只是 Map 里换个值你也要重新打包 jar回到 Trae 的 MCP 面板确认旧配置有没有失效必要时删掉再注册一遍。Trae 的图形化界面不会告诉你“这次 tools 列表为什么是空的”它只会显示连接失败或者工具数量不对。排查时你分不清是 jar 没重新打包、java 路径不对、Tool 注解没生效还是智能体没刷新。1.2 换成 Codex 后Token 和工具调用分成两条线把验证挪到 Codex 之后事情分成两条线模型通道走 TaoToken工具通道走本地 Spring AI MCP Server。Codex 负责理解“我要调用 queryUserAddress”然后通过 MCP 配置启动本地 jar由 UserServiceImpl 里的 Map 返回“北京市海淀区中关村大街1号”。TaoToken 在这条链路里只做一件事让 Codex 的模型调用能跑起来并把这轮对话的 Token 用量记到你的 Key 上。这样做的好处是验证 Tool 是否注册成功不再依赖 Trae 的智能体挂载。Codex 每次启动都会读取~/.codex/config.tomlMCP Server 的启动参数、jar 路径、工具列表都写在同一份文件里。改完 Java 代码重新mvn package重启 Codex直接在对话里让模型调 queryUserAddress 就行。工具没注册成功时Codex 侧会提示找不到工具或者你手动跑一遍 jar 就能看到日志里 tools 的数量。2. 把 queryUserAddress 和 queryUserName 注册成 Spring AI 的 Tool2.1 pom.xml 里选 spring-ai-starter-mcp-server-webmvcMCP Server 的依赖有三种传输方式原文选了基于 Spring MVC 的 SSE 实现。这里继续沿用spring-ai-starter-mcp-server-webmvc因为后面 Codex 既可以用 stdio 启动也可以保留 SSE 端点给其他客户端。BOM 仍然放在dependencyManagement里实际依赖放在dependencies内。dependencyManagement dependencies dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-bom/artifactId version1.0.0-M7/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-mcp-server-webmvc/artifactId /dependency版本号以你项目里实际能拉到的 Spring AI 版本为准如果升级了 M7 之后的版本Tool和ToolParam的包路径不变但 BOM 版本要同步改。不要只改 starter 不改 BOM否则容易在启动时看到NoClassDefFoundError。2.2 UserService 只声明两个查询方法接口层保持干净只声明两个方法不写实现也不加 Spring 注解。MCP 工具的描述和参数说明放在实现类上这样接口可以被其他 Service 复用不会因为 MCP 而绑死。public interface UserService { String queryUserAddress(String userName); String queryUserName(String userNumber); }方法名就是后面 Codex 看到的工具名所以别写成selectUserAddress或getUserAddress后又希望模型能猜对。工具名越直白Codex 调用时越不容易跑偏。2.3 UserServiceImpl 的 Tool 和 ToolParam 注解别写错实现类里用两个静态 Map 模拟数据张三的地址必须是“北京市海淀区中关村大街1号”这样验证时才有明确预期。Tool的 description 会暴露给模型ToolParam的 description 会告诉模型参数含义。注解加在实现方法上不要只加在接口方法上。Slf4j Service public class UserServiceImpl implements UserService { private static final MapString, String userAddressMap new HashMap(); private static final MapString, String userNameMap new HashMap(); static { userAddressMap.put(张三, 北京市海淀区中关村大街1号); userAddressMap.put(李四, 上海市浦东新区陆家嘴1号); userAddressMap.put(王五, 广州市天河区珠江新城1号); userAddressMap.put(赵六, 深圳市南山区科技园1号); userNameMap.put(N001, 张三); userNameMap.put(N002, 李四); userNameMap.put(N003, 王五); userNameMap.put(N004, 赵六); } Tool(description 按姓名查询住址) Override public String queryUserAddress(ToolParam(description 姓名) String userName) { log.info(按姓名查询住址: {}, userName); return userAddressMap.get(userName); } Tool(description 按学号查询姓名) Override public String queryUserName(ToolParam(description 学号) String userNumber) { log.info(按学号查询姓名: {}, userNumber); return userNameMap.get(userNumber); } }注意ToolParam里的“姓名”“学号”不是给 Java 编译器看的是给模型看的。如果写成userName、userNumber模型也能调用但在中文对话里传参时容易犹豫。保持中文描述后面 Codex 提示词里直接说“姓名传张三”就能对上。2.4 DemoApplication 里注册 UserService 的 ToolCallbackProviderSpring AI 不会自动扫描所有Tool方法需要在启动类里显式注册一个ToolCallbackProvider。原文用MethodToolCallbackProvider.builder().toolObjects(userService).build()这里保持同样思路。Bean public ToolCallbackProvider userTools(UserService userService) { return MethodToolCallbackProvider.builder() .toolObjects(userService) .build(); }如果你项目里还有别的 Service 也带Tool可以继续往toolObjects里加但不要重复注册同一个对象。重复注册时日志里可能还是 2 个 tools但工具名会加上前缀Codex 调用时容易找不到你预期的queryUserAddress。2.5 application.properties 里 MCP 端点与端口配置文件保留 SSE 端点和端口方便你之后用 Trae 或浏览器调试。Codex 走 stdio 时不依赖这个端口但留着不影响。spring.application.namemcp-demo server.port7766 spring.ai.mcp.server.namewebmvc-mcp-server spring.ai.mcp.server.sse-message-endpoint/mcp/messages spring.ai.mcp.server.version1.0.0 spring.ai.mcp.server.typeSYNC logging.pattern.console%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n logging.level.rootINFO日志级别先保持 INFO启动时能看到 tools 注册数量。等验证稳定后再把无关包的日志调高避免 Codex 启动 MCP Server 时把 stdio 通道弄脏。3. 启动 mcp-demo从日志确认两个 tools 已经挂上3.1 打包 demo-0.0.1-SNAPSHOT.jar先在项目根目录执行打包命令生成可执行 jar。路径后面要写进 Codex 的 config.toml所以打包完成后先pwd或复制绝对路径。mvn clean package -DskipTests ls target/demo-0.0.1-SNAPSHOT.jar如果打包时报Tool找不到符号先检查 BOM 版本和 starter 是否匹配。如果打包成功但启动时 beans 创建失败优先看ToolCallbackProvider有没有返回 null。3.2 启动日志里找已注册 2 个 tools用普通模式启动一次观察日志里 MCP Server 注册的工具数量。不同版本日志措辞不一样有的会打印Registered tools: 2有的会列出 tool 名称。只要能看到queryUserAddress和queryUserName就说明Tool和ToolCallbackProvider都生效了。java -jar target/demo-0.0.1-SNAPSHOT.jar启动后如果端口 7766 被占用先停掉旧进程。不要带着旧 jar 继续测否则 Codex 调用的可能是上一次编译的类返回的地址对不上你会误以为 MCP 配置有问题。3.3 给 Codex 留一个 stdio 启动入口Codex 通过 MCP 调本地工具时更常用的是 stdio 模式Codex 自己启动一个 Java 进程通过标准输入输出收发 MCP 消息。启动参数里把 web 应用类型关掉并把控制台日志清空避免日志混进 MCP 协议。java -Dspring.ai.mcp.server.stdiotrue \ -Dspring.main.web-application-typenone \ -Dlogging.pattern.console \ -jar /Users/yourname/workspace/mcp-server-test/target/demo-0.0.1-SNAPSHOT.jar这条命令先在终端手动跑一遍确认进程能起来、不会立刻退出。手动跑能成功再写进 Codex 配置。手动跑就失败的话Codex 里看到的“MCP server 启动失败”只是结果真正原因还在 Java 日志里。4. Codex 的 config.toml模型走 TaoTokenMCP 走本地 jar4.1 先创建 Key 并确认模型 ID打开 TaoToken 注册并创建 API KeyKey 用YOUR_API_KEY占位别直接写进文章或提交到 Git。同一个页面进模型广场确认你要填进 Codex 的模型 ID。模型 ID 以 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 模型广场当时列表为准不要照着旧截图填一个已经下线的名字。Base URL 填https://taotoken.net/api末尾不要加/v1也不要加跟踪参数。跟踪参数只用于官网页面填进工具里会导致请求路径不对。Key 和 Base URL 是两条线Key 决定用量记在哪个账号Base URL 决定请求发到哪个兼容通道。4.2 ~/.codex/config.toml 的 model_provider 与 base_urlCodex 的模型配置写在~/.codex/config.toml。model填你在模型广场确认的 IDmodel_provider指向下面自定义的 provider。base_url必须是https://taotoken.net/api不要写成官网落地页也不要带 UTM。model YOUR_MODEL_ID model_provider taotoken [model_providers.taotoken] name TaoToken base_url https://taotoken.net/api env_key TAOTOKEN_API_KEY wire_api chatenv_key是 Codex 读取环境变量的名字。你也可以沿用系统里已有的 OpenAI 变量名但更推荐单独用一个TAOTOKEN_API_KEY避免和别的工具串 Key。保存后重新打开终端让环境变量生效。4.3 同一份 config.toml 注册 spring-ai-demo MCP Server模型 provider 写完后在同一份文件里加 MCP Server。command用javaargs里放刚才手动跑通的参数和 jar 绝对路径。这一段对应原文在 Trae 里手动填的 JSON只是换成了 Codex 的 TOML 格式。[mcp_servers.spring-ai-demo] command java args [ -Dspring.ai.mcp.server.stdiotrue, -Dspring.main.web-application-typenone, -Dlogging.pattern.console, -jar, /Users/yourname/workspace/mcp-server-test/target/demo-0.0.1-SNAPSHOT.jar ]路径里的用户名和目录要换成你机器上的真实路径。如果java不在 Codex 启动时的 PATH 里command写java会找不到命令可以改成/usr/bin/java或你本机which java的结果。args里的参数顺序不要调换-jar后面必须紧跟 jar 路径。4.4 环境变量 TAOTOKEN_API_KEY 用 YOUR_API_KEY在终端里导出 Key然后从同一个终端启动 Codex。Key 从 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 创建填进环境变量时替换占位符。export TAOTOKEN_API_KEYYOUR_API_KEY codex如果你用 shell 配置文件可以把export写进~/.zshrc或~/.bash_profile但不要写进项目仓库。验证阶段也可以临时导出关掉终端就失效。Codex 启动后如果提示 401先echo $TAOTOKEN_API_KEY确认变量有值再确认 Key 没有多余空格。5. 用 Codex 调 queryUserAddress 查“张三”的住址5.1 启动 Codex 后确认 MCP server 已加载Codex 启动后先看它有没有加载spring-ai-demo。不同版本展示方式不同有的在欢迎信息里列出 MCP servers有的需要你在对话里问“当前有哪些 MCP 工具”。如果完全看不到queryUserAddress先不要急着改 Java 代码回到上一章手动跑一遍 stdio 启动命令看 jar 是否还能输出两个 tools。MCP Server 加载成功和模型通道成功是两件事。模型通道失败会报 401、404 或模型不存在MCP 失败会表现为工具列表为空、工具调用超时、或者 Codex 说找不到queryUserAddress。排查时分开看不要混在一起改配置。5.2 提示词要明确调用 queryUserAddress在 Codex 对话里不要只问“张三住哪里”那样模型可能直接编一个地址。要明确要求使用 MCP 工具并指定工具名和参数名。可以这样写请使用 spring-ai-demo MCP server 里的 queryUserAddress 工具参数 userName 传“张三”只返回住址字段不要自己编造。如果 Codex 支持工具调用确认它会先请求启动spring-ai-demo然后发起一次queryUserAddress调用。你可以在 Java 进程日志里看到按姓名查询住址: 张三说明请求已经进入 UserServiceImpl。TaoToken 在这条链路里只负责 Codex 的模型推理Token 用量记在你的 Key 上查询逻辑没有离开本地。5.3 预期返回北京市海淀区中关村大街1号如果一切正常Codex 返回的住址应该是“北京市海淀区中关村大街1号”。这个结果来自UserServiceImpl静态块里的userAddressMap不是模型训练数据。验证时最好把这个地址和原文示例保持一致这样你能一眼看出返回的是本地 Map 还是模型瞎编。再测第二个工具确认两个 tools 都能被 Codex 调用请使用 spring-ai-demo 的 queryUserName 工具参数 userNumber 传“N001”只返回姓名。预期返回“张三”。两个工具都通过说明 Spring AI MCP Server 的 Tool 注册、Codex 的 MCP 配置、TaoToken 的模型通道三条线都通了。5.4 返回 null 与工具未调用的区别返回null和“工具没被调用”是两种问题。返回null说明 MCP 调用已经进入queryUserAddress只是userAddressMap.get(张三)没取到值。这时检查 UserServiceImpl 静态块有没有执行或者传进去的姓名是不是带了空格。工具没被调用时Codex 会直接给你一段自然语言回答Java 日志里也不会有“按姓名查询住址”这一行。如果 Codex 说“我没有 queryUserAddress 工具”回到 config.toml 检查[mcp_servers.spring-ai-demo]这一段有没有拼错jar 路径是否存在java命令是否能在终端直接执行。改完配置后完全退出 Codex 再重新启动不要只开新会话有些版本不会热加载 MCP 配置。6. 对照 Trae 验证tools 为空、401、404 分别怎么查6.1 Codex 看不到 tools先手动启动 jarCodex 看不到工具时第一步不是改提示词而是手动执行 config.toml 里那段java命令。把-Dlogging.pattern.console临时改成默认日志看启动日志里有没有注册 tools。如果手动启动能看到 2 个 tools而 Codex 看不到问题在 Codex 的 MCP 配置或启动环境如果手动启动也看不到问题在 Spring AI 项目本身。重点检查三处Tool是否加在UserServiceImpl的实现方法上ToolCallbackProviderBean 是否注册了UserService打包后的 jar 是不是最新版本。改完代码忘记mvn package是最常见的原因Codex 启动的还是旧 jar工具列表自然对不上。6.2 401 与 404Key 和 Base URL 两条线分开查401 通常来自模型通道说明TAOTOKEN_API_KEY没有传进 Codex或者 Key 被复制错了。回 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 控制台重新创建一把 Key替换环境变量后重开终端。不要在同一台机器上混用多把 Key验证阶段只留一把排除变量覆盖。404 通常来自 Base URL 写错。Codex 的base_url必须是https://taotoken.net/api末尾不要加/v1也不要写成官网首页。如果你把 UTM 参数带进了base_url请求会落到错误路径表现可能是 404 或 400。官网链接只用于注册、创建 Key、看模型广场和看用量填进工具里的地址一律用https://taotoken.net/api。6.3 改完接口后重新验证的最短路径改完UserServiceImpl后最短验证路径是mvn package重新打包退出 Codex重新启动 Codex在对话里再发一次调用queryUserAddress的提示词。不需要再打开 Trae 的 MCP 面板删配置也不需要重新挂智能体。如果你同时保留了 Trae 验证记得 Trae 那边也重新指向新 jar否则两边验证结果会不一致。这个流程跑顺之后Tool 的验证就从“图形化配置 智能体挂载”变成了“打包 重启 Codex 一句话”。Trae 仍然可以用来做交叉验证但日常改接口时Codex 这条链路更短报错也更集中。7. 这次调用的 Token 记在哪回控制台对用量7.1 在模型广场核对模型 ID验证完成后回 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 模型广场确认你 config.toml 里填的YOUR_MODEL_ID和广场当前列表一致。如果广场里已经找不到这个 IDCodex 下一次启动可能报模型不存在。把模型 ID 改成广场里可用的名字再重新启动 Codex。模型 ID 不要在文章或配置里写死成某个带日期后缀的猜测值。以模型广场当时列表为准这是唯一可靠的来源。7.2 在控制台看这次调用记了多少 Token同一把 Key 的用量在控制台里看。这次验证会消耗两类 TokenCodex 理解提示词和生成回答的 Token以及它决定调用queryUserAddress时的工具调用 Token。查询逻辑本身在本地 UserServiceImpl 里执行不消耗模型 Token。对照用量时重点看是不是记在了你刚创建的那把 Key 上而不是旧 Key 或别的项目 Key。如果用量为 0先检查 Codex 是不是真的走了 TaoToken 的模型通道。可以回 TaoToken 模型对话 用同一把 Key 发一条测试消息确认 Key 和模型 ID 都能用。模型对话能通说明 Key 没问题再回头看 Codex 的model_provider和env_key。7.3 下一步把 Coding Plan 接进日常验证如果你每天都要改 UserServiceImpl、重新验证 tools可以看 Coding Plan 是否够用。Key 不够就再去 控制台 API Keys 创建。以后要把 Codex 换成 Claude Code 继续跑同一套 MCP Server环境变量对照看 Claude Code 接入文档。这次验证的重点不是 Trae 里那个 MCP 面板而是让 Tool 的注册结果直接出现在 Codex 对话里同时把 Codex 的 Token 用量记到 TaoToken 控制台。