ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

jFinal 使用 SolonMCP 开发 MCP:把 endpoint 改到 TaoToken 的 Java8 实践

jFinal 使用 SolonMCP 开发 MCP:把 endpoint 改到 TaoToken 的 Java8 实践 1. jFinal 老项目想接 MCPJava8 卡在哪一步如果你手上跑着一个 jFinal 单体应用JDK 还停在 8最近又被 MCPModel Context Protocol刷屏想给系统加一个能被大模型调用的工具入口大概率会先撞上一堵墙官方mcp-java-sdk要求 Java 17 起步直接升 JDK 对老项目来说牵一发动全身Spring 版本、依赖冲突、打包脚本全得跟着动。SolonMCPsolon-ai-mcp解决的正是这个尴尬。它是 Solon 生态里的 MCP 扩展可以内嵌进 jFinal、Vert.x、Spring Boot 2/3 等框架用接近 MVC 的写法把方法暴露成 MCP 工具、资源和提示词而且能在 Java8 上跑。换句话说你不用换框架、不用升 JDK只要在现有 jFinal 里挂一个 Handler 和一个 Plugin就能让/mcp/sse这个端点活起来。这篇面向的是已有 jFinal 单体应用、想快速暴露 MCP 能力的后端开发者。我会把依赖坐标、入口类、路由挂载、endpoint 指向 TaoToken 统一 Key/API 通道的配置片段全部写出来再给一次本地调用验证和返回结果核对清单。适合谁能改 jFinal 配置、会看 Maven 依赖、想用最小改动试水 MCP 的后端同学。不适合谁完全没接触过 jFinal 生命周期、也不打算读配置的人。先说清楚一个概念避免后面混淆。MCP 服务端Server负责暴露能力MCP 客户端Client负责调用能力而大模型LLM是最终消费方。SolonMCP 同时提供了服务端注解和客户端McpClientProvider所以你可以先在本机把服务端跑起来再用客户端验证最后把客户端接到 TaoToken 的统一通道上让模型通过标准接口来调你的 jFinal 业务方法。我试过在一台只装了 JDK8 的机器上从零搭这个链路踩的坑主要集中在异步支持和 endpoint 路径匹配上下面按顺序讲。2. TaoToken 前置准备统一 Key 与 API 通道在动手改 jFinal 之前先把外部通道准备好。MCP 客户端最终要调用大模型如果每个模型都单独配一套 Key 和地址代码里会散落一堆常量换模型就得改代码。TaoToken 提供的是统一 Key 和统一 API 通道客户端只需要认一个 Base URL 和一个 Key模型 ID 作为参数传入切换模型时改一个字符串就行。你需要准备三样东西我把它叫做「三件套」后面所有配置都围绕它展开项目值说明Base URLhttps://taotoken.net/api统一 API 通道地址不加任何查询参数API Key在控制台创建形如sk-开头的一串字符只显示一次Model ID例如claude-sonnet-4-5等具体以文档里的模型列表为准获取路径很直接打开官网https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content注册登录后进入控制台在 API Keys 页面创建一个新 Key。创建时建议按用途命名比如jfinal-mcp-dev方便以后区分环境和轮换。Key 只在创建时完整显示一次复制后先存到本地环境变量或配置中心别直接硬编码进 Git 仓库。注意Base URL 用https://taotoken.net/api不要在后面拼/v1之类的后缀也不要带 UTM 参数客户端拼接路径时容易出问题。控制台里还能看到模型列表和用量统计。模型 ID 建议先用文档里标注的稳定版本别一上来就选实验性模型排查问题时变量太多。如果你只是本地验证链路通不通选一个响应快的轻量模型即可等链路跑通再换更强的模型做实际业务。把这三件套写进一个本地配置文件比如mcpserver.yml同级放一个llm.properties或者直接用环境变量注入。环境变量的好处是打包镜像时不用改文件坏处是本地调试要记得 export。我一般两种都留配置文件写默认值环境变量可覆盖。export TAOTOKEN_BASE_URLhttps://taotoken.net/api export TAOTOKEN_API_KEYsk-你的Key export TAOTOKEN_MODELclaude-sonnet-4-5到这里前置就绪。接下来进入 jFinal 侧的实际改造重点是让 Solon 容器和 jFinal 容器共存并且把/mcp/开头的请求交给 Solon 处理。3. 可复制配置依赖、入口类与 endpoint 挂载这一节是全文的核心所有片段都可以直接复制。先加 Maven 依赖solon-ai-mcp的版本以你仓库里能拉到的最新稳定版为准写死一个你验证过的版本号别用LATEST。dependency groupIdorg.noear/groupId artifactIdsolon-ai-mcp/artifactId version3.x.x/version /dependency然后是 jFinal 入口类。关键点有两个一是onDeploy里给 jfinal 过滤器开启异步支持MCP 内部基于响应式不开异步会直接卡住二是把McpServerConfig同时注册为 Plugin 和 HandlerPlugin 负责 Solon 生命周期Handler 负责请求转发。public class HelloApp extends JFinalConfig { public static void main(String[] args) { UndertowServer.create(HelloApp.class) .setDevMode(false) .setPort(8080) .onDeploy((cl, di) - { // 关键MCP 基于响应式必须开启异步支持 di.getFilters().get(jfinal).setAsyncSupported(true); }) .start(); } public void configConstant(Constants me) { me.setDevMode(false); } public void configRoute(Routes me) { // 业务路由照常写MCP 走 Handler 不走 Route } public void configEngine(Engine me) { } public void configPlugin(Plugins me) { me.add(mcpServerConfig); } public void configInterceptor(Interceptors me) { } public void configHandler(Handlers me) { me.add(mcpServerConfig); } private McpServerConfig mcpServerConfig new McpServerConfig(); }接着是McpServerConfig它继承Handler并实现IPlugin。start()里启动 Solon 并指定配置文件handle()里判断路径前缀命中/mcp/就交给 Solon 处理否则放行给下一个 Handler。这里有个细节isHandled[0] true一定要设否则 jFinal 会继续往下找处理器导致重复响应。public class McpServerConfig extends Handler implements IPlugin { public boolean start() { Solon.start(McpServerConfig.class, new String[]{--cfgmcpserver.yml}); return true; } public boolean stop() { if (Solon.app() ! null) { Solon.stopBlock(false, Solon.cfg().stopDelay()); } return true; } Override public void handle(String target, HttpServletRequest request, HttpServletResponse response, boolean[] isHandled) { if (target.startsWith(/mcp/)) { Context ctx new SolonServletContext(request, response); try { Solon.app().tryHandle(ctx); if (isHandled ! null isHandled.length 0) { isHandled[0] true; } } catch (Throwable e) { ctx.errors e; throw e; } finally { ContextUtil.currentRemove(); } } else { if (next ! null) { next.handle(target, request, response, isHandled); } } } }然后是 MCP 服务端点类用McpServerEndpoint声明 SSE 路径方法上用ToolMapping、ResourceMapping、PromptMapping分别暴露工具、资源和提示词。编译时建议加-parameters参数否则参数名会丢失得在每个Param里手写 name。McpServerEndpoint(sseEndpoint /mcp/sse) public class McpServer { ToolMapping(description 查询天气预报) public String getWeather(Param(description 城市位置) String location) { return 晴14度; } ResourceMapping(uri config://app-version, description 获取应用版本号) public String getAppVersion() { return v3.2.0; } ResourceMapping(uri db://users/{user_id}/email, description 根据用户ID查询邮箱) public String getEmail(Param(description 用户Id) String user_id) { return user_id example.com; } PromptMapping(description 生成关于某个主题的提问) public CollectionChatMessage askQuestion(Param(description 主题) String topic) { return Arrays.asList( ChatMessage.ofUser(请解释一下 topic 的概念) ); } }最后是mcpserver.yml把 Solon 的端口和 MCP 相关配置写进去。注意这个端口是 Solon 内部用的jFinal 对外还是 8080请求通过 Handler 转发进来。server: port: 8081 solon: app: name: jfinal-mcp-serverMaven 编译参数别忘了加plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-compiler-plugin/artifactId configuration compilerArgs arg-parameters/arg /compilerArgs /configuration /plugin到这里配置就齐了。启动HelloApp.main控制台看到 Solon 启动日志和 jFinal 启动日志都打印出来说明两个容器共存成功。下一步做验证。4. 验证请求本地调用与返回结果核对验证分两层先验证 MCP 服务端本身能响应再验证客户端能通过 TaoToken 通道让模型调用到你的工具。第一层直接用McpClientProvider连本地 SSE 端点调用getWeather工具核对返回是不是晴14度。public class McpClientTest { public static void main(String[] args) throws Exception { McpClientProvider toolProvider McpClientProvider.builder() .apiUrl(http://localhost:8080/mcp/sse) .build(); MapString, Object map Collections.singletonMap(location, 杭州); String rst toolProvider.callToolAsText(getWeather, map).getContent(); System.out.println(rst); assert 晴14度.equals(rst); String version toolProvider.readResourceAsText(config://app-version).getContent(); System.out.println(version); } }跑通后控制台应该依次输出晴14度和v3.2.0。如果assert没抛异常说明工具调用和资源读取两条链路都通了。第二层把 MCP 客户端作为工具集挂到 ChatModel 上让模型自己决定调哪个工具。这里用 TaoToken 的统一通道Base URL 填https://taotoken.net/apiKey 从环境变量读模型 ID 用你准备好的那个。public class McpWithLlmTest { public static void main(String[] args) throws Exception { String baseUrl System.getenv(TAOTOKEN_BASE_URL); String apiKey System.getenv(TAOTOKEN_API_KEY); String model System.getenv(TAOTOKEN_MODEL); McpClientProvider toolProvider McpClientProvider.builder() .apiUrl(http://localhost:8080/mcp/sse) .build(); ChatModel chatModel ChatModel.of(baseUrl /chat/completions) .apiKey(apiKey) .model(model) .defaultToolsAdd(toolProvider) .build(); ChatResponse resp chatModel.prompt(杭州今天的天气怎么样).call(); System.out.println(resp.getMessage()); } }核对清单如下逐条对检查项期望结果不符时看哪Solon 启动日志打印 app name 和端口mcpserver.yml路径jFinal 启动日志打印路由和端口 8080HelloApp配置/mcp/sse可访问返回 SSE 事件流Handler 路径前缀工具调用返回晴14度ToolMapping方法资源读取返回v3.2.0ResourceMappinguri模型回复含天气提到晴、14 度三件套配置模型回复里如果出现了「晴14度」这类信息说明它确实调用了你的 jFinal 方法而不是自己编的。这一步是整个链路打通的标志。提示验证阶段把模型温度调低减少它自由发挥的概率更容易判断工具是否真的被调用。5. 常见报错排查401、local proxy failed 与 reading choices链路跑不通时报错信息往往指向几个固定位置。下面按我实际遇到的顺序列出来对照着查。401 Unauthorized。最常见的原因是 Key 没读到或读错了。先确认环境变量在当前 shell 里echo $TAOTOKEN_API_KEY有值再确认代码里读的是同一个变量名。如果 Key 是从配置文件读的检查有没有多余空格或换行。还有一种情况是 Key 被禁用或额度耗尽去控制台看用量。注意 Base URL 别写成带/v1的旧习惯统一用https://taotoken.net/api。local proxy failed / connection refused。这个报错通常出现在客户端连本地 MCP 服务端时说明http://localhost:8080/mcp/sse根本没起来。检查三件事HelloApp.main是否真的启动成功、8080 端口是否被占用、target.startsWith(/mcp/)的路径是否和McpServerEndpoint的sseEndpoint一致。我踩过的坑是sseEndpoint写成/sse但 Handler 判断的是/mcp/结果请求进来直接被放行给下一个 Handler客户端一直等不到响应。reading choices 相关报错。这类报错一般出现在解析模型响应时说明返回结构不是预期的 chat completion 格式。先确认请求地址拼的是baseUrl /chat/completions再确认模型 ID 在 TaoToken 的模型列表里存在。如果模型 ID 写错有些通道会返回错误结构而不是标准 404解析时就会在choices字段上报错。把模型 ID 换成文档里明确列出的稳定版本再试。OAuth / 认证方式不匹配。如果你之前配过别的客户端可能残留了 OAuth 相关的配置项而 TaoToken 走的是 API Key 认证。检查配置文件里有没有多余的authType、oauth字段删掉后只保留apiKey。Claude Code 这类工具如果之前配过别的通道也要把旧的认证配置清干净否则会优先走旧配置。异步未开启导致的挂起。表现是请求发出去后一直不返回也不报错。回到HelloApp.onDeploy确认di.getFilters().get(jfinal).setAsyncSupported(true)这行真的执行了。如果 jFinal 版本较老过滤器名字可能不是jfinal打印一下di.getFilters()的 key 集合确认。参数名为空。工具调用时报参数缺失但客户端明明传了。这是编译时没加-parameters导致的方法参数名被编译成arg0、arg1。两个解法加编译参数或者在每个Param里显式写name。排查顺序建议从内到外先确认本地 MCP 服务端能被McpClientProvider直接调用再确认模型能通过 TaoToken 通道回复最后才怀疑业务逻辑。这样能把问题范围快速缩小到某一层。6. 把链路固定下来接入文档与后续动作链路跑通之后建议把配置固化别每次靠记忆。三件套写进配置中心或环境变量模板mcpserver.yml和HelloApp的异步设置写进项目 README新同学拉下来就能跑。如果你在排障阶段卡在认证或路径上直接对照接入文档里的示例再核一遍比反复猜快得多。需要新建或轮换 Key 时去 API Keys 页面操作别在代码里改。想先确认模型通道本身是否正常可以先用模型对话页面发一条消息排除掉模型侧的问题再回来查 MCP 侧。后续要扩展的话方向有几个把ToolMapping方法接到真实的 jFinal Service 上让模型能查真实业务数据给不同环境配不同的 Key用命名区分把McpServerConfig的路径前缀做成可配置项避免和现有路由冲突。每加一个工具就补一条验证用例保证工具描述和实际行为一致模型才不会调错。最后留一个实用技巧工具方法的description写得越具体模型选对工具的概率越高。别写「查询数据」写「根据用户 ID 查询邮箱地址输入为纯数字字符串」。这个细节比调模型参数更影响实际效果。
返回列表