sendMessage(McpSchema.JSONRPCMessage message, String messageId);
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/solon-ai-mcp/src/main/java/io/modelcontextprotocol/spec/McpTransportException.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 - 2025 the original author or authors.
3 | */
4 | package io.modelcontextprotocol.spec;
5 |
6 | /**
7 | * Exception thrown when there is an issue with the transport layer of the Model Context
8 | * Protocol (MCP).
9 | *
10 | *
11 | * This exception is used to indicate errors that occur during communication between the
12 | * MCP client and server, such as connection failures, protocol violations, or unexpected
13 | * responses.
14 | *
15 | * @author Christian Tzolov
16 | */
17 | public class McpTransportException extends RuntimeException {
18 |
19 | private static final long serialVersionUID = 1L;
20 |
21 | public McpTransportException(String message) {
22 | super(message);
23 | }
24 |
25 | public McpTransportException(String message, Throwable cause) {
26 | super(message, cause);
27 | }
28 |
29 | public McpTransportException(Throwable cause) {
30 | super(cause);
31 | }
32 |
33 | public McpTransportException(String message, Throwable cause, boolean enableSuppression,
34 | boolean writableStackTrace) {
35 | super(message, cause, enableSuppression, writableStackTrace);
36 | }
37 |
38 | }
--------------------------------------------------------------------------------
/solon-ai-mcp/src/main/java/io/modelcontextprotocol/spec/McpTransportSessionNotFoundException.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024-2025 the original author or authors.
3 | */
4 |
5 | package io.modelcontextprotocol.spec;
6 |
7 | /**
8 | * Exception that signifies that the server does not recognize the connecting client via
9 | * the presented transport session identifier.
10 | *
11 | * @author Dariusz Jędrzejczyk
12 | */
13 | public class McpTransportSessionNotFoundException extends RuntimeException {
14 |
15 | /**
16 | * Construct an instance with a known {@link Exception cause}.
17 | * @param sessionId transport session identifier
18 | * @param cause the cause that was identified as a session not found error
19 | */
20 | public McpTransportSessionNotFoundException(String sessionId, Exception cause) {
21 | super("Session " + sessionId + " not found on the server", cause);
22 | }
23 |
24 | /**
25 | * Construct an instance with the session identifier but without a {@link Exception
26 | * cause}.
27 | * @param sessionId transport session identifier
28 | */
29 | public McpTransportSessionNotFoundException(String sessionId) {
30 | super("Session " + sessionId + " not found on the server");
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/solon-ai-mcp/src/main/java/io/modelcontextprotocol/spec/ProtocolVersions.java:
--------------------------------------------------------------------------------
1 | package io.modelcontextprotocol.spec;
2 |
3 | public interface ProtocolVersions {
4 |
5 | /**
6 | * MCP protocol version for 2024-11-05.
7 | * https://modelcontextprotocol.io/specification/2024-11-05
8 | */
9 | String MCP_2024_11_05 = "2024-11-05";
10 |
11 | /**
12 | * MCP protocol version for 2025-03-26.
13 | * https://modelcontextprotocol.io/specification/2025-03-26
14 | */
15 | String MCP_2025_03_26 = "2025-03-26";
16 |
17 | /**
18 | * MCP protocol version for 2025-06-18.
19 | * https://modelcontextprotocol.io/specification/2025-06-18
20 | */
21 | String MCP_2025_06_18 = "2025-06-18";
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/solon-ai-mcp/src/main/java/io/modelcontextprotocol/util/DeafaultMcpUriTemplateManagerFactory.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 - 2025 the original author or authors.
3 | */
4 |
5 | package io.modelcontextprotocol.util;
6 |
7 | /**
8 | * @author Christian Tzolov
9 | */
10 | public class DeafaultMcpUriTemplateManagerFactory implements McpUriTemplateManagerFactory {
11 |
12 | /**
13 | * Creates a new instance of {@link McpUriTemplateManager} with the specified URI
14 | * template.
15 | * @param uriTemplate The URI template to be used for variable extraction
16 | * @return A new instance of {@link McpUriTemplateManager}
17 | * @throws IllegalArgumentException if the URI template is null or empty
18 | */
19 | @Override
20 | public McpUriTemplateManager create(String uriTemplate) {
21 | return new DefaultMcpUriTemplateManager(uriTemplate);
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/solon-ai-mcp/src/main/java/io/modelcontextprotocol/util/McpUriTemplateManagerFactory.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 - 2025 the original author or authors.
3 | */
4 |
5 | package io.modelcontextprotocol.util;
6 |
7 | /**
8 | * Factory interface for creating instances of {@link McpUriTemplateManager}.
9 | *
10 | * @author Christian Tzolov
11 | */
12 | public interface McpUriTemplateManagerFactory {
13 |
14 | /**
15 | * Creates a new instance of {@link McpUriTemplateManager} with the specified URI
16 | * template.
17 | * @param uriTemplate The URI template to be used for variable extraction
18 | * @return A new instance of {@link McpUriTemplateManager}
19 | * @throws IllegalArgumentException if the URI template is null or empty
20 | */
21 | McpUriTemplateManager create(String uriTemplate);
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/solon-ai-mcp/src/main/java/org/noear/solon/ai/annotation/PromptMapping.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2025 noear.org and authors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.noear.solon.ai.annotation;
17 |
18 | import java.lang.annotation.*;
19 |
20 | /**
21 | * 提示语映射
22 | *
23 | * @author noear
24 | * @since 3.1
25 | */
26 | @Target({ElementType.METHOD})
27 | @Retention(RetentionPolicy.RUNTIME)
28 | @Documented
29 | public @interface PromptMapping {
30 | /**
31 | * 名字
32 | */
33 | String name() default "";
34 |
35 | /**
36 | * 标题
37 | */
38 | String title() default "";
39 |
40 | /**
41 | * 描述
42 | */
43 | String description();
44 | }
--------------------------------------------------------------------------------
/solon-ai-mcp/src/main/java/org/noear/solon/ai/mcp/McpChannel.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2025 noear.org and authors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.noear.solon.ai.mcp;
17 |
18 | /**
19 | * Mcp 通道
20 | *
21 | * @author noear
22 | * @since 3.1
23 | */
24 | public interface McpChannel {
25 | String STDIO = "stdio";
26 | String SSE = "sse";
27 | String STREAMABLE = "streamable";
28 | // String STATELESS = "Stateless";
29 | }
30 |
--------------------------------------------------------------------------------
/solon-ai-mcp/src/main/java/org/noear/solon/ai/mcp/exception/McpException.java:
--------------------------------------------------------------------------------
1 | package org.noear.solon.ai.mcp.exception;
2 |
3 | import org.noear.solon.exception.SolonException;
4 |
5 | /**
6 | * @author noear
7 | * @since 3.1
8 | */
9 | public class McpException extends SolonException {
10 | public McpException(String message) {
11 | super(message);
12 | }
13 |
14 | public McpException(String message, Throwable cause) {
15 | super(message, cause);
16 | }
17 |
18 | public McpException(Throwable cause) {
19 | super(cause);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/solon-ai-mcp/src/main/java/org/noear/solon/ai/mcp/server/IMcpServerEndpoint.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2025 noear.org and authors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.noear.solon.ai.mcp.server;
17 |
18 | /**
19 | * Mcp 服务端点声明(方便第三方框架批量获取后,构建 McpServerEndpointProvider)
20 | *
21 | * @author noear
22 | * @since 3.5
23 | */
24 | public interface IMcpServerEndpoint {
25 | }
26 |
--------------------------------------------------------------------------------
/solon-ai-mcp/src/main/java/org/noear/solon/ai/mcp/server/prompt/PromptProvider.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2025 noear.org and authors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.noear.solon.ai.mcp.server.prompt;
17 |
18 | import java.util.Collection;
19 |
20 | /**
21 | * 提示语提供者
22 | *
23 | * @author noear
24 | * @since 3.2
25 | */
26 | public interface PromptProvider {
27 | /**
28 | * 获取提示语
29 | */
30 | Collection getPrompts();
31 | }
--------------------------------------------------------------------------------
/solon-ai-mcp/src/main/java/org/noear/solon/ai/mcp/server/resource/ResourceProvider.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2025 noear.org and authors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.noear.solon.ai.mcp.server.resource;
17 |
18 | import java.util.Collection;
19 |
20 | /**
21 | * 资源提供者
22 | *
23 | * @author noear
24 | * @since 3.2
25 | */
26 | public interface ResourceProvider {
27 | /**
28 | * 获取资源
29 | */
30 | Collection getResources();
31 | }
--------------------------------------------------------------------------------
/solon-ai-mcp/src/main/resources/META-INF/solon/solon-ai-mcp.properties:
--------------------------------------------------------------------------------
1 | solon.plugin=org.noear.solon.ai.mcp.integration.McpPlugin
2 | solon.plugin.priority=20
--------------------------------------------------------------------------------
/solon-ai-mcp/src/test/java/demo/ai/mcp/client/McpClientApp.java:
--------------------------------------------------------------------------------
1 | package demo.ai.mcp.client;
2 |
3 | import org.noear.solon.Solon;
4 | import org.noear.solon.annotation.Import;
5 |
6 | /**
7 | * @author noear 2025/4/12 created
8 | */
9 | @Import(profiles = "app-client.yml")
10 | public class McpClientApp {
11 | public static void main(String[] args) {
12 | Solon.start(McpClientApp.class, args);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/solon-ai-mcp/src/test/java/demo/ai/mcp/client/McpClientConfig.java:
--------------------------------------------------------------------------------
1 | package demo.ai.mcp.client;
2 |
3 | import org.noear.solon.ai.chat.ChatConfig;
4 | import org.noear.solon.ai.chat.ChatModel;
5 | import org.noear.solon.ai.mcp.client.McpClientProvider;
6 | import org.noear.solon.annotation.Bean;
7 | import org.noear.solon.annotation.Configuration;
8 | import org.noear.solon.annotation.Inject;
9 |
10 | @Configuration
11 | public class McpClientConfig {
12 | @Bean
13 | public McpClientProvider clientWrapper(@Inject("${solon.ai.mcp.client.demo}") McpClientProvider client) {
14 | return client;
15 | }
16 |
17 | @Bean
18 | public ChatModel chatModel(@Inject("${solon.ai.chat.demo}") ChatConfig chatConfig, McpClientProvider toolProvider) {
19 | return ChatModel.of(chatConfig)
20 | .defaultToolsAdd(toolProvider)
21 | .build();
22 | }
23 | }
--------------------------------------------------------------------------------
/solon-ai-mcp/src/test/java/demo/ai/mcp/client_auth/AuthDemo.java:
--------------------------------------------------------------------------------
1 | package demo.ai.mcp.client_auth;
2 |
3 | import org.noear.solon.ai.mcp.client.McpClientProvider;
4 |
5 | /**
6 | * @author noear 2025/4/21 created
7 | */
8 | public class AuthDemo {
9 | public void case1() {
10 | //通过 queryString 传递(需要 3.2.1-M1 或之后)
11 | McpClientProvider toolProvider = McpClientProvider.builder()
12 | .apiUrl("http://xxx.xxx.xxx/sse?key=yyy")
13 | .build();
14 | }
15 |
16 | public void case2() {
17 | //通过 baisc auth 传递
18 | McpClientProvider toolProvider = McpClientProvider.builder()
19 | .apiUrl("http://xxx.xxx.xxx/sse")
20 | .apiKey("yyy")
21 | .build();
22 | }
23 |
24 | public void case3() {
25 | //通过 baisc auth 传递
26 | McpClientProvider toolProvider = McpClientProvider.builder()
27 | .apiUrl("http://xxx.xxx.xxx/sse")
28 | .headerSet("tokey", "yyy")
29 | .build();
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/solon-ai-mcp/src/test/java/demo/ai/mcp/client_ssl/SslDemo.java:
--------------------------------------------------------------------------------
1 | package demo.ai.mcp.client_ssl;
2 |
3 | import io.modelcontextprotocol.spec.McpSchema;
4 | import org.noear.solon.Utils;
5 | import org.noear.solon.ai.mcp.client.McpClientProvider;
6 | import org.noear.solon.net.http.impl.HttpSslSupplierDefault;
7 |
8 | /**
9 | * @author noear 2025/7/24 created
10 | */
11 | public class SslDemo {
12 | public void case1() {
13 | //通过 queryString 传递(需要 3.2.1-M1 或之后)
14 | McpClientProvider clientProvider = McpClientProvider.builder()
15 | .apiUrl("http://xxx.xxx.xxx/sse?key=yyy")
16 | .httpSsl(HttpSslSupplierDefault.getInstance())
17 | .build();
18 |
19 |
20 | clientProvider.getClient()
21 | .callTool(new McpSchema.CallToolRequest("demo", Utils.asMap("a", 1)))
22 | .doOnNext(rest ->{
23 | System.out.println(rest);
24 | })
25 | .subscribe();
26 | }
27 | }
--------------------------------------------------------------------------------
/solon-ai-mcp/src/test/java/demo/ai/mcp/server/ChatConfig.java:
--------------------------------------------------------------------------------
1 | package demo.ai.mcp.server;
2 |
3 | import org.noear.solon.ai.chat.ChatModel;
4 | import org.noear.solon.annotation.Bean;
5 | import org.noear.solon.annotation.Configuration;
6 |
7 | /**
8 | *
9 | * @author noear 2025/9/23 created
10 | *
11 | */
12 | @Configuration
13 | public class ChatConfig {
14 | /**
15 | * 与大模型集成
16 | */
17 | @Bean
18 | public ChatModel chatModel() throws Exception {
19 | return ChatModel.of(_Constants.chat_apiUrl)
20 | .provider(_Constants.chat_provider)
21 | .model(_Constants.chat_model)
22 | .build();
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/solon-ai-mcp/src/test/java/demo/ai/mcp/server/ChatController.java:
--------------------------------------------------------------------------------
1 | package demo.ai.mcp.server;
2 |
3 | import lombok.extern.slf4j.Slf4j;
4 | import org.noear.solon.ai.chat.ChatModel;
5 | import org.noear.solon.annotation.Controller;
6 | import org.noear.solon.annotation.Inject;
7 | import org.noear.solon.annotation.Mapping;
8 | import org.noear.solon.annotation.Produces;
9 | import org.noear.solon.core.util.MimeType;
10 | import reactor.core.publisher.Flux;
11 |
12 | /**
13 | * @author noear 2025/4/14 created
14 | */
15 | @Slf4j
16 | @Controller
17 | public class ChatController {
18 | @Inject
19 | ChatModel chatModel;
20 |
21 | @Produces(MimeType.TEXT_EVENT_STREAM_VALUE)
22 | @Mapping("/test/stream")
23 | public Flux stream(String prompt) throws Exception {
24 | return Flux.from(chatModel.prompt(prompt).stream())
25 | //.subscribeOn(Schedulers.boundedElastic()) //加这个打印效果更好
26 | .filter(resp -> resp.hasContent())
27 | .map(resp -> resp.getContent())
28 | .concatWithValues("[DONE]"); //有些前端框架,需要 [DONE] 实识用作识别
29 | }
30 | }
--------------------------------------------------------------------------------
/solon-ai-mcp/src/test/java/demo/ai/mcp/server/FilterImpl.java:
--------------------------------------------------------------------------------
1 | package demo.ai.mcp.server;
2 |
3 | import lombok.extern.slf4j.Slf4j;
4 | import org.noear.solon.annotation.Component;
5 | import org.noear.solon.core.handle.Context;
6 | import org.noear.solon.core.handle.Filter;
7 | import org.noear.solon.core.handle.FilterChain;
8 |
9 | /**
10 | * @author noear 2025/5/28 created
11 | */
12 | @Slf4j
13 | @Component
14 | public class FilterImpl implements Filter {
15 | @Override
16 | public void doFilter(Context ctx, FilterChain chain) throws Throwable {
17 | if (ctx.pathNew().equals("/demo2/sse") || ctx.pathNew().equals("/demo4/sse")) {
18 | log.warn(">> params: " + ctx.paramMap());
19 | log.warn(">> headers: " + ctx.headerMap());
20 | }
21 |
22 | chain.doFilter(ctx);
23 | }
24 | }
--------------------------------------------------------------------------------
/solon-ai-mcp/src/test/java/demo/ai/mcp/server/McpRedirectServer5.java:
--------------------------------------------------------------------------------
1 | package demo.ai.mcp.server;
2 |
3 | import org.noear.solon.ai.annotation.ToolMapping;
4 | import org.noear.solon.ai.mcp.McpChannel;
5 | import org.noear.solon.ai.mcp.server.annotation.McpServerEndpoint;
6 | import org.noear.solon.annotation.Component;
7 | import org.noear.solon.annotation.Controller;
8 | import org.noear.solon.annotation.Mapping;
9 | import org.noear.solon.annotation.Param;
10 | import org.noear.solon.core.handle.Context;
11 | import org.noear.solon.core.handle.Filter;
12 | import org.noear.solon.core.handle.FilterChain;
13 |
14 | /**
15 | * @author noear 2025/4/8 created
16 | */
17 | @Mapping("/demo5/jump")
18 | @Controller
19 | @McpServerEndpoint(channel = McpChannel.STREAMABLE, sseEndpoint = "/demo5/sse")
20 | public class McpRedirectServer5 {
21 | @ToolMapping(description = "查询天气预报", returnDirect = true)
22 | public String getWeather(@Param(description = "城市位置") String location, Context ctx) {
23 | System.out.println("------------: sessionId: " + ctx.sessionId());
24 |
25 | ctx.realIp();
26 |
27 | return "晴,14度";
28 | }
29 |
30 | @Mapping("sse")
31 | public void sse(Context ctx) throws Exception {
32 | ctx.redirect("/demo5/sse", 307);
33 | }
34 | }
--------------------------------------------------------------------------------
/solon-ai-mcp/src/test/java/demo/ai/mcp/server/McpServerApp.java:
--------------------------------------------------------------------------------
1 | package demo.ai.mcp.server;
2 |
3 | import org.noear.solon.Solon;
4 | import org.noear.solon.annotation.Import;
5 | import org.noear.solon.server.http.HttpServerConfigure;
6 |
7 | /**
8 | * @author noear 2025/4/8 created
9 | */
10 | @Import(profiles = "app-server.yml")
11 | public class McpServerApp {
12 | public static void main(String[] args) {
13 | if (Solon.app() != null) {
14 | if (Solon.app().source() != McpServerApp.class) {
15 | Solon.stopBlock();
16 | }
17 | }
18 |
19 | Solon.start(McpServerApp.class, args, app -> {
20 | app.onEvent(HttpServerConfigure.class, e -> {
21 | e.enableDebug(true);
22 | });
23 | });
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/solon-ai-mcp/src/test/java/demo/ai/mcp/server/McpServerAuth.java:
--------------------------------------------------------------------------------
1 | package demo.ai.mcp.server;
2 |
3 | import org.noear.solon.ai.annotation.ToolMapping;
4 | import org.noear.solon.ai.mcp.McpChannel;
5 | import org.noear.solon.ai.mcp.server.annotation.McpServerEndpoint;
6 | import org.noear.solon.annotation.Component;
7 | import org.noear.solon.annotation.Param;
8 | import org.noear.solon.core.handle.Context;
9 | import org.noear.solon.core.handle.Filter;
10 | import org.noear.solon.core.handle.FilterChain;
11 |
12 | /**
13 | * @author noear 2025/7/1 created
14 | */
15 | @Component
16 | @McpServerEndpoint(channel = McpChannel.STREAMABLE, sseEndpoint = "/auth/sse")
17 | public class McpServerAuth implements Filter {
18 | @ToolMapping(description = "查询天气预报")
19 | public String getWeather(@Param(description = "城市位置") String location) {
20 | return "晴,14度";
21 | }
22 |
23 | @Override
24 | public void doFilter(Context ctx, FilterChain chain) throws Throwable {
25 | if (ctx.pathNew().endsWith("/auth/sse")) {
26 | if (ctx.param("user").equals("1") == false) {
27 | ctx.status(401);
28 | return;
29 | }
30 | }
31 |
32 | chain.doFilter(ctx);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/solon-ai-mcp/src/test/java/demo/ai/mcp/server/McpServerConfig.java:
--------------------------------------------------------------------------------
1 | package demo.ai.mcp.server;
2 |
3 | import org.noear.solon.ai.chat.tool.MethodToolProvider;
4 | import org.noear.solon.ai.mcp.server.McpServerEndpointProvider;
5 | import org.noear.solon.annotation.Bean;
6 | import org.noear.solon.annotation.Configuration;
7 | import org.noear.solon.annotation.Inject;
8 |
9 | /**
10 | * @author noear 2025/4/17 created
11 | */
12 | @Configuration
13 | public class McpServerConfig {
14 | //用配置构建
15 | @Bean
16 | public McpServerEndpointProvider demo1(@Inject("${solon.ai.mcp.server.demo1}") McpServerEndpointProvider serverEndpoint,
17 | McpServerTool serverTool) {
18 | serverEndpoint.addTool(new MethodToolProvider(serverTool));
19 |
20 | return serverEndpoint;
21 | }
22 |
23 | //用构建器构建
24 | //@Bean
25 | public McpServerEndpointProvider demo2(McpServerTool2 serverTool) {
26 | McpServerEndpointProvider serverEndpoint = McpServerEndpointProvider.builder()
27 | .name("demo2")
28 | .mcpEndpoint("/demo2/sse")
29 | .build();
30 |
31 | serverEndpoint.addTool(new MethodToolProvider(serverTool));
32 |
33 | return serverEndpoint;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/solon-ai-mcp/src/test/java/demo/ai/mcp/server/McpServerTool.java:
--------------------------------------------------------------------------------
1 | package demo.ai.mcp.server;
2 |
3 | import org.noear.solon.ai.annotation.ToolMapping;
4 | import org.noear.solon.annotation.Component;
5 | import org.noear.solon.annotation.Param;
6 |
7 | /**
8 | * @author noear 2025/4/8 created
9 | */
10 | @Component
11 | public class McpServerTool {
12 | //
13 | // 建议开启编译参数:-parameters (否则,要再配置参数的 name)
14 | //
15 | @ToolMapping(description = "查询天气预报")
16 | public String getWeather(@Param(description = "城市位置") String location) {
17 | return "晴,14度";
18 | }
19 | }
--------------------------------------------------------------------------------
/solon-ai-mcp/src/test/java/demo/ai/mcp/server/McpServerTool3.java:
--------------------------------------------------------------------------------
1 | package demo.ai.mcp.server;
2 |
3 | import org.noear.solon.ai.annotation.ToolMapping;
4 | import org.noear.solon.ai.mcp.McpChannel;
5 | import org.noear.solon.ai.mcp.server.annotation.McpServerEndpoint;
6 | import org.noear.solon.annotation.Param;
7 |
8 | /**
9 | * @author noear 2025/4/8 created
10 | */
11 | @McpServerEndpoint(channel = McpChannel.STDIO)
12 | public class McpServerTool3 {
13 | //
14 | // 建议开启编译参数:-parameters (否则,要再配置参数的 name)
15 | //
16 | @ToolMapping(description = "查询天气预报")
17 | public String getWeather(@Param(description = "城市位置") String location) {
18 | return "晴,14度";
19 | }
20 | }
--------------------------------------------------------------------------------
/solon-ai-mcp/src/test/java/demo/ai/mcp/server/_Constants.java:
--------------------------------------------------------------------------------
1 | package demo.ai.mcp.server;
2 |
3 |
4 | public class _Constants {
5 | //换成自己的模型配置(参考:https://solon.noear.org/article/918)
6 | public static final String chat_apiUrl = "http://127.0.0.1:11434/api/chat";
7 | public static final String chat_provider = "ollama";
8 | public static final String chat_model = "qwen2.5:1.5b"; //"llama3.2";//deepseek-r1:1.5b;
9 |
10 |
11 |
12 | //换成自己的模型配置(参考:https://solon.noear.org/article/934)
13 | public static final String embedding_apiUrl = "http://127.0.0.1:11434/api/embed";
14 | public static final String embedding_provider = "ollama";
15 | public static final String embedding_model = "bge-m3";//
16 | }
17 |
--------------------------------------------------------------------------------
/solon-ai-mcp/src/test/java/demo/ai/mcp/server/art1/CalculatorTools.java:
--------------------------------------------------------------------------------
1 | package demo.ai.mcp.server.art1;
2 |
3 | import org.noear.solon.ai.annotation.ToolMapping;
4 | import org.noear.solon.ai.mcp.McpChannel;
5 | import org.noear.solon.ai.mcp.server.annotation.McpServerEndpoint;
6 | import org.noear.solon.annotation.Param;
7 |
8 | @McpServerEndpoint(channel = McpChannel.STREAMABLE, sseEndpoint = "/mcp/CalculatorTools/sse")
9 | public class CalculatorTools {
10 | @ToolMapping(description = "将两个数字相加")
11 | public int add(@Param int a, @Param int b) {
12 | return a + b;
13 | }
14 |
15 | @ToolMapping(description = "从第一个数中减去第二个数")
16 | public int subtract(@Param int a, @Param int b) {
17 | return a - b;
18 | }
19 |
20 | @ToolMapping(description = "将两个数相乘")
21 | public int multiply(@Param int a, @Param int b) {
22 | return a * b;
23 | }
24 |
25 | @ToolMapping(description = "将第一个数除以第二个数")
26 | public float divide(@Param float a, @Param float b) {
27 | return a / b;
28 | }
29 | }
--------------------------------------------------------------------------------
/solon-ai-mcp/src/test/java/demo/ai/mcp/server/outputschema/dataobject/CityInfo.java:
--------------------------------------------------------------------------------
1 | package demo.ai.mcp.server.outputschema.dataobject;
2 |
3 | import org.noear.solon.annotation.Param;
4 |
5 | /**
6 | * @Auther: ityangs@163.com
7 | * @Date: 2025/5/20 16:01
8 | * @Description:
9 | */
10 | public class CityInfo {
11 | @Param(description = "城市名")
12 | private String name;
13 |
14 | @Param(description = "城市编码")
15 | private String code;
16 |
17 | // 构造器、getter、setter 省略
18 | }
--------------------------------------------------------------------------------
/solon-ai-mcp/src/test/java/demo/ai/mcp/server/outputschema/dataobject/Result.java:
--------------------------------------------------------------------------------
1 | package demo.ai.mcp.server.outputschema.dataobject;
2 |
3 | /**
4 | * @Auther: ityangs@163.com
5 | * @Date: 2025/5/20 15:59
6 | * @Description:
7 | */
8 | public class Result {
9 | private String code;
10 | private String message;
11 | private T data;
12 |
13 | // 省略构造器、getter、setter
14 |
15 | public static Result ok(T data) {
16 | Result result = new Result<>();
17 | return result;
18 | }
19 | }
20 |
21 |
--------------------------------------------------------------------------------
/solon-ai-mcp/src/test/java/demo/ai/mcp/server/outputschema/dataobject/UserInfo.java:
--------------------------------------------------------------------------------
1 | package demo.ai.mcp.server.outputschema.dataobject;
2 |
3 | import com.fasterxml.jackson.annotation.JsonFormat;
4 | import org.noear.solon.annotation.Param;
5 |
6 | import java.util.Date;
7 |
8 | /**
9 | * @Auther: ityangs@163.com
10 | * @Date: 2025/5/20 15:59
11 | * @Description:
12 | */
13 | public class UserInfo {
14 | @Param(description = "用户名")
15 | private String name;
16 |
17 | @Param(description = "年龄")
18 | private Integer age;
19 |
20 | @Param(description = "性别。0表示女,1表示男")
21 | private Integer gender;
22 |
23 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
24 | @Param(description = "创建时间")
25 | private Date created = new Date(1751798348208L);
26 | }
--------------------------------------------------------------------------------
/solon-ai-mcp/src/test/java/demo/ai/mcp/server_context_path/McpServerApp.java:
--------------------------------------------------------------------------------
1 | package demo.ai.mcp.server_context_path;
2 |
3 | import org.noear.solon.Solon;
4 | import org.noear.solon.annotation.Import;
5 | import org.noear.solon.server.http.HttpServerConfigure;
6 |
7 | /**
8 | * @author noear 2025/4/8 created
9 | */
10 | @Import(profiles = "app-server2.yml")
11 | public class McpServerApp {
12 | public static void main(String[] args) {
13 | if (Solon.app() != null) {
14 | if (Solon.app().source() != McpServerApp.class) {
15 | Solon.stopBlock();
16 | }
17 | }
18 |
19 | Solon.start(McpServerApp.class, args, app -> {
20 | app.onEvent(HttpServerConfigure.class, e -> {
21 | e.enableDebug(true);
22 | });
23 | });
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/solon-ai-mcp/src/test/java/demo/ai/mcp/server_resume/McpServerApp.java:
--------------------------------------------------------------------------------
1 | package demo.ai.mcp.server_resume;
2 |
3 | import org.noear.solon.Solon;
4 | import org.noear.solon.annotation.Import;
5 | import org.noear.solon.scheduling.annotation.EnableScheduling;
6 |
7 | /**
8 | * @author noear 2025/4/23 created
9 | */
10 | @EnableScheduling
11 | @Import(profiles = "app-server.yml")
12 | public class McpServerApp {
13 | public static void main(String[] args) {
14 | Solon.start(McpServerApp.class, args);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/solon-ai-mcp/src/test/java/demo/ai/mcp/server_resume/McpServerTool.java:
--------------------------------------------------------------------------------
1 | package demo.ai.mcp.server_resume;
2 |
3 | import org.noear.solon.ai.annotation.ToolMapping;
4 | import org.noear.solon.ai.mcp.McpChannel;
5 | import org.noear.solon.ai.mcp.server.McpServerEndpointProvider;
6 | import org.noear.solon.ai.mcp.server.annotation.McpServerEndpoint;
7 | import org.noear.solon.annotation.Inject;
8 | import org.noear.solon.annotation.Param;
9 | import org.noear.solon.scheduling.annotation.Scheduled;
10 |
11 | /**
12 | * @author noear 2025/4/8 created
13 | */
14 | @McpServerEndpoint(channel = McpChannel.STREAMABLE, name = "mcp-server1")
15 | public class McpServerTool {
16 | @ToolMapping(description = "查询天气预报")
17 | public String getWeather(@Param(description = "城市位置") String location) {
18 | return "晴,14度";
19 | }
20 |
21 | //注入当前工具对应的端点提供者
22 | @Inject("mcp-server1")
23 | private McpServerEndpointProvider serverEndpointProvider;
24 |
25 | //30秒为间隔(暂停或恢复)//或者用 web 控制
26 | @Scheduled(fixedRate = 30_000)
27 | public void pauseAndResume() {
28 | if (serverEndpointProvider.pause() == false) {
29 | //如果暂停失败,说明之前已经暂停
30 | serverEndpointProvider.resume();
31 | }
32 | }
33 | }
--------------------------------------------------------------------------------
/solon-ai-mcp/src/test/java/demo/ai/mcp/server_resume/McpServerTool2.java:
--------------------------------------------------------------------------------
1 | package demo.ai.mcp.server_resume;
2 |
3 | import org.noear.solon.ai.annotation.ToolMapping;
4 | import org.noear.solon.ai.mcp.McpChannel;
5 | import org.noear.solon.ai.mcp.server.annotation.McpServerEndpoint;
6 | import org.noear.solon.annotation.Param;
7 |
8 | /**
9 | * @author noear 2025/4/8 created
10 | */
11 | @McpServerEndpoint(channel = McpChannel.STREAMABLE, sseEndpoint = "/case2/sse")
12 | public class McpServerTool2 {
13 | @ToolMapping(description = "查询天气预报")
14 | public String getWeather(@Param(description = "城市位置") String location) {
15 | return "晴,14度";
16 | }
17 | }
--------------------------------------------------------------------------------
/solon-ai-mcp/src/test/java/features/ai/mcp/client/ConfigTest.java:
--------------------------------------------------------------------------------
1 | package features.ai.mcp.client;
2 |
3 | import org.junit.jupiter.api.Test;
4 | import org.noear.solon.Solon;
5 | import org.noear.solon.ai.mcp.client.McpClientProperties;
6 | import org.noear.solon.test.SolonTest;
7 |
8 | @SolonTest(args = "-cfg=app-client.yml")
9 | public class ConfigTest {
10 | @Test
11 | public void case1() {
12 | McpClientProperties config = Solon.cfg().toBean("solon.ai.mcp.client.proxy1", McpClientProperties.class);
13 | assert config != null;
14 | assert config.getHttpProxy() != null;
15 | }
16 | }
--------------------------------------------------------------------------------
/solon-ai-mcp/src/test/java/features/ai/mcp/client/McpClientErrTest.java:
--------------------------------------------------------------------------------
1 | package features.ai.mcp.client;
2 |
3 | import org.junit.jupiter.api.Test;
4 | import org.noear.solon.ai.mcp.McpChannel;
5 | import org.noear.solon.ai.mcp.client.McpClientProvider;
6 |
7 | /**
8 | * @author noear 2025/6/12 created
9 | */
10 | public class McpClientErrTest {
11 | @Test
12 | public void connTest() throws Throwable {
13 | McpClientProvider clientProvider = McpClientProvider.builder()
14 | .channel(McpChannel.STREAMABLE)
15 | .apiUrl("https://mcp.map.baidu.com/sse")
16 | .build();
17 |
18 | long start = System.currentTimeMillis();
19 | try {
20 | clientProvider.getTools();
21 | clientProvider.close();
22 | } catch (Throwable e) {
23 |
24 | }
25 | long end = System.currentTimeMillis();
26 |
27 | assert start - end < 1000;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/solon-ai-mcp/src/test/java/features/ai/mcp/client/McpRedirectTest5.java:
--------------------------------------------------------------------------------
1 | package features.ai.mcp.client;
2 |
3 | import demo.ai.mcp.server.McpServerApp;
4 | import lombok.extern.slf4j.Slf4j;
5 | import org.junit.jupiter.api.Test;
6 | import org.noear.solon.Utils;
7 | import org.noear.solon.ai.mcp.McpChannel;
8 | import org.noear.solon.ai.mcp.client.McpClientProvider;
9 | import org.noear.solon.test.SolonTest;
10 |
11 | import java.util.Collections;
12 |
13 | /**
14 | * @author noear 2025/5/16 created
15 | */
16 | @Slf4j
17 | @SolonTest(McpServerApp.class)
18 | public class McpRedirectTest5 {
19 | @Test
20 | public void tool1() throws Exception {
21 | McpClientProvider mcpClient = McpClientProvider.builder()
22 | .channel(McpChannel.STREAMABLE)
23 | .apiUrl("http://localhost:8081/demo5/jump/sse")
24 | .build();
25 |
26 | String response = mcpClient.callToolAsText("getWeather", Collections.singletonMap("location", "杭州")).getContent();
27 |
28 | log.warn("{}", response);
29 | assert Utils.isNotEmpty(response);
30 | mcpClient.close();
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/solon-ai-mcp/src/test/java/features/ai/mcp/client/McpToolMixTest.java:
--------------------------------------------------------------------------------
1 | package features.ai.mcp.client;
2 |
3 | import demo.ai.mcp.server.McpServerApp;
4 | import lombok.extern.slf4j.Slf4j;
5 | import org.junit.jupiter.api.Test;
6 | import org.noear.solon.ai.mcp.McpChannel;
7 | import org.noear.solon.ai.mcp.client.McpClientProvider;
8 | import org.noear.solon.ai.media.Text;
9 | import org.noear.solon.test.SolonTest;
10 |
11 | /**
12 | * @author noear 2025/5/11 created
13 | */
14 | @Slf4j
15 | @SolonTest(McpServerApp.class)
16 | public class McpToolMixTest {
17 |
18 |
19 | @Test
20 | public void case1() throws Exception {
21 | McpClientProvider mcpClient = McpClientProvider.builder()
22 | .channel(McpChannel.STREAMABLE)
23 | .apiUrl("http://localhost:8081/mcp/WeatherTools/sse")
24 | .build();
25 |
26 | Text mediaText = mcpClient.readResourceAsText("weather://cities");
27 |
28 | System.out.println(mediaText);
29 |
30 | assert "[Tokyo, Sydney, Tokyo]".equals(mediaText.getContent());
31 | mcpClient.close();
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/solon-ai-mcp/src/test/java/lab/ai/a2a/AgentTaskHandler.java:
--------------------------------------------------------------------------------
1 | package lab.ai.a2a;
2 |
3 | /**
4 | * 智能体任务处理器
5 | *
6 | * @author noear
7 | * @since 3.5
8 | */
9 | public interface AgentTaskHandler {
10 | /**
11 | * 智能体任务处理
12 | */
13 | String handleTask(String message) throws Throwable;
14 | }
15 |
--------------------------------------------------------------------------------
/solon-ai-mcp/src/test/java/lab/ai/a2a/demo/a2a/Tools1.java:
--------------------------------------------------------------------------------
1 | package lab.ai.a2a.demo.a2a;
2 |
3 | import org.noear.solon.ai.annotation.ToolMapping;
4 | import org.noear.solon.annotation.Param;
5 |
6 | /**
7 | * @author haiTao.Wang on 2025/8/21.
8 | */
9 | public class Tools1 {
10 |
11 | @ToolMapping(description = "查询天气预报", returnDirect = true)
12 | public String getWeather(@Param(description = "城市位置") String location) {
13 | return location + "天气晴";
14 | }
15 |
16 | @ToolMapping(description = "查询温度", returnDirect = true)
17 | public String getTemperature(@Param(description = "城市位置") String location) {
18 | return location + "温度14度";
19 | }
20 | }
--------------------------------------------------------------------------------
/solon-ai-mcp/src/test/java/lab/ai/a2a/demo/a2a/Tools2.java:
--------------------------------------------------------------------------------
1 | package lab.ai.a2a.demo.a2a;
2 |
3 | import org.noear.snack.core.utils.StringUtil;
4 | import org.noear.solon.ai.annotation.ToolMapping;
5 | import org.noear.solon.annotation.Param;
6 |
7 | /**
8 | * @author haiTao.Wang on 2025/8/21.
9 | */
10 | public class Tools2 {
11 |
12 | @ToolMapping(description = "根据天气推荐旅游景点", returnDirect = true)
13 | public String recommendTourist(@Param(description = "天气") String weather) {
14 | if (StringUtil.isEmpty(weather)) {
15 | return "请输入天气";
16 | }
17 |
18 | if (weather.contains("晴")) {
19 | return "公园、爬山等室外运动";
20 | }
21 | return "海洋馆、科技馆等室内运动";
22 | }
23 |
24 | }
--------------------------------------------------------------------------------
/solon-ai-mcp/src/test/java/lab/ai/a2a/demo/a2a_remote/App.java:
--------------------------------------------------------------------------------
1 | package lab.ai.a2a.demo.a2a_remote;
2 |
3 | import org.noear.solon.Solon;
4 |
5 | /**
6 | * @author noear 2025/8/31 created
7 | */
8 | public class App {
9 | public static void main(String[] args) {
10 | Solon.start(App.class, new String[]{"--server.port=9001"});
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/solon-ai-mcp/src/test/java/lab/ai/a2a/demo/a2a_remote/Server1.java:
--------------------------------------------------------------------------------
1 | package lab.ai.a2a.demo.a2a_remote;
2 |
3 | import lab.ai.a2a.AgentTaskHandler;
4 | import lab.ai.a2a.demo.a2a.Tools1;
5 | import org.noear.solon.ai.annotation.ToolMapping;
6 | import org.noear.solon.ai.chat.ChatModel;
7 | import org.noear.solon.ai.mcp.McpChannel;
8 | import org.noear.solon.ai.mcp.server.annotation.McpServerEndpoint;
9 |
10 | /**
11 | * @author noear 2025/8/31 created
12 | */
13 | @McpServerEndpoint(channel = McpChannel.STREAMABLE, mcpEndpoint = "mcp1")
14 | public class Server1 implements AgentTaskHandler {
15 | ChatModel chatModel = ChatModel.of("http://127.0.0.1:11434/api/chat")
16 | .model("qwen2.5:1.5b")
17 | .provider("ollama")
18 | .defaultToolsAdd(new Tools1())
19 | .build();
20 |
21 | @ToolMapping(name = "weather_agent", description = "专业的天气预报助手。主要任务是利用所提供的工具获取并传递天气信息")
22 | @Override
23 | public String handleTask(String message) throws Throwable {
24 | return chatModel.prompt(message).call().getMessage().getResultContent();
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/solon-ai-mcp/src/test/java/lab/ai/a2a/demo/a2a_remote/Server2.java:
--------------------------------------------------------------------------------
1 | package lab.ai.a2a.demo.a2a_remote;
2 |
3 | import lab.ai.a2a.AgentTaskHandler;
4 | import lab.ai.a2a.demo.a2a.Tools2;
5 | import org.noear.solon.ai.annotation.ToolMapping;
6 | import org.noear.solon.ai.chat.ChatModel;
7 | import org.noear.solon.ai.mcp.McpChannel;
8 | import org.noear.solon.ai.mcp.server.annotation.McpServerEndpoint;
9 |
10 | /**
11 | * @author noear 2025/8/31 created
12 | */
13 | @McpServerEndpoint(channel = McpChannel.STREAMABLE, mcpEndpoint = "mcp2")
14 | public class Server2 implements AgentTaskHandler {
15 | ChatModel chatModel = ChatModel.of("http://127.0.0.1:11434/api/chat")
16 | .model("qwen2.5:latest")
17 | .provider("ollama")
18 | .defaultToolsAdd(new Tools2())
19 | .build();
20 |
21 | @ToolMapping(name = "spot_agent", description = "专业的景区推荐助手。主要任务是推荐景点信息")
22 | @Override
23 | public String handleTask(String message) throws Throwable {
24 | return chatModel.prompt(message).call().getMessage().getResultContent();
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/solon-ai-mcp/src/test/java/lab/ai/a2a/demo/tool_only/Tools1.java:
--------------------------------------------------------------------------------
1 | package lab.ai.a2a.demo.tool_only;
2 |
3 | import org.noear.solon.ai.annotation.ToolMapping;
4 | import org.noear.solon.annotation.Param;
5 |
6 | /**
7 | * @author haiTao.Wang on 2025/8/21.
8 | */
9 | public class Tools1 {
10 |
11 | @ToolMapping(description = "查询天气预报")
12 | public String getWeather(@Param(description = "城市位置") String location) {
13 | return location + "天气晴";
14 | }
15 |
16 | @ToolMapping(description = "查询温度")
17 | public String getTemperature(@Param(description = "城市位置") String location) {
18 | return location + "温度14度";
19 | }
20 | }
--------------------------------------------------------------------------------
/solon-ai-mcp/src/test/java/lab/ai/a2a/demo/tool_only/Tools2.java:
--------------------------------------------------------------------------------
1 | package lab.ai.a2a.demo.tool_only;
2 |
3 | import org.noear.snack.core.utils.StringUtil;
4 | import org.noear.solon.ai.annotation.ToolMapping;
5 | import org.noear.solon.annotation.Param;
6 |
7 | /**
8 | * @author haiTao.Wang on 2025/8/21.
9 | */
10 | public class Tools2 {
11 |
12 | @ToolMapping(description = "根据天气推荐旅游景点")
13 | public String recommendTourist(@Param(description = "天气") String weather) {
14 | if (StringUtil.isEmpty(weather)) {
15 | return "请输入天气";
16 | }
17 |
18 | if (weather.contains("晴")) {
19 | return "公园、爬山等室外运动";
20 | }
21 | return "海洋馆、科技馆等室内运动";
22 | }
23 |
24 | }
--------------------------------------------------------------------------------
/solon-ai-mcp/src/test/java/lab/ai/mcp/client/DashscopeTest.java:
--------------------------------------------------------------------------------
1 | package lab.ai.mcp.client;
2 |
3 | import org.junit.jupiter.api.Test;
4 | import org.noear.solon.ai.chat.tool.FunctionTool;
5 | import org.noear.solon.ai.mcp.McpChannel;
6 | import org.noear.solon.ai.mcp.client.McpClientProvider;
7 |
8 | import java.util.Collection;
9 |
10 | /**
11 | *
12 | * @author noear 2025/9/3 created
13 | *
14 | */
15 | public class DashscopeTest {
16 | @Test
17 | public void case1() {
18 | McpClientProvider clientProvider = McpClientProvider.builder()
19 | .channel(McpChannel.SSE)
20 | .apiUrl("https://dashscope.aliyuncs.com/api/v1/mcps/amap-maps/sse")
21 | .apiKey("xxx")
22 | .build();
23 |
24 | Collection tools = clientProvider.getTools();
25 | for (FunctionTool tool : tools) {
26 | System.out.println(tool.inputSchema());
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/solon-ai-mcp/src/test/java/lab/ai/mcp/client/McpServerTool.java:
--------------------------------------------------------------------------------
1 | package lab.ai.mcp.client;
2 |
3 | import org.noear.solon.ai.annotation.ToolMapping;
4 | import org.noear.solon.ai.mcp.McpChannel;
5 | import org.noear.solon.ai.mcp.server.annotation.McpServerEndpoint;
6 | import org.noear.solon.annotation.Param;
7 |
8 | @McpServerEndpoint(channel = McpChannel.STDIO) //表示使用 stdio
9 | public class McpServerTool {
10 | @ToolMapping(description = "查询天气预报")
11 | public String get_weather(@Param(description = "城市位置") String location) {
12 | return "晴,14度";
13 | }
14 | }
--------------------------------------------------------------------------------
/solon-ai-mcp/src/test/java/lab/ai/mcp/client/McpSseClientCloseTest.java:
--------------------------------------------------------------------------------
1 | package lab.ai.mcp.client;
2 |
3 | import org.noear.solon.ai.mcp.client.McpClientProvider;
4 |
5 | import java.util.Collections;
6 |
7 | /**
8 | * @author noear 2025/4/22 created
9 | */
10 | public class McpSseClientCloseTest {
11 | public static void main(String[] args) throws Exception {
12 | McpClientProvider toolProvider = McpClientProvider.builder()
13 | .apiUrl("http://localhost:8081/sse")
14 | .build();
15 |
16 |
17 | call(toolProvider);
18 |
19 | //关闭后即退出
20 | toolProvider.close();
21 | }
22 |
23 | private static void call(McpClientProvider toolProvider) {
24 | try {
25 | String response = toolProvider.callToolAsText("getWeather", Collections.singletonMap("location", "杭州")).getContent();
26 | assert response != null;
27 | System.err.println(response);
28 | } catch (Throwable e) {
29 | e.printStackTrace();
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/solon-ai-mcp/src/test/java/lab/ai/mcp/client/McpSseClientRetryTest.java:
--------------------------------------------------------------------------------
1 | package lab.ai.mcp.client;
2 |
3 | import org.noear.solon.ai.mcp.client.McpClientProvider;
4 | import org.noear.solon.core.util.RunUtil;
5 |
6 | import java.util.Collections;
7 |
8 | /**
9 | * @author noear 2025/4/22 created
10 | */
11 | public class McpSseClientRetryTest {
12 | public static void main(String[] args) throws Exception {
13 | McpClientProvider toolProvider = McpClientProvider.builder()
14 | .apiUrl("http://localhost:8081/sse")
15 | .build();
16 |
17 |
18 | call(toolProvider);
19 |
20 |
21 | RunUtil.delayAndRepeat(() -> {
22 | call(toolProvider);
23 | }, 1000);
24 |
25 | System.in.read();
26 | }
27 |
28 | private static void call(McpClientProvider toolProvider) {
29 | try {
30 | String response = toolProvider.callToolAsText("getWeather", Collections.singletonMap("location", "杭州")).getContent();
31 | assert response != null;
32 | System.err.println(response);
33 | } catch (Throwable e) {
34 | e.printStackTrace();
35 | }
36 | }
37 | }
--------------------------------------------------------------------------------
/solon-ai-mcp/src/test/java/lab/ai/mcp/client/McpSseClientRetryTest2.java:
--------------------------------------------------------------------------------
1 | package lab.ai.mcp.client;
2 |
3 | import org.noear.solon.ai.mcp.client.McpClientProvider;
4 |
5 | import java.util.Collections;
6 |
7 | /**
8 | * @author noear 2025/4/22 created
9 | */
10 | public class McpSseClientRetryTest2 {
11 | public static void main(String[] args) throws Exception {
12 | McpClientProvider toolProvider = McpClientProvider.builder()
13 | .apiUrl("http://localhost:8081/sse")
14 | .build();
15 |
16 |
17 | call(toolProvider);
18 |
19 | System.in.read();
20 | }
21 |
22 | private static void call(McpClientProvider toolProvider) {
23 | try {
24 | String response = toolProvider.callToolAsText("getWeather", Collections.singletonMap("location", "杭州")).getContent();
25 | assert response != null;
26 | System.err.println(response);
27 | } catch (Throwable e) {
28 | e.printStackTrace();
29 | }
30 | }
31 | }
--------------------------------------------------------------------------------
/solon-ai-mcp/src/test/java/lab/ai/mcp/client/server/McpSseToStdioServerDemo.java:
--------------------------------------------------------------------------------
1 | package lab.ai.mcp.client.server;
2 |
3 | import org.noear.solon.Solon;
4 | import org.noear.solon.ai.chat.tool.FunctionTool;
5 | import org.noear.solon.ai.chat.tool.ToolProvider;
6 | import org.noear.solon.ai.mcp.McpChannel;
7 | import org.noear.solon.ai.mcp.client.McpClientProvider;
8 | import org.noear.solon.ai.mcp.server.annotation.McpServerEndpoint;
9 |
10 | import java.util.Collection;
11 |
12 | /**
13 | * 把 sse mcp-server 转为 stdio mcp-server
14 | */
15 | @McpServerEndpoint(name = "sse-to-stdio-tool", channel = McpChannel.STDIO)
16 | public class McpSseToStdioServerDemo implements ToolProvider {
17 | McpClientProvider sseToolProvider = McpClientProvider.builder()
18 | .apiUrl("http://localhost:8081/sse")
19 | .build();
20 |
21 | @Override
22 | public Collection getTools() {
23 | return sseToolProvider.getTools();
24 | }
25 |
26 | public static void main(String[] args) {
27 | Solon.start(McpSseToStdioServerDemo.class, args);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/solon-ai-mcp/src/test/java/lab/ai/mcp/debug/client/McpClientLab.java:
--------------------------------------------------------------------------------
1 | package lab.ai.mcp.debug.client;
2 |
3 | import lombok.extern.slf4j.Slf4j;
4 | import org.noear.solon.ai.mcp.McpChannel;
5 | import org.noear.solon.ai.mcp.client.McpClientProvider;
6 |
7 | import java.util.Collections;
8 |
9 | /**
10 | *
11 | * @author noear 2025/8/22 created
12 | *
13 | */
14 | @Slf4j
15 | public class McpClientLab {
16 | public static void main(String[] args) throws Exception {
17 | McpClientProvider mcpClient = McpClientProvider.builder()
18 | .channel(McpChannel.STREAMABLE)
19 | .apiUrl("http://localhost:8081/mcp/")
20 | .cacheSeconds(30)
21 | .build();
22 |
23 | String response = mcpClient.callToolAsText("getWeather", Collections.singletonMap("location", "杭州")).getContent();
24 |
25 | log.warn("{}", response);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/solon-ai-mcp/src/test/java/lab/ai/mcp/debug/server/McpApp.java:
--------------------------------------------------------------------------------
1 | package lab.ai.mcp.debug.server;
2 |
3 | import org.noear.solon.Solon;
4 | import org.noear.solon.annotation.Import;
5 |
6 | @Import(profiles = "app-server.yml")
7 | public class McpApp {
8 | public static void main(String[] args) {
9 | Solon.start(McpApp.class, args);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/solon-ai-mcp/src/test/resources/app-client-serverparams.yml:
--------------------------------------------------------------------------------
1 | solon.ai:
2 | mcp:
3 | client:
4 | demo1:
5 | channel: "stdio"
6 | command: "xxx"
7 | args:
8 | - "-jar"
9 | - "/Users/noear/Downloads/demo-mcp-stdio/target/demo-mcp-stdio.jar"
10 | env:
11 | a: "1"
12 | demo2:
13 | channel: "stdio"
14 | command: "xxx"
15 | args: ["-jar", "/Users/noear/Downloads/demo-mcp-stdio/target/demo-mcp-stdio.jar"]
16 | env: {a: "1"}
17 |
--------------------------------------------------------------------------------
/solon-ai-mcp/src/test/resources/app-client.yml:
--------------------------------------------------------------------------------
1 | server.port: 8082
2 | server.idleTimeout: 120000
3 |
4 | solon.ai:
5 | chat:
6 | demo:
7 | apiUrl: "https://api.deepseek.com/v1/chat/completions"
8 | apiKey: "sk-9f4415ddc570496581897c22e3d41a54"
9 | provider: "deepseek"
10 | model: "deepseek-chat"
11 | mcp:
12 | client:
13 | demo1:
14 | channel: streamable
15 | apiUrl: "http://localhost:8081/sse"
16 | demo2:
17 | channel: streamable
18 | apiUrl: "http://localhost:8081/demo2/sse"
19 | proxy1:
20 | channel: streamable
21 | apiUrl: "http://localhost:8081/sse"
22 | httpProxy:
23 | type: "HTTP"
24 | host: "127.0.0.1"
25 | port: 7851
26 |
27 | solon.logging.logger.root.level: TRACE
--------------------------------------------------------------------------------
/solon-ai-mcp/src/test/resources/app-server.yml:
--------------------------------------------------------------------------------
1 | server.port: 8081
2 | server.idleTimeout: 120000
3 |
4 | solon.ai.mcp.server:
5 | demo1:
6 | channel: streamable
7 | sseEndpoint: "/sse"
8 | demo2:
9 | channel: streamable
10 | sseEndpoint: "/demo2/sse"
11 |
12 | solon.logging.logger:
13 | root.level: TRACE
--------------------------------------------------------------------------------
/solon-ai-mcp/src/test/resources/app-server2.yml:
--------------------------------------------------------------------------------
1 | server.port: 8082
2 | server.idleTimeout: 120000
3 | server.context-path: "test"
4 |
5 | solon.logging.logger:
6 | root.level: TRACE
--------------------------------------------------------------------------------
/solon-ai-mcp/src/test/resources/app.yml:
--------------------------------------------------------------------------------
1 | solon.logging.logger:
2 | root.level: DEBUG
--------------------------------------------------------------------------------
/solon-ai-mcp/src/test/resources/mcpServers.json:
--------------------------------------------------------------------------------
1 | {
2 | "mcpServers": {
3 | "server1": {
4 | "type": "streamable",
5 | "url": "http://localhost:8081/demo2/sse",
6 | "headers": {
7 | "Token": "value"
8 | }
9 | },
10 | "fetch": {
11 | "type": "stdio",
12 | "command": "/full/path/to/bin/mcp-proxy",
13 | "args": [
14 | "http://localhost:8932/sse"
15 | ]
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/solon-ai-mcp/src/test/resources/mcpServers2.json:
--------------------------------------------------------------------------------
1 | {
2 | "mcpServers": {
3 | "server1": {
4 | "type": "streamable",
5 | "url": "http://localhost:8081/demo2/sse",
6 | "headers": {
7 | "Token": "value"
8 | }
9 | },
10 | "fetch": {
11 | "command": "/full/path/to/bin/mcp-proxy",
12 | "args": [
13 | "http://localhost:8932/sse"
14 | ]
15 | }
16 | }
17 | }
--------------------------------------------------------------------------------
/solon-ai-rag-loaders/solon-ai-load-ddl/src/test/resources/sqlite.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/opensolon/solon-ai/0ae6af88f88b623c6ea7fdc324b06e061a5e8fcd/solon-ai-rag-loaders/solon-ai-load-ddl/src/test/resources/sqlite.db
--------------------------------------------------------------------------------
/solon-ai-rag-loaders/solon-ai-load-excel/src/test/java/features/ai/load/excel/ExcelLoaderTest.java:
--------------------------------------------------------------------------------
1 | package features.ai.load.excel;
2 |
3 | import org.junit.jupiter.api.Test;
4 | import org.noear.solon.ai.rag.Document;
5 | import org.noear.solon.ai.rag.loader.ExcelLoader;
6 | import org.noear.solon.core.util.ResourceUtil;
7 | import org.slf4j.Logger;
8 | import org.slf4j.LoggerFactory;
9 |
10 | import java.util.List;
11 |
12 | public class ExcelLoaderTest {
13 | private static final Logger log = LoggerFactory.getLogger(ExcelLoaderTest.class);
14 |
15 | @Test
16 | public void test1() throws Exception {
17 | ExcelLoader loader = new ExcelLoader(ResourceUtil.getResource("demo.xlsx"));
18 | List docs = loader.load();
19 | System.out.println(docs);
20 | }
21 |
22 | @Test
23 | public void test2() throws Exception {
24 | ExcelLoader loader = new ExcelLoader(ResourceUtil.getResource("demo.xls"));
25 | List docs = loader.load();
26 | System.out.println(docs);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/solon-ai-rag-loaders/solon-ai-load-excel/src/test/resources/demo.xls:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/opensolon/solon-ai/0ae6af88f88b623c6ea7fdc324b06e061a5e8fcd/solon-ai-rag-loaders/solon-ai-load-excel/src/test/resources/demo.xls
--------------------------------------------------------------------------------
/solon-ai-rag-loaders/solon-ai-load-excel/src/test/resources/demo.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/opensolon/solon-ai/0ae6af88f88b623c6ea7fdc324b06e061a5e8fcd/solon-ai-rag-loaders/solon-ai-load-excel/src/test/resources/demo.xlsx
--------------------------------------------------------------------------------
/solon-ai-rag-loaders/solon-ai-load-excel/src/test/resources/demo2.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/opensolon/solon-ai/0ae6af88f88b623c6ea7fdc324b06e061a5e8fcd/solon-ai-rag-loaders/solon-ai-load-excel/src/test/resources/demo2.xlsx
--------------------------------------------------------------------------------
/solon-ai-rag-loaders/solon-ai-load-html/src/test/java/features/ai/load/html/HtmlTest.java:
--------------------------------------------------------------------------------
1 | package features.ai.load.html;
2 |
3 | import java.net.URI;
4 | import java.util.Arrays;
5 | import java.util.HashMap;
6 | import java.util.List;
7 | import java.util.Map;
8 |
9 | import org.junit.jupiter.api.Assertions;
10 | import org.junit.jupiter.api.Test;
11 | import org.noear.solon.ai.rag.Document;
12 | import org.noear.solon.ai.rag.loader.HtmlSimpleLoader;
13 |
14 | /**
15 | * HtmlSimpleLoader 测试用例
16 | *
17 | * @author noear 2025/2/21 created
18 | */
19 | public class HtmlTest {
20 | private static final String TEST_URL = "https://solon.noear.org/article/about";
21 |
22 | @Test
23 | public void testSingleUrl() throws Exception {
24 | // 测试单个URL加载
25 | HtmlSimpleLoader loader = new HtmlSimpleLoader(URI.create(TEST_URL).toURL());
26 | List docs = loader.load();
27 |
28 | Assertions.assertFalse(docs.isEmpty(), "文档列表不应为空");
29 | Assertions.assertEquals(1, docs.size(), "应该只有一个文档");
30 |
31 | Document doc = docs.get(0);
32 | Assertions.assertNotNull(doc.getContent(), "文档内容不应为空");
33 | for (Document d : docs) {
34 | System.out.println(d.getContent());
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/solon-ai-rag-loaders/solon-ai-load-pdf/src/test/resources/PdfLoaderTest.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/opensolon/solon-ai/0ae6af88f88b623c6ea7fdc324b06e061a5e8fcd/solon-ai-rag-loaders/solon-ai-load-pdf/src/test/resources/PdfLoaderTest.pdf
--------------------------------------------------------------------------------
/solon-ai-rag-loaders/solon-ai-load-pdf/src/test/resources/PdfWithImg.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/opensolon/solon-ai/0ae6af88f88b623c6ea7fdc324b06e061a5e8fcd/solon-ai-rag-loaders/solon-ai-load-pdf/src/test/resources/PdfWithImg.pdf
--------------------------------------------------------------------------------
/solon-ai-rag-loaders/solon-ai-load-ppt/src/test/java/features/ai/load/ppt/PptLoaderTest.java:
--------------------------------------------------------------------------------
1 | package features.ai.load.ppt;
2 |
3 | import org.junit.jupiter.api.Test;
4 | import org.noear.solon.ai.rag.Document;
5 | import org.noear.solon.ai.rag.loader.PptLoader;
6 | import org.noear.solon.core.util.ResourceUtil;
7 | import org.slf4j.Logger;
8 | import org.slf4j.LoggerFactory;
9 |
10 | import java.util.List;
11 |
12 | public class PptLoaderTest {
13 | private static final Logger log = LoggerFactory.getLogger(PptLoaderTest.class);
14 |
15 | @Test
16 | public void test1() throws Exception {
17 | PptLoader loader = new PptLoader(ResourceUtil.getResource("demo.pptx"))
18 | .options(opt -> opt.loadMode(PptLoader.LoadMode.PAGE));
19 | List docs = loader.load();
20 | System.out.println(docs);
21 | assert docs.size() == 2;
22 | }
23 |
24 | @Test
25 | public void test2() throws Exception {
26 | PptLoader loader = new PptLoader(ResourceUtil.getResource("demo.ppt"))
27 | .options(opt -> opt.loadMode(PptLoader.LoadMode.PAGE));
28 | List docs = loader.load();
29 | System.out.println(docs);
30 | assert docs.size() == 2;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/solon-ai-rag-loaders/solon-ai-load-ppt/src/test/resources/demo.ppt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/opensolon/solon-ai/0ae6af88f88b623c6ea7fdc324b06e061a5e8fcd/solon-ai-rag-loaders/solon-ai-load-ppt/src/test/resources/demo.ppt
--------------------------------------------------------------------------------
/solon-ai-rag-loaders/solon-ai-load-ppt/src/test/resources/demo.pptx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/opensolon/solon-ai/0ae6af88f88b623c6ea7fdc324b06e061a5e8fcd/solon-ai-rag-loaders/solon-ai-load-ppt/src/test/resources/demo.pptx
--------------------------------------------------------------------------------
/solon-ai-rag-loaders/solon-ai-load-word/src/test/java/features/ai/load/word/WordLoaderTest.java:
--------------------------------------------------------------------------------
1 | package features.ai.load.word;
2 |
3 | import org.junit.jupiter.api.Test;
4 | import org.noear.solon.ai.rag.Document;
5 | import org.noear.solon.ai.rag.loader.WordLoader;
6 | import org.noear.solon.core.util.ResourceUtil;
7 | import org.slf4j.Logger;
8 | import org.slf4j.LoggerFactory;
9 |
10 | import java.util.List;
11 |
12 | public class WordLoaderTest {
13 | private static final Logger log = LoggerFactory.getLogger(WordLoaderTest.class);
14 |
15 | @Test
16 | public void test1() throws Exception {
17 | WordLoader loader = new WordLoader(ResourceUtil.getResource("demo.docx"))
18 | .options(opt -> opt.loadMode(WordLoader.LoadMode.PARAGRAPH));
19 | List docs = loader.load();
20 | System.out.println(docs);
21 | assert docs.size() > 0;
22 | }
23 |
24 | @Test
25 | public void test2() throws Exception {
26 | WordLoader loader = new WordLoader(ResourceUtil.getResource("demo.doc"))
27 | .options(opt -> opt.loadMode(WordLoader.LoadMode.PARAGRAPH));
28 | List docs = loader.load();
29 | System.out.println(docs);
30 | assert docs.size() > 0;
31 | }
32 | }
--------------------------------------------------------------------------------
/solon-ai-rag-loaders/solon-ai-load-word/src/test/resources/demo.doc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/opensolon/solon-ai/0ae6af88f88b623c6ea7fdc324b06e061a5e8fcd/solon-ai-rag-loaders/solon-ai-load-word/src/test/resources/demo.doc
--------------------------------------------------------------------------------
/solon-ai-rag-loaders/solon-ai-load-word/src/test/resources/demo.docx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/opensolon/solon-ai/0ae6af88f88b623c6ea7fdc324b06e061a5e8fcd/solon-ai-rag-loaders/solon-ai-load-word/src/test/resources/demo.docx
--------------------------------------------------------------------------------
/solon-ai-rag-repositorys/solon-ai-repo-chroma/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3.5'
2 |
3 | networks:
4 | net:
5 | driver: bridge
6 | services:
7 | chromadb:
8 | image: chromadb/chroma:0.6.3
9 | volumes:
10 | - ./chromadb:/chroma/chroma
11 | environment:
12 | - IS_PERSISTENT=TRUE
13 | - PERSIST_DIRECTORY=/chroma/chroma # this is the default path, change it as needed
14 | - ANONYMIZED_TELEMETRY=${ANONYMIZED_TELEMETRY:-TRUE}
15 | ports:
16 | - 8000:8000
17 | networks:
18 | - net
--------------------------------------------------------------------------------
/solon-ai-rag-repositorys/solon-ai-repo-chroma/src/main/java/org/noear/solon/ai/rag/repository/chroma/ChromaResponse.java:
--------------------------------------------------------------------------------
1 | package org.noear.solon.ai.rag.repository.chroma;
2 |
3 | /**
4 | * Chroma 响应基类
5 | *
6 | * @author 小奶奶花生米
7 | * @since 3.1
8 | */
9 | public class ChromaResponse {
10 | private String error;
11 | private String message;
12 |
13 | public String getError() {
14 | return error;
15 | }
16 |
17 | public void setError(String error) {
18 | this.error = error;
19 | }
20 |
21 | public String getMessage() {
22 | return message;
23 | }
24 |
25 | public void setMessage(String message) {
26 | this.message = message;
27 | }
28 |
29 | public boolean hasError() {
30 | return error != null && !error.isEmpty();
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/solon-ai-rag-repositorys/solon-ai-repo-chroma/src/main/java/org/noear/solon/ai/rag/repository/chroma/CollectionsResponse.java:
--------------------------------------------------------------------------------
1 | package org.noear.solon.ai.rag.repository.chroma;
2 |
3 | import java.util.List;
4 | import java.util.Map;
5 |
6 | /**
7 | * Chroma 集合列表响应
8 | *
9 | * @author 小奶奶花生米
10 | * @since 3.1
11 | */
12 | public class CollectionsResponse extends ChromaResponse {
13 | private List