用 Java 5 分钟写一个 MCP Server:基于开源 MCP Java SDK 接入 TaoToken 统一 Key
1. Java 开发者为什么需要一个能跑起来的 MCP ServerMCP 全称 Model Context Protocol你可以把它理解成 AI Agent 和外部世界之间的一根标准数据线。大模型本身只会生成文本它不知道你数据库里有哪些表、不知道你 Redis 里缓存了什么、更没法直接调用你 Spring Boot 里那个写了三年的订单查询接口。MCP 就是来解决这件事的它定义了一套统一的协议让 Agent 通过标准方式发现并调用你暴露出来的工具、资源和提示词。我身边不少 Java 同学第一次接触 MCP 时看的都是 Node.js 或 Python 的示例。照着敲一遍能跑但一旦想把自己项目里的业务能力接进去就卡住了——总不能为了一个工具调用再学一套 JS 生态吧。Java 开发者需要的是一个符合自己习惯的入口Maven 依赖、注解、Spring Boot 自动装配最好五分钟内能看到一个能响应请求的 Server。这篇要做的就是这件事。我会用一个开源的 MCP Java SDK带你从零搭一个 MCP Server把工具注册进去本地启动然后用 curl 验证工具列表能不能正常返回。同时把服务端点的鉴权配置统一改到 TaoToken 的 Key/API 通道上这样你后面接 Claude Code、Cline 或者自己的 Agent 时不用每个客户端都单独配一遍密钥。适合谁看有 Java 基础、用过 Maven、写过 Spring Boot 的开发者想把自己的内部系统暴露给 AI Agent 但不想碰 Node/Python 的人以及已经在用 MCP 客户端、想自己写 Server 的折腾党。先说清楚 MCP Server 到底在干什么。它本质上是一个进程通过 stdio 或者 SSE 两种传输方式和客户端通信。客户端发过来的是 JSON-RPC 格式的请求比如tools/list、tools/callServer 解析后执行对应逻辑再把结果按协议格式返回。你不需要手写 JSON-RPC 的序列化反序列化SDK 会帮你处理。你要做的只有两件事定义工具方法启动传输层。工具方法就是普通的 Java 方法加上注解描述它的名字、参数、用途。SDK 在启动时扫描这些注解把它们注册成 MCP 协议里的 tool。Agent 看到的工具列表就是你这些方法的元数据。这里有个容易混淆的点MCP Server 不是 Web 服务至少 stdio 模式下不是。它不监听端口不处理 HTTP 请求而是通过标准输入输出和父进程通信。所以你不能用浏览器直接访问它得用 MCP 客户端或者专门的调试工具。这也是为什么后面验证环节我会用 curl 配合 SSE 模式而不是直接 curl 一个 stdio 进程。理解了这些再看代码就不会觉得是在念咒语了。下面进入实操。2. TaoToken 统一 Key 的前置准备与 MCP Java SDK 依赖引入在写代码之前先把 Key 的事情理清楚。MCP Server 本身如果只是本地 stdio 跑其实不涉及外部鉴权。但一旦你的工具需要调用大模型能力或者你要把 Server 以 SSE 方式暴露出去给远程 Agent 用就需要一个统一的 API 通道来管理鉴权和计费。TaoToken 在这里扮演的就是这个角色一个统一的 Key 和 API 入口兼容主流模型调用格式你不需要为每个客户端单独申请密钥。先去官网注册并拿到 Keyhttps://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。登录后在控制台创建 API Key复制出来保存好后面配置里要用。API 的基础地址是 https://taotoken.net/api 注意这个地址不带任何查询参数配置时直接填这个。拿到 Key 之后回到 Java 工程。我用的是 Maven 多模块结构核心依赖是开源的 MCP Java SDK。在pom.xml里加入以下依赖dependencies dependency groupIdio.github.6000fish/groupId artifactIdmcp-sdk/artifactId version0.1.1/version /dependency dependency groupIdio.github.6000fish/groupId artifactIdmcp-spring-boot-starter/artifactId version0.1.1/version /dependency /dependencies如果你只是想要核心 SDK不加 Spring Boot Starter 也能跑用DefaultMcpServer.builder()手动构建即可。但既然场景里提到了 Spring Boot 工程结构我建议两个都加上后面用 starter 会自动扫描注解并装配 Server省掉手写启动类的麻烦。依赖拉下来之后确认一下版本。0.1.1 是当前稳定版已经发布到 Maven Central不需要额外配仓库地址。如果你公司内网有私服记得把io.github.6000fish这个 groupId 加进镜像白名单否则可能拉不到。接下来配置 Key。在src/main/resources/application.yml里加上taotoken: api-key: ${TAOTOKEN_API_KEY:sk-your-key-here} base-url: https://taotoken.net/api model: claude-sonnet-4-20250514这里用环境变量优先、配置文件兜底的方式避免把 Key 硬编码进代码提交到 Git。本地开发时在 IDE 的运行配置里加一个TAOTOKEN_API_KEY环境变量就行。生产环境用配置中心或者容器 secret 注入。注意base-url填的是https://taotoken.net/api不要在后面加/v1或者别的路径SDK 内部会自己拼接。模型 ID 按你实际要用的填这里只是示例。依赖和配置都齐了下一步写 Server 启动类和工具注册。3. 可复制的 Server 启动类与工具注册配置先写一个最简的启动类。如果你用了 Spring Boot Starter其实可以更省事但为了让你看清楚整个链路我先给出手动构建的版本再给 Spring Boot 版本。手动构建的启动类长这样import io.github.mcpjava.sdk.DefaultMcpServer; import io.github.mcpjava.sdk.McpServer; import io.github.mcpjava.sdk.transport.StdioTransport; import io.github.mcpjava.sdk.annotation.McpAnnotationScanner; public class MyMcpServer { public static void main(String[] args) { McpServer server DefaultMcpServer.builder() .name(my-java-server) .version(1.0.0) .build(); McpAnnotationScanner.scan(server, new MyTools()); server.start(new StdioTransport()); } }DefaultMcpServer.builder()构建 Server 实例name和version是给客户端看的元数据。McpAnnotationScanner.scan()扫描你传入的对象把带注解的方法注册成工具。最后server.start(new StdioTransport())启动 stdio 传输进程会阻塞在这里等待客户端请求。工具类这样写import io.github.mcpjava.sdk.annotation.McpTool; import io.github.mcpjava.sdk.annotation.Param; public class MyTools { McpTool(name greet, description 根据名字返回问候语) public String greet(Param(name name) String name) { return Hello, name !; } McpTool(name current_time, description 返回服务器当前时间) public String currentTime() { return java.time.LocalDateTime.now().toString(); } McpTool(name calculate, description 计算两个整数之和) public int calculate( Param(name a) int a, Param(name b) int b) { return a b; } }每个McpTool方法就是一个工具。name是 Agent 调用时用的标识description会展示给模型看帮它判断什么时候该调这个工具。参数用Param标注名字SDK 会自动做类型转换。如果你用 Spring Boot Starter可以省掉手动扫描。在启动类上加SpringBootApplication然后定义一个BeanConfiguration public class McpConfig { Bean public McpServer mcpServer(MyTools myTools) { McpServer server DefaultMcpServer.builder() .name(spring-boot-mcp-server) .version(1.0.0) .build(); McpAnnotationScanner.scan(server, myTools); return server; } }然后在application.yml里指定传输方式mcp: transport: stdio server: name: spring-boot-mcp-server version: 1.0.0Starter 会在应用启动时自动拉起 Server。如果你要改成 SSE 模式把transport改成sse再加一个端口配置mcp: transport: sse sse: port: 8081 path: /mcp/sseSSE 模式下 Server 会监听 HTTP 端口这时候就可以用 curl 来验证了。stdio 模式没法直接 curl得用 MCP 客户端连。关于鉴权配置改到 TaoToken 统一 Key 这件事分两种情况。如果你的工具方法内部要调用大模型比如做一个「总结文本」的工具那就在工具类里注入一个 HTTP 客户端请求时带上 TaoToken 的 KeyMcpTool(name summarize, description 调用大模型总结文本) public String summarize(Param(name text) String text) { // 从配置读取 apiKey 和 baseUrl String apiKey System.getenv(TAOTOKEN_API_KEY); String baseUrl https://taotoken.net/api; // 用 HttpClient 发请求Header 里带 Authorization: Bearer {apiKey} // 具体请求体按模型接口格式构造 return callModel(baseUrl, apiKey, text); }如果你的 Server 是以 SSE 方式暴露给远程 Agent那鉴权应该在传输层做。可以在 SSE 的路径上加一个拦截器校验请求头里的 Key 是否匹配 TaoToken 下发的凭证。这样所有连过来的 Agent 都走同一个 Key 通道不用每个客户端单独配。配置片段汇总一下方便你直接复制。application.ymlserver: port: 8080 taotoken: api-key: ${TAOTOKEN_API_KEY} base-url: https://taotoken.net/api model: claude-sonnet-4-20250514 mcp: transport: sse server: name: my-java-server version: 1.0.0 sse: port: 8081 path: /mcp/ssepom.xml依赖片段前面已经给过这里不重复。注意 Spring Boot Starter 的版本要和 mcp-sdk 保持一致都是 0.1.1混用版本可能出现注解扫描不到的问题。代码写完mvn package打包然后java -jar target/my-server-1.0.0.jar启动。看到日志里输出MCP Server started on SSE port 8081就说明起来了。4. 验证请求用 curl 检查 MCP 工具列表与调用结果Server 起来之后第一件事是确认工具列表能正常返回。SSE 模式下MCP 的交互分两步先建立 SSE 连接拿到一个 session 端点再往那个端点发 JSON-RPC 请求。先开一个终端发起 SSE 连接curl -N http://localhost:8081/mcp/sse-N是关闭缓冲让你能实时看到服务端推过来的事件。正常的话会返回类似这样的内容event: endpoint data: /mcp/message?sessionIdabc123-def456这个sessionId就是本次会话的标识后面的请求都要带上它。记下这个路径。再开一个终端发tools/list请求curl -X POST http://localhost:8081/mcp/message?sessionIdabc123-def456 \ -H Content-Type: application/json \ -d { jsonrpc: 2.0, id: 1, method: tools/list, params: {} }如果一切正常你会收到工具列表的 JSON 响应里面包含greet、current_time、calculate三个工具的元数据每个都有 name、description 和 inputSchema。inputSchema 是 SDK 根据方法参数自动生成的 JSON SchemaAgent 靠它知道该传什么参数。接着验证工具调用curl -X POST http://localhost:8081/mcp/message?sessionIdabc123-def456 \ -H Content-Type: application/json \ -d { jsonrpc: 2.0, id: 2, method: tools/call, params: { name: greet, arguments: { name: Java } } }预期返回{ jsonrpc: 2.0, id: 2, result: { content: [ { type: text, text: Hello, Java! } ] } }看到这个就说明整条链路通了curl 发请求 → SSE 传输 → SDK 解析 JSON-RPC → 调用你的 Java 方法 → 结果按协议格式返回。再测一下calculatecurl -X POST http://localhost:8081/mcp/message?sessionIdabc123-def456 \ -H Content-Type: application/json \ -d { jsonrpc: 2.0, id: 3, method: tools/call, params: { name: calculate, arguments: { a: 3, b: 5 } } }返回的 text 应该是8。注意 SDK 会把返回值转成字符串放进 content 里即使你方法返回的是 int。如果你用的是 stdio 模式没法直接 curl可以用 MCP 官方提供的 inspector 工具或者直接在你的 Agent 客户端里配置。以 Claude Code 为例在配置文件里加{ mcpServers: { my-java-server: { type: stdio, command: java, args: [ -jar, /absolute/path/to/target/my-server-1.0.0.jar ], env: { TAOTOKEN_API_KEY: sk-your-key-here } } } }重启客户端后Agent 就能看到你注册的工具了。这里 env 里传的TAOTOKEN_API_KEY会被 Server 进程读取用于内部调用大模型时的鉴权。验证环节的关键是先确认tools/list能返回再确认tools/call能执行。两步都过了说明 Server 本身没问题剩下的就是往工具方法里填业务逻辑。5. 常见报错排查401、local proxy failed 与 reading choices实际跑的时候大概率不会一次成功我把踩过的坑列一下对照着排查。401 Unauthorized。这个最常见通常是 TaoToken 的 Key 没配对。检查三个地方环境变量TAOTOKEN_API_KEY是否真的注入到了进程里可以在启动类里打印一下System.getenv(TAOTOKEN_API_KEY)确认base-url是否写成了https://taotoken.net/api有没有多写/v1或者结尾斜杠请求头里的Authorization格式是否是Bearer sk-xxx注意 Bearer 后面有一个空格。如果 Key 是从配置文件读的确认 YAML 缩进没写错api-key和base-url在同一层级。local proxy failed。这个报错一般出现在你通过某个客户端连 Server 时客户端尝试走本地代理但没连上。先确认你的 Server 进程本身是活的ps -ef | grep java能看到。然后确认端口没被占用lsof -i:8081检查一下。如果是 stdio 模式检查客户端配置里的command路径是不是绝对路径args里的 jar 包路径是否存在。相对路径在客户端启动时的工作目录可能和你预期的不一样统一用绝对路径最稳。reading choices 相关报错。这个通常出现在工具方法内部调用大模型接口时响应体解析失败。原因可能是模型返回的 JSON 结构和你的解析代码不匹配或者model参数填的模型 ID 不存在。先确认application.yml里的model值是你账号下有权限调用的模型。然后在调用大模型的地方把原始响应打出来看看别直接反序列化。如果响应里是错误信息而不是正常结构解析自然会失败。工具列表为空。tools/list返回空数组说明注解扫描没生效。检查工具类是否被McpAnnotationScanner.scan()传进去了McpTool注解的包路径是否和依赖里的类一致方法是否是 public 的。Spring Boot Starter 模式下确认工具类被 Spring 管理加了Component或者Bean声明。SSE 连接建立后收不到 endpoint 事件。检查mcp.sse.path配置和 curl 的 URL 是否一致。有些客户端会在路径后面自动加斜杠导致匹配不上。另外确认没有防火墙或者安全组拦截 8081 端口。JSON-RPC 返回 method not found。说明请求的 method 名字拼错了MCP 协议里是tools/list和tools/call注意是复数 tools不是 tool。id 字段也要带上否则某些客户端会认为是通知而不是请求。中文乱码。工具返回的中文在客户端显示成问号检查启动参数里有没有加-Dfile.encodingUTF-8。Java 默认编码在有些系统上不是 UTF-8显式指定一下。排查的基本思路是先确认进程活着再确认传输层通再确认协议层通最后确认业务逻辑对。一层一层往下查别跳步。6. 把 MCP Server 接进你的日常开发流Server 跑通之后接下来就是把它用起来。几个实际场景你可以直接套。第一个场景是把内部接口暴露给 Agent。你有一个查询订单状态的 HTTP 接口写一个McpTool方法包一层Agent 就能在对话里直接查订单。方法内部用 HttpClient 调你的接口返回结果转成字符串。这样不用改现有系统加一个 MCP Server 模块就行。第二个场景是数据库查询。开源项目里已经带了 MySQL 和 Redis 的 ready-to-use Server你直接构建就能用。MySQL Server 支持query、list_tables、describe_table等工具而且做了安全限制query只允许单条 SELECTexecute只允许 INSERT 和 UPDATEDELETE 和 DROP 会被拒绝。这对让 Agent 辅助排查数据问题很有用又不用担心它误删数据。第三个场景是配合 Coding Plan 做长期编码任务。如果你在用 Claude Code 或者类似的编码 Agent可以把 MCP Server 配进去让 Agent 在写代码时能调用你的工具。比如一个「查接口文档」的工具Agent 写完代码后自己调一下确认参数对不对。这种场景下建议用 TaoToken 的 Coding Plan统一管理调用额度和鉴权不用每个工具单独配 Key。具体可以看 https://taotoken.net/coding-plan 。配置的时候记住三件套Base URL 填https://taotoken.net/apiKey 填你控制台创建的 API KeyModel ID 填你要用的模型标识。这三个在客户端配置、环境变量、代码里都要保持一致任何一处写错都会导致 401 或者模型调用失败。如果你用的是 Cline 或者带 MCP 支持的编辑器插件配置方式类似在 MCP 配置里加一个 server 条目type 选 stdio 或 ssecommand 和 args 指向你的 jar 包。CC Switch 这类工具切换配置时注意把 TaoToken 的 Key 一起带过去别切完发现鉴权丢了。调试工具方面模型对话页面可以帮你快速验证 Key 和模型是否可用https://taotoken.net/model-conversation 。接入文档在 https://taotoken.net/doc 里面有各客户端的详细配置步骤。API Keys 管理在 https://taotoken.net/api-keys Key 泄露了可以在这里吊销重发。最后说一个实用技巧把 MCP Server 的启动脚本写成 shell把环境变量注入和 java 命令放一起这样换机器或者换客户端时直接跑脚本不用每次手动配。脚本里记得用exec java -jar ...而不是直接java -jar ...这样信号能正确传递给 JVM 进程客户端关闭时 Server 能干净退出。代码写到这里一个能跑、能验证、能接进日常流程的 Java MCP Server 就完整了。剩下的就是往工具方法里填你自己的业务逻辑把内部能力一个个暴露出去。