1. 为什么 Java 开发者需要一个 MCP Proxy
MCP 这两年火得很快,但真正落到 Java 项目里,很多人第一步就卡住了。原因不复杂:MCP 的通讯方式有好几种,官方和社区的实现又各自为政,你手里的工具链未必能直接对接上。
先把 MCP 的通讯方式理清楚。目前主流分两大类:
| 通道 | 说明 | 现状 |
|---|---|---|
| stdio | 本地进程内通讯 | 生态最成熟,大量 mcp-server 都是这种 |
| sse http | 远程 http 通讯 | 已有实现,适合给前端或远程调用 |
| streamable http | 远程 http 通讯 | 官方刚通过,mcp-java-sdk 还没跟上 |
按部署形态又能分成「本地进程间通讯」和「远程通讯」两类。问题就出在这里:你本地攒了一堆 stdio 的 mcp-server,每个都要单独配 command、env、token;前端或者别的服务想调用,又得走远程 http。配置散落在各个客户端的 json 里,鉴权 token 一处一份,改一次要翻好几个文件。
这就是 MCP Proxy 要解决的事。代理层把后端的 stdio / sse 服务统一收口,对外只暴露一个 endpoint,鉴权、转发、协议转换都在代理里做。Java 这边可以直接用solon-ai-mcp来写,它同时支持 java8、java11、java17、java21、java24,对老项目也友好。
我试过把本地几个 mcp-server 通过 Solon AI MCP 收口成一个 sse endpoint,再把上游 endpoint 指到 TaoToken 的统一通道,客户端只需要认一个地址和一个 Key。下面把完整过程拆开讲,包括依赖、配置、代码和验证动作。
先说清楚适合谁看:如果你是用 Java 写后端、手里有若干 stdio mcp-server、又想让前端或远程服务统一调用,这篇就是给你准备的。不需要你先把 MCP 协议读一遍,跟着配置走就能跑通。
2. TaoToken 前置准备:统一 Key 与 API 通道
代理层要解决「鉴权难统一」,核心思路是把上游模型的访问凭证收敛到一处。TaoToken 在这里扮演的就是统一入口的角色:你不需要在每个 mcp-server 里各配一份 Key,而是让代理层统一持有,后端服务只管转发。
先做前置准备。打开官网 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 注册并登录,进入控制台。控制台地址是 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite ,在里面创建 API Key。
创建完 Key 之后,去 API Keys 页面管理你的凭证:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 。这里建议按用途分 Key,比如代理层用一个、本地调试用一个,方便后面排查问题时定位。
API 的基础地址是 https://taotoken.net/api ,注意这个地址不带任何查询参数,配置里直接写这个就行。模型对话的入口在 https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=models&utm_campaign=rewrite ,你可以先在这里确认要用的 Model ID,后面配置里要填。
如果你后面要接 Claude Code 这类编码工具,对应的接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,Claude Code 的专门说明在 https://taotoken.net/claudecode?utm_source=taotoken_aicg_blog_end&utm_content=claudecode&utm_campaign=rewrite 。长期做编码或 Agent 的话,可以看下 Coding Plan:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite 。
前置准备就三件事:拿到 Key、确认 Base URL、确认 Model ID。这三样东西后面会同时出现在代理配置里,缺一不可。很多人配代理失败,最后发现是 Model ID 写错或者 Base URL 多带了斜杠,所以这一步别跳过。
注意:Key 属于敏感凭证,不要硬编码进提交到仓库的配置文件。建议用环境变量注入,或者放在本地不纳入版本管理的配置里。
3. 可复制配置:Solon AI MCP 代理片段
这一节是重点,给出可以直接复制的配置。先加依赖,solon-ai-mcp的坐标如下:
<dependency> <groupId>org.noear</groupId> <artifactId>solon-ai-mcp</artifactId> <version>3.2.1-M3</version> </dependency>3.1 用 mcpServers 格式加载后端
经典的mcpServers配置格式现在很通用,stdio mcp-server 项目基本都会提供。新建一个mcp/mcpServers.case1.json:
{ "mcpServers": { "gitee": { "command": "mcp-gitee-ent", "env": { "GITEE_ENT_API_BASE": "https://api.gitee.com/enterprises", "GITEE_ENT_MCP_ACCESS_TOKEN": "<your mcp ent access token>" } } } }然后写代理服务端,用McpClientToolProvider加载配置,再通过McpServerEndpoint输出:
@McpServerEndpoint(sseEndpoint = "/mcp/proxy/gitee") public class McpServerTool implements ToolProvider { McpClientToolProvider toolProvider = McpClientToolProvider .fromMcpServers("classpath:mcp/mcpServers.case1.json") .get("gitee"); @Override public Collection<FunctionTool> getTools() { return toolProvider.getTools(); } }原理很直白:McpClientToolProvider把 mcpServers 里的服务加载成工具,McpServerEndpoint再把这些工具对外输出,代理效果就出来了。因为 mcpServers 支持多服务,解析后是个 Map,.get("gitee")就是取其中一个。
3.2 用 yaml 配置加载
如果你更习惯 yaml,可以在app.yml里按McpClientProperties的实体属性配:
solon.ai: mcp: client: gitee: channel: "stdio" serverParameters: command: "mcp-gitee-ent" env: GITEE_ENT_API_BASE: "https://api.gitee.com/enterprises" GITEE_ENT_MCP_ACCESS_TOKEN: "<your mcp ent access token>"服务端直接注入:
@McpServerEndpoint(sseEndpoint = "/mcp/proxy/gitee") public class McpServerTool implements ToolProvider { @Inject("${solon.ai.mcp.client.gitee}") McpClientToolProvider toolProvider; @Override public Collection<FunctionTool> getTools() { return toolProvider.getTools(); } }3.3 把上游 endpoint 指到 TaoToken
前面两种是把本地 stdio 服务代理成 sse。如果你要代理的是远程 http 服务,或者想让代理层统一走 TaoToken 的通道,就把apiUrl指过去。这里三件套要写全:Base URL、Key、Model ID。
McpClientToolProvider sseToolProvider = McpClientToolProvider.builder() .apiUrl("https://taotoken.net/api") .apiKey(System.getenv("TAOTOKEN_API_KEY")) .model("your-model-id") .build();如果是反向代理,把 sse mcp-server 代理成 stdio 输出:
@McpServerEndpoint(channel = McpChannel.STDIO) public class McpServerTool implements ToolProvider { McpClientToolProvider sseToolProvider = McpClientToolProvider.builder() .apiUrl("http://localhost:8081/mcp/sse") .build(); @Override public Collection<FunctionTool> getTools() { return sseToolProvider.getTools(); } }打包后就能被别的工具通过 mcpServers 配置调用:
{ "mcpServers": { "demo1": { "command": "java", "args": ["-jar", "/demo-mcp-stdio/target/demo-mcp-stdio.jar"] } } }Java 侧也可以直接用 builder 构建 stdio 客户端:
McpClientToolProvider mcpClient = McpClientToolProvider.builder() .channel(McpChannel.STDIO) .serverParameters(McpServerParameters.builder("java") .args("-jar", "/demo-mcp-stdio/target/demo-mcp-stdio.jar") .build()) .build();配置到这一步,代理链路就搭好了。关键点是:Base URL 写https://taotoken.net/api,Key 从环境变量读,Model ID 填你在模型对话页确认过的那个。
4. 验证请求:跑通代理链路
配置写完不代表通了,得实际发一次请求验证。启动 Solon 应用后,代理的 sse endpoint 会挂在/mcp/proxy/gitee这类路径上。
先确认服务起来了,看启动日志里有没有 endpoint 注册成功的记录。然后可以用 curl 探一下 sse 端点是否响应:
curl -N http://localhost:8080/mcp/proxy/gitee-N是关闭缓冲,sse 是流式的,不加这个看不到实时输出。正常的话你会看到event:和data:开头的行持续输出。
接着验证工具列表能不能拉到。MCP 客户端初始化后会请求 tools/list,你可以用官方客户端或者自己写个简单的调用。如果代理层配置正确,返回的 tools 列表应该和后端 mcp-server 暴露的一致。
再验证一次实际调用。挑一个后端工具,比如 gitee 的某个查询接口,通过代理发一次请求,看返回结果是否正常。这一步能同时验证三件事:代理转发通不通、鉴权对不对、上游 endpoint 有没有指错。
如果上游指向 TaoToken,验证时重点看返回里有没有正常的模型响应内容。常见的成功标志是返回结构里带choices字段,且内容非空。如果返回 401,说明 Key 有问题;如果报连接错误,多半是 Base URL 写错了。
实测下来,最容易出问题的是 Model ID。很多人以为随便填一个就行,结果请求发出去返回模型不存在。所以验证前一定去模型对话页确认一遍可用的 Model ID。
验证通过后,你可以把代理 endpoint 配到前端或其他服务里,它们只需要认这一个地址,不用再关心后端有几个 mcp-server、各自怎么鉴权。
5. 常见报错排查
代理链路跑不通时,报错信息往往比较隐晦。这里列几个高频的,对照着查。
401 Unauthorized:鉴权失败。先检查 Key 有没有正确注入,环境变量名对不对。如果 Key 是从控制台复制的,注意别把首尾空格带进去。还有一种情况是 Key 权限不够,去 API Keys 页面确认这个 Key 的状态。
local proxy failed / 连接被拒绝:代理层连不上上游。检查apiUrl是不是写成了https://taotoken.net/api/(多了斜杠),或者本地 stdio 服务的 command 路径不对。stdio 模式下还要确认command指向的可执行文件在 PATH 里能找到。
reading choices 报错 / 返回结构里没有 choices:说明请求发出去了但响应格式不对。大概率是 Model ID 填错,或者上游返回的是错误信息被当成了正常响应。去模型对话页核对 Model ID,确认 Base URL 是https://taotoken.net/api。
OAuth 相关报错:如果你接的是需要 OAuth 的服务,检查 token 有没有过期。这类报错通常带invalid_token或expired字样,重新走一遍授权流程即可。
工具列表为空:代理起来了但 tools/list 返回空。检查mcpServers配置里的服务名和.get("xxx")是否一致,yaml 模式下检查@Inject的路径和配置层级对不对。
stdio 子进程启动失败:反向代理成 stdio 时,java -jar的路径要写绝对路径,相对路径在不同工作目录下会找不到 jar 包。
排查顺序建议从外往里:先确认代理服务本身起来了,再确认能连上上游,最后确认鉴权。这样能快速定位是哪一层的问题。
6. 继续接入与统一管理
代理跑通之后,日常维护的重点就变成统一管理。所有后端服务的 Key 收敛到代理层,客户端只认一个 endpoint,改配置时只动一处。这对团队协作尤其有用,新人接入不用再挨个问每个 mcp-server 的 token。
如果你还要接编码工具,Claude Code 的接入说明在 https://taotoken.net/claudecode?utm_source=taotoken_aicg_blog_end&utm_content=claudecode&utm_campaign=rewrite ,完整文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite 。需要管理更多 Key 就去 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite ,长期做 Agent 或编码的可以看 Coding Plan:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite 。
最后留个实用技巧:代理层的配置建议按环境分文件,本地、测试、生产各一份,用 Solon 的配置加载机制切换。这样切环境时不用改代码,也不会把生产 Key 带到本地。