From 05d70eb8043b15f554eb864eb9731e1b803b6c89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E5=8F=AF=E6=AC=A3?= <2218887102@qq.com> Date: Mon, 24 Nov 2025 10:17:36 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E5=A2=9E=E5=8A=A0SSE=E5=AE=A2=E6=88=B7?= =?UTF-8?q?=E7=AB=AF=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../support/DefaultMcpClientFactory.java | 26 ++++++++++++++++++- .../support/DefaultMcpStreamableClient.java | 14 +++------- .../fel/tool/mcp/test/TestController.java | 12 +++++++-- .../fel/tool/mcp/client/McpClientFactory.java | 23 ++++++++++++++-- 4 files changed, 60 insertions(+), 15 deletions(-) diff --git a/framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/DefaultMcpClientFactory.java b/framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/DefaultMcpClientFactory.java index 330a31721..f5a93eb1f 100644 --- a/framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/DefaultMcpClientFactory.java +++ b/framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/DefaultMcpClientFactory.java @@ -6,6 +6,11 @@ package modelengine.fel.tool.mcp.client.support; +import com.fasterxml.jackson.databind.ObjectMapper; + +import io.modelcontextprotocol.client.transport.HttpClientSseClientTransport; +import io.modelcontextprotocol.client.transport.HttpClientStreamableHttpTransport; +import io.modelcontextprotocol.json.jackson.JacksonMcpJsonMapper; import modelengine.fel.tool.mcp.client.McpClient; import modelengine.fel.tool.mcp.client.McpClientFactory; import modelengine.fitframework.annotation.Component; @@ -32,7 +37,26 @@ public DefaultMcpClientFactory(@Value("${mcp.client.request.timeout-seconds}") i } @Override + public McpClient createStreamable(String baseUri, String sseEndpoint) { + HttpClientStreamableHttpTransport transport = HttpClientStreamableHttpTransport.builder(baseUri) + .jsonMapper(new JacksonMcpJsonMapper(new ObjectMapper())) + .endpoint(sseEndpoint) + .build(); + return new DefaultMcpStreamableClient(baseUri, sseEndpoint, this.requestTimeoutSeconds, transport); + } + + @Override + public McpClient createSse(String baseUri, String sseEndpoint) { + HttpClientSseClientTransport transport = HttpClientSseClientTransport.builder(baseUri) + .jsonMapper(new JacksonMcpJsonMapper(new ObjectMapper())) + .sseEndpoint(sseEndpoint) + .build(); + return new DefaultMcpStreamableClient(baseUri, sseEndpoint, this.requestTimeoutSeconds, transport); + } + + @Override + @Deprecated public McpClient create(String baseUri, String sseEndpoint) { - return new DefaultMcpStreamableClient(baseUri, sseEndpoint, requestTimeoutSeconds); + return this.createStreamable(baseUri, sseEndpoint); } } diff --git a/framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/DefaultMcpStreamableClient.java b/framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/DefaultMcpStreamableClient.java index 44bca8478..434e3021e 100644 --- a/framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/DefaultMcpStreamableClient.java +++ b/framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/DefaultMcpStreamableClient.java @@ -11,9 +11,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.modelcontextprotocol.client.McpSyncClient; -import io.modelcontextprotocol.client.transport.HttpClientStreamableHttpTransport; -import io.modelcontextprotocol.json.jackson.JacksonMcpJsonMapper; import io.modelcontextprotocol.json.schema.jackson.DefaultJsonSchemaValidator; +import io.modelcontextprotocol.spec.McpClientTransport; import io.modelcontextprotocol.spec.McpSchema; import modelengine.fel.tool.mcp.client.McpClient; import modelengine.fel.tool.mcp.entity.Tool; @@ -54,23 +53,18 @@ public class DefaultMcpStreamableClient implements McpClient { * @param sseEndpoint The endpoint for the Server-Sent Events (SSE) connection. * @param requestTimeoutSeconds The timeout duration of requests. Units: seconds. */ - public DefaultMcpStreamableClient(String baseUri, String sseEndpoint, int requestTimeoutSeconds) { + public DefaultMcpStreamableClient(String baseUri, String sseEndpoint, int requestTimeoutSeconds, + McpClientTransport transport) { this.clientId = UuidUtils.randomUuidString(); notBlank(baseUri, "The MCP server base URI cannot be blank."); notBlank(sseEndpoint, "The MCP server SSE endpoint cannot be blank."); log.info("Creating MCP client. [clientId={}, baseUri={}]", this.clientId, baseUri); - ObjectMapper mapper = new ObjectMapper(); - HttpClientStreamableHttpTransport transport = HttpClientStreamableHttpTransport.builder(baseUri) - .jsonMapper(new JacksonMcpJsonMapper(mapper)) - .endpoint(sseEndpoint) - .build(); - this.logHandler = new DefaultMcpClientLogHandler(this.clientId); this.mcpSyncClient = io.modelcontextprotocol.client.McpClient.sync(transport) .requestTimeout(Duration.ofSeconds(requestTimeoutSeconds)) .capabilities(McpSchema.ClientCapabilities.builder().build()) .loggingConsumer(this.logHandler::handleLoggingMessage) - .jsonSchemaValidator(new DefaultJsonSchemaValidator(mapper)) + .jsonSchemaValidator(new DefaultJsonSchemaValidator(new ObjectMapper())) .build(); } diff --git a/framework/fel/java/plugins/tool-mcp-test/src/main/java/modelengine/fel/tool/mcp/test/TestController.java b/framework/fel/java/plugins/tool-mcp-test/src/main/java/modelengine/fel/tool/mcp/test/TestController.java index 110dc3cb6..9e5e52be4 100644 --- a/framework/fel/java/plugins/tool-mcp-test/src/main/java/modelengine/fel/tool/mcp/test/TestController.java +++ b/framework/fel/java/plugins/tool-mcp-test/src/main/java/modelengine/fel/tool/mcp/test/TestController.java @@ -51,9 +51,17 @@ public TestController(McpClientFactory mcpClientFactory) { * @return A string indicating that the initialization was successful. */ @PostMapping(path = "/initialize") - public String initialize(@RequestQuery(name = "baseUri") String baseUri, + public String initializeStreamable(@RequestQuery(name = "baseUri") String baseUri, @RequestQuery(name = "sseEndpoint") String sseEndpoint) { - this.client = this.mcpClientFactory.create(baseUri, sseEndpoint); + this.client = this.mcpClientFactory.createStreamable(baseUri, sseEndpoint); + this.client.initialize(); + return "Initialized"; + } + + @PostMapping(path = "/initialize-sse") + public String initializeSse(@RequestQuery(name = "baseUri") String baseUri, + @RequestQuery(name = "sseEndpoint") String sseEndpoint) { + this.client = this.mcpClientFactory.createSse(baseUri, sseEndpoint); this.client.initialize(); return "Initialized"; } diff --git a/framework/fel/java/services/tool-mcp-client-service/src/main/java/modelengine/fel/tool/mcp/client/McpClientFactory.java b/framework/fel/java/services/tool-mcp-client-service/src/main/java/modelengine/fel/tool/mcp/client/McpClientFactory.java index d52c639da..56bc7310c 100644 --- a/framework/fel/java/services/tool-mcp-client-service/src/main/java/modelengine/fel/tool/mcp/client/McpClientFactory.java +++ b/framework/fel/java/services/tool-mcp-client-service/src/main/java/modelengine/fel/tool/mcp/client/McpClientFactory.java @@ -16,11 +16,30 @@ */ public interface McpClientFactory { /** - * Creates a {@link McpClient} instance. + * Creates a {@link McpClient} instance with streamable HTTP transport. * * @param baseUri The base URI of the MCP server. * @param sseEndpoint The SSE endpoint of the MCP server. * @return The connected {@link McpClient} instance. */ - McpClient create(String baseUri, String sseEndpoint); + public McpClient createStreamable(String baseUri, String sseEndpoint); + + /** + * Creates a {@link McpClient} instance with SSE transport. + * + * @param baseUri The base URI of the MCP server. + * @param sseEndpoint The SSE endpoint of the MCP server. + * @return The connected {@link McpClient} instance. + */ + public McpClient createSse(String baseUri, String sseEndpoint); + + /** + * Creates a {@link McpClient} instance with streamable HTTP transport. + * + * @param baseUri The base URI of the MCP server. + * @param sseEndpoint The SSE endpoint of the MCP server. + * @return The connected {@link McpClient} instance. + */ + @Deprecated + public McpClient create(String baseUri, String sseEndpoint); } \ No newline at end of file From 41e04ade9d5a9aa5511c96f3664a56b965350b1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E5=8F=AF=E6=AC=A3?= <2218887102@qq.com> Date: Wed, 26 Nov 2025 09:15:48 +0800 Subject: [PATCH 2/4] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=AE=A2=E6=88=B7?= =?UTF-8?q?=E7=AB=AFelicitation=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...mableClient.java => DefaultMcpClient.java} | 43 +++++++---- .../support/DefaultMcpClientFactory.java | 19 +++-- .../DefaultMcpClientLogHandler.java | 2 +- .../handler/DefaultMcpElicitationHandler.java | 77 +++++++++++++++++++ .../fel/tool/mcp/test/TestController.java | 16 +++- .../fel/tool/mcp/client/McpClientFactory.java | 35 ++++++--- .../mcp/client/elicitation/ElicitRequest.java | 20 +++++ .../mcp/client/elicitation/ElicitResult.java | 29 +++++++ 8 files changed, 208 insertions(+), 33 deletions(-) rename framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/{DefaultMcpStreamableClient.java => DefaultMcpClient.java} (84%) rename framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/{ => handler}/DefaultMcpClientLogHandler.java (96%) create mode 100644 framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/handler/DefaultMcpElicitationHandler.java create mode 100644 framework/fel/java/services/tool-mcp-client-service/src/main/java/modelengine/fel/tool/mcp/client/elicitation/ElicitRequest.java create mode 100644 framework/fel/java/services/tool-mcp-client-service/src/main/java/modelengine/fel/tool/mcp/client/elicitation/ElicitResult.java diff --git a/framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/DefaultMcpStreamableClient.java b/framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/DefaultMcpClient.java similarity index 84% rename from framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/DefaultMcpStreamableClient.java rename to framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/DefaultMcpClient.java index 434e3021e..7723664f2 100644 --- a/framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/DefaultMcpStreamableClient.java +++ b/framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/DefaultMcpClient.java @@ -15,7 +15,12 @@ import io.modelcontextprotocol.spec.McpClientTransport; import io.modelcontextprotocol.spec.McpSchema; import modelengine.fel.tool.mcp.client.McpClient; +import modelengine.fel.tool.mcp.client.elicitation.ElicitRequest; +import modelengine.fel.tool.mcp.client.elicitation.ElicitResult; +import modelengine.fel.tool.mcp.client.support.handler.DefaultMcpClientLogHandler; +import modelengine.fel.tool.mcp.client.support.handler.DefaultMcpElicitationHandler; import modelengine.fel.tool.mcp.entity.Tool; +import modelengine.fitframework.inspection.Nullable; import modelengine.fitframework.log.Logger; import modelengine.fitframework.util.StringUtils; import modelengine.fitframework.util.UuidUtils; @@ -25,6 +30,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Function; import java.util.stream.Collectors; /** @@ -36,36 +42,47 @@ * @author 黄可欣 * @since 2025-11-03 */ -public class DefaultMcpStreamableClient implements McpClient { - private static final Logger log = Logger.get(DefaultMcpStreamableClient.class); +public class DefaultMcpClient implements McpClient { + private static final Logger log = Logger.get(DefaultMcpClient.class); private final String clientId; private final McpSyncClient mcpSyncClient; - private final DefaultMcpClientLogHandler logHandler; private volatile boolean initialized = false; private volatile boolean closed = false; /** - * Constructs a new instance of the DefaultMcpStreamableClient. + * Constructs a new instance of the DefaultMcpClient. * * @param baseUri The base URI of the MCP server. * @param sseEndpoint The endpoint for the Server-Sent Events (SSE) connection. * @param requestTimeoutSeconds The timeout duration of requests. Units: seconds. */ - public DefaultMcpStreamableClient(String baseUri, String sseEndpoint, int requestTimeoutSeconds, - McpClientTransport transport) { + public DefaultMcpClient(String baseUri, String sseEndpoint, McpClientTransport transport, int requestTimeoutSeconds, + @Nullable Function elicitationHandler) { this.clientId = UuidUtils.randomUuidString(); notBlank(baseUri, "The MCP server base URI cannot be blank."); notBlank(sseEndpoint, "The MCP server SSE endpoint cannot be blank."); log.info("Creating MCP client. [clientId={}, baseUri={}]", this.clientId, baseUri); - this.logHandler = new DefaultMcpClientLogHandler(this.clientId); - this.mcpSyncClient = io.modelcontextprotocol.client.McpClient.sync(transport) - .requestTimeout(Duration.ofSeconds(requestTimeoutSeconds)) - .capabilities(McpSchema.ClientCapabilities.builder().build()) - .loggingConsumer(this.logHandler::handleLoggingMessage) - .jsonSchemaValidator(new DefaultJsonSchemaValidator(new ObjectMapper())) - .build(); + DefaultMcpClientLogHandler logHandler = new DefaultMcpClientLogHandler(this.clientId); + if (elicitationHandler != null) { + DefaultMcpElicitationHandler mcpElicitationHandler = + new DefaultMcpElicitationHandler(this.clientId, elicitationHandler); + this.mcpSyncClient = io.modelcontextprotocol.client.McpClient.sync(transport) + .capabilities(McpSchema.ClientCapabilities.builder().elicitation().build()) + .loggingConsumer(logHandler::handleLoggingMessage) + .elicitation(mcpElicitationHandler::handleElicitationRequest) + .requestTimeout(Duration.ofSeconds(requestTimeoutSeconds)) + .jsonSchemaValidator(new DefaultJsonSchemaValidator(new ObjectMapper())) + .build(); + } else { + this.mcpSyncClient = io.modelcontextprotocol.client.McpClient.sync(transport) + .capabilities(McpSchema.ClientCapabilities.builder().build()) + .loggingConsumer(logHandler::handleLoggingMessage) + .requestTimeout(Duration.ofSeconds(requestTimeoutSeconds)) + .jsonSchemaValidator(new DefaultJsonSchemaValidator(new ObjectMapper())) + .build(); + } } @Override diff --git a/framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/DefaultMcpClientFactory.java b/framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/DefaultMcpClientFactory.java index f5a93eb1f..32ec0bd78 100644 --- a/framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/DefaultMcpClientFactory.java +++ b/framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/DefaultMcpClientFactory.java @@ -13,11 +13,16 @@ import io.modelcontextprotocol.json.jackson.JacksonMcpJsonMapper; import modelengine.fel.tool.mcp.client.McpClient; import modelengine.fel.tool.mcp.client.McpClientFactory; +import modelengine.fel.tool.mcp.client.elicitation.ElicitRequest; +import modelengine.fel.tool.mcp.client.elicitation.ElicitResult; import modelengine.fitframework.annotation.Component; import modelengine.fitframework.annotation.Value; +import modelengine.fitframework.inspection.Nullable; + +import java.util.function.Function; /** - * Represents a factory for creating instances of the {@link DefaultMcpStreamableClient}. + * Represents a factory for creating instances of the {@link DefaultMcpClient}. * This class is responsible for initializing and configuring. * * @author 季聿阶 @@ -37,26 +42,28 @@ public DefaultMcpClientFactory(@Value("${mcp.client.request.timeout-seconds}") i } @Override - public McpClient createStreamable(String baseUri, String sseEndpoint) { + public McpClient createStreamable(String baseUri, String sseEndpoint, + @Nullable Function elicitationHandler) { HttpClientStreamableHttpTransport transport = HttpClientStreamableHttpTransport.builder(baseUri) .jsonMapper(new JacksonMcpJsonMapper(new ObjectMapper())) .endpoint(sseEndpoint) .build(); - return new DefaultMcpStreamableClient(baseUri, sseEndpoint, this.requestTimeoutSeconds, transport); + return new DefaultMcpClient(baseUri, sseEndpoint, transport, this.requestTimeoutSeconds, elicitationHandler); } @Override - public McpClient createSse(String baseUri, String sseEndpoint) { + public McpClient createSse(String baseUri, String sseEndpoint, + @Nullable Function elicitationHandler) { HttpClientSseClientTransport transport = HttpClientSseClientTransport.builder(baseUri) .jsonMapper(new JacksonMcpJsonMapper(new ObjectMapper())) .sseEndpoint(sseEndpoint) .build(); - return new DefaultMcpStreamableClient(baseUri, sseEndpoint, this.requestTimeoutSeconds, transport); + return new DefaultMcpClient(baseUri, sseEndpoint, transport, this.requestTimeoutSeconds, elicitationHandler); } @Override @Deprecated public McpClient create(String baseUri, String sseEndpoint) { - return this.createStreamable(baseUri, sseEndpoint); + return this.createStreamable(baseUri, sseEndpoint, null); } } diff --git a/framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/DefaultMcpClientLogHandler.java b/framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/handler/DefaultMcpClientLogHandler.java similarity index 96% rename from framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/DefaultMcpClientLogHandler.java rename to framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/handler/DefaultMcpClientLogHandler.java index bffb88df3..fa1b9de92 100644 --- a/framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/DefaultMcpClientLogHandler.java +++ b/framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/handler/DefaultMcpClientLogHandler.java @@ -4,7 +4,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -package modelengine.fel.tool.mcp.client.support; +package modelengine.fel.tool.mcp.client.support.handler; import io.modelcontextprotocol.spec.McpSchema; import modelengine.fitframework.log.Logger; diff --git a/framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/handler/DefaultMcpElicitationHandler.java b/framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/handler/DefaultMcpElicitationHandler.java new file mode 100644 index 000000000..0b468171f --- /dev/null +++ b/framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/handler/DefaultMcpElicitationHandler.java @@ -0,0 +1,77 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +package modelengine.fel.tool.mcp.client.support.handler; + +import io.modelcontextprotocol.spec.McpSchema; +import modelengine.fel.tool.mcp.client.elicitation.ElicitRequest; +import modelengine.fel.tool.mcp.client.elicitation.ElicitResult; +import modelengine.fitframework.log.Logger; + +import java.util.function.Function; + +/** + * Default MCP elicitation handler that delegates to an external handler function. + * + *

Converts {@link McpSchema.ElicitRequest} to {@link ElicitRequest}, + * calls the user's handler, and converts {@link ElicitResult} back to {@link McpSchema.ElicitResult}. + * + * @author 黄可欣 + * @since 2025-11-25 + */ +public class DefaultMcpElicitationHandler { + private static final Logger log = Logger.get(DefaultMcpElicitationHandler.class); + private final String clientId; + private final Function elicitationHandler; + + /** + * Constructs a new handler. + * + * @param clientId The client ID. + * @param elicitationHandler The user's handler function that processes {@link ElicitRequest} + * and returns {@link ElicitResult}. + */ + public DefaultMcpElicitationHandler(String clientId, Function elicitationHandler) { + this.clientId = clientId; + this.elicitationHandler = elicitationHandler; + } + + /** + * Handles an elicitation request by converting {@link McpSchema.ElicitRequest} to {@link ElicitRequest}, + * delegating to the user's handler, and converting {@link ElicitResult} back to {@link McpSchema.ElicitResult}. + * + * @param request The {@link McpSchema.ElicitRequest} from MCP server. + * @return The {@link McpSchema.ElicitResult} to send back to MCP server. + */ + public McpSchema.ElicitResult handleElicitationRequest(McpSchema.ElicitRequest request) { + log.info("Received elicitation request from MCP server. [clientId={}, message={}, requestSchema={}]", + this.clientId, + request.message(), + request.requestedSchema()); + + try { + ElicitRequest elicitRequest = new ElicitRequest(request.message(), request.requestedSchema()); + ElicitResult result = this.elicitationHandler.apply(elicitRequest); + log.info("Successfully handled elicitation request. [clientId={}, action={}, content={}]", + this.clientId, + result.action(), + result.content()); + + McpSchema.ElicitResult.Action mcpAction = switch (result.action()) { + case ACCEPT -> McpSchema.ElicitResult.Action.ACCEPT; + case DECLINE -> McpSchema.ElicitResult.Action.DECLINE; + case CANCEL -> McpSchema.ElicitResult.Action.CANCEL; + }; + return new McpSchema.ElicitResult(mcpAction, result.content()); + } catch (Exception e) { + log.error("Failed to handle elicitation request. [clientId={}, error={}]", + this.clientId, + e.getMessage(), + e); + throw new IllegalStateException("Failed to handle elicitation request: " + e.getMessage(), e); + } + } +} diff --git a/framework/fel/java/plugins/tool-mcp-test/src/main/java/modelengine/fel/tool/mcp/test/TestController.java b/framework/fel/java/plugins/tool-mcp-test/src/main/java/modelengine/fel/tool/mcp/test/TestController.java index 9e5e52be4..e32ca9a17 100644 --- a/framework/fel/java/plugins/tool-mcp-test/src/main/java/modelengine/fel/tool/mcp/test/TestController.java +++ b/framework/fel/java/plugins/tool-mcp-test/src/main/java/modelengine/fel/tool/mcp/test/TestController.java @@ -8,6 +8,7 @@ import modelengine.fel.tool.mcp.client.McpClient; import modelengine.fel.tool.mcp.client.McpClientFactory; +import modelengine.fel.tool.mcp.client.elicitation.ElicitResult; import modelengine.fel.tool.mcp.entity.Tool; import modelengine.fit.http.annotation.GetMapping; import modelengine.fit.http.annotation.PostMapping; @@ -17,6 +18,7 @@ import modelengine.fitframework.annotation.Component; import java.io.IOException; +import java.util.Collections; import java.util.List; import java.util.Map; @@ -53,7 +55,7 @@ public TestController(McpClientFactory mcpClientFactory) { @PostMapping(path = "/initialize") public String initializeStreamable(@RequestQuery(name = "baseUri") String baseUri, @RequestQuery(name = "sseEndpoint") String sseEndpoint) { - this.client = this.mcpClientFactory.createStreamable(baseUri, sseEndpoint); + this.client = this.mcpClientFactory.createStreamable(baseUri, sseEndpoint, null); this.client.initialize(); return "Initialized"; } @@ -61,7 +63,17 @@ public String initializeStreamable(@RequestQuery(name = "baseUri") String baseUr @PostMapping(path = "/initialize-sse") public String initializeSse(@RequestQuery(name = "baseUri") String baseUri, @RequestQuery(name = "sseEndpoint") String sseEndpoint) { - this.client = this.mcpClientFactory.createSse(baseUri, sseEndpoint); + this.client = this.mcpClientFactory.createSse(baseUri, sseEndpoint, null); + this.client.initialize(); + return "Initialized"; + } + + @PostMapping(path = "/initialize-elicitation") + public String initializeElicitation(@RequestQuery(name = "baseUri") String baseUri, + @RequestQuery(name = "sseEndpoint") String sseEndpoint) { + this.client = this.mcpClientFactory.createStreamable(baseUri, + sseEndpoint, + request -> new ElicitResult(ElicitResult.Action.ACCEPT, Collections.emptyMap())); this.client.initialize(); return "Initialized"; } diff --git a/framework/fel/java/services/tool-mcp-client-service/src/main/java/modelengine/fel/tool/mcp/client/McpClientFactory.java b/framework/fel/java/services/tool-mcp-client-service/src/main/java/modelengine/fel/tool/mcp/client/McpClientFactory.java index 56bc7310c..33c3e48e6 100644 --- a/framework/fel/java/services/tool-mcp-client-service/src/main/java/modelengine/fel/tool/mcp/client/McpClientFactory.java +++ b/framework/fel/java/services/tool-mcp-client-service/src/main/java/modelengine/fel/tool/mcp/client/McpClientFactory.java @@ -6,39 +6,52 @@ package modelengine.fel.tool.mcp.client; +import modelengine.fel.tool.mcp.client.elicitation.ElicitRequest; +import modelengine.fel.tool.mcp.client.elicitation.ElicitResult; +import modelengine.fitframework.inspection.Nullable; + +import java.util.function.Function; + /** - * Indicates the factory of {@link McpClient}. - *

- * Each {@link McpClient} instance created by this factory is designed to connect to a single specified MCP server. + * Factory for creating {@link McpClient} instances. + * + *

Each client connects to a single MCP server. * * @author 季聿阶 * @since 2025-05-21 */ public interface McpClientFactory { /** - * Creates a {@link McpClient} instance with streamable HTTP transport. + * Creates a client with streamable HTTP transport. * * @param baseUri The base URI of the MCP server. * @param sseEndpoint The SSE endpoint of the MCP server. - * @return The connected {@link McpClient} instance. + * @param elicitationFunction The function to handle {@link ElicitRequest} and return {@link ElicitResult}. + * If null, elicitation will not be supported in MCP client. + * @return The created {@link McpClient} instance. */ - public McpClient createStreamable(String baseUri, String sseEndpoint); + public McpClient createStreamable(String baseUri, String sseEndpoint, + @Nullable Function elicitationFunction); /** - * Creates a {@link McpClient} instance with SSE transport. + * Creates a client with SSE transport. * * @param baseUri The base URI of the MCP server. * @param sseEndpoint The SSE endpoint of the MCP server. - * @return The connected {@link McpClient} instance. + * @param elicitationFunction The function to handle {@link ElicitRequest} and return {@link ElicitResult}. + * If null, elicitation will not be supported in MCP client. + * @return The created {@link McpClient} instance. */ - public McpClient createSse(String baseUri, String sseEndpoint); + public McpClient createSse(String baseUri, String sseEndpoint, + @Nullable Function elicitationFunction); /** - * Creates a {@link McpClient} instance with streamable HTTP transport. + * Creates a client with streamable HTTP transport. * * @param baseUri The base URI of the MCP server. * @param sseEndpoint The SSE endpoint of the MCP server. - * @return The connected {@link McpClient} instance. + * @return The created {@link McpClient} instance. + * @deprecated Use {@link #createStreamable(String, String, Function)} instead. */ @Deprecated public McpClient create(String baseUri, String sseEndpoint); diff --git a/framework/fel/java/services/tool-mcp-client-service/src/main/java/modelengine/fel/tool/mcp/client/elicitation/ElicitRequest.java b/framework/fel/java/services/tool-mcp-client-service/src/main/java/modelengine/fel/tool/mcp/client/elicitation/ElicitRequest.java new file mode 100644 index 000000000..36e48f418 --- /dev/null +++ b/framework/fel/java/services/tool-mcp-client-service/src/main/java/modelengine/fel/tool/mcp/client/elicitation/ElicitRequest.java @@ -0,0 +1,20 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +package modelengine.fel.tool.mcp.client.elicitation; + +import java.util.Map; + +/** + * Represents an elicitation request from an MCP server. + * This is a simplified version that doesn't depend on MCP SDK types. + * + * @param message The message describing what information is needed from the user + * @param requestedSchema The JSON schema defining the expected data structure + * @author 黄可欣 + * @since 2025-11-25 + */ +public record ElicitRequest(String message, Map requestedSchema) {} diff --git a/framework/fel/java/services/tool-mcp-client-service/src/main/java/modelengine/fel/tool/mcp/client/elicitation/ElicitResult.java b/framework/fel/java/services/tool-mcp-client-service/src/main/java/modelengine/fel/tool/mcp/client/elicitation/ElicitResult.java new file mode 100644 index 000000000..c39f43945 --- /dev/null +++ b/framework/fel/java/services/tool-mcp-client-service/src/main/java/modelengine/fel/tool/mcp/client/elicitation/ElicitResult.java @@ -0,0 +1,29 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +package modelengine.fel.tool.mcp.client.elicitation; + +import java.util.Map; + +/** + * Represents the result of handling an elicitation request. + * This is a simplified version that doesn't depend on MCP SDK types. + * + * @param action The action to take + * @param content The user-provided data matching the requested schema + * @author 黄可欣 + * @since 2025-11-25 + */ +public record ElicitResult(Action action, Map content) { + /** + * Action types for elicitation results. + */ + public enum Action { + ACCEPT, + DECLINE, + CANCEL + } +} From da4df0c7d4a2bfe28de379093f04c8f2a3742514 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E5=8F=AF=E6=AC=A3?= <2218887102@qq.com> Date: Wed, 26 Nov 2025 10:20:52 +0800 Subject: [PATCH 3/4] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../modelengine/fel/tool/mcp/client/McpClientFactory.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/framework/fel/java/services/tool-mcp-client-service/src/main/java/modelengine/fel/tool/mcp/client/McpClientFactory.java b/framework/fel/java/services/tool-mcp-client-service/src/main/java/modelengine/fel/tool/mcp/client/McpClientFactory.java index 33c3e48e6..aad61c75d 100644 --- a/framework/fel/java/services/tool-mcp-client-service/src/main/java/modelengine/fel/tool/mcp/client/McpClientFactory.java +++ b/framework/fel/java/services/tool-mcp-client-service/src/main/java/modelengine/fel/tool/mcp/client/McpClientFactory.java @@ -13,9 +13,8 @@ import java.util.function.Function; /** - * Factory for creating {@link McpClient} instances. - * - *

Each client connects to a single MCP server. + * Factory for creating {@link McpClient} instances with SSE or Streamable HTTP transport. + *

Each client connects to a single MCP server.

* * @author 季聿阶 * @since 2025-05-21 From 525f75157e71670477da9d01e482d1d589ea6193 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E5=8F=AF=E6=AC=A3?= <2218887102@qq.com> Date: Thu, 27 Nov 2025 10:02:26 +0800 Subject: [PATCH 4/4] =?UTF-8?q?=E5=AE=8C=E5=96=84client=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mcp/client/support/DefaultMcpClient.java | 10 +++--- .../support/DefaultMcpClientFactory.java | 6 ---- ...gHandler.java => McpClientLogHandler.java} | 8 ++--- ...andler.java => McpElicitationHandler.java} | 10 +++--- .../fel/tool/mcp/client/McpClientFactory.java | 34 +++++++++++++++---- .../mcp/client/elicitation/ElicitRequest.java | 7 ++-- .../mcp/client/elicitation/ElicitResult.java | 18 ++++++++-- 7 files changed, 63 insertions(+), 30 deletions(-) rename framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/handler/{DefaultMcpClientLogHandler.java => McpClientLogHandler.java} (85%) rename framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/handler/{DefaultMcpElicitationHandler.java => McpElicitationHandler.java} (89%) diff --git a/framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/DefaultMcpClient.java b/framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/DefaultMcpClient.java index 7723664f2..311240074 100644 --- a/framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/DefaultMcpClient.java +++ b/framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/DefaultMcpClient.java @@ -17,8 +17,8 @@ import modelengine.fel.tool.mcp.client.McpClient; import modelengine.fel.tool.mcp.client.elicitation.ElicitRequest; import modelengine.fel.tool.mcp.client.elicitation.ElicitResult; -import modelengine.fel.tool.mcp.client.support.handler.DefaultMcpClientLogHandler; -import modelengine.fel.tool.mcp.client.support.handler.DefaultMcpElicitationHandler; +import modelengine.fel.tool.mcp.client.support.handler.McpClientLogHandler; +import modelengine.fel.tool.mcp.client.support.handler.McpElicitationHandler; import modelengine.fel.tool.mcp.entity.Tool; import modelengine.fitframework.inspection.Nullable; import modelengine.fitframework.log.Logger; @@ -64,10 +64,10 @@ public DefaultMcpClient(String baseUri, String sseEndpoint, McpClientTransport t notBlank(baseUri, "The MCP server base URI cannot be blank."); notBlank(sseEndpoint, "The MCP server SSE endpoint cannot be blank."); log.info("Creating MCP client. [clientId={}, baseUri={}]", this.clientId, baseUri); - DefaultMcpClientLogHandler logHandler = new DefaultMcpClientLogHandler(this.clientId); + McpClientLogHandler logHandler = new McpClientLogHandler(this.clientId); if (elicitationHandler != null) { - DefaultMcpElicitationHandler mcpElicitationHandler = - new DefaultMcpElicitationHandler(this.clientId, elicitationHandler); + McpElicitationHandler mcpElicitationHandler = + new McpElicitationHandler(this.clientId, elicitationHandler); this.mcpSyncClient = io.modelcontextprotocol.client.McpClient.sync(transport) .capabilities(McpSchema.ClientCapabilities.builder().elicitation().build()) .loggingConsumer(logHandler::handleLoggingMessage) diff --git a/framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/DefaultMcpClientFactory.java b/framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/DefaultMcpClientFactory.java index 32ec0bd78..68a648f38 100644 --- a/framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/DefaultMcpClientFactory.java +++ b/framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/DefaultMcpClientFactory.java @@ -60,10 +60,4 @@ public McpClient createSse(String baseUri, String sseEndpoint, .build(); return new DefaultMcpClient(baseUri, sseEndpoint, transport, this.requestTimeoutSeconds, elicitationHandler); } - - @Override - @Deprecated - public McpClient create(String baseUri, String sseEndpoint) { - return this.createStreamable(baseUri, sseEndpoint, null); - } } diff --git a/framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/handler/DefaultMcpClientLogHandler.java b/framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/handler/McpClientLogHandler.java similarity index 85% rename from framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/handler/DefaultMcpClientLogHandler.java rename to framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/handler/McpClientLogHandler.java index fa1b9de92..ae59e015c 100644 --- a/framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/handler/DefaultMcpClientLogHandler.java +++ b/framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/handler/McpClientLogHandler.java @@ -16,16 +16,16 @@ * @author 黄可欣 * @since 2025-11-03 */ -public class DefaultMcpClientLogHandler { - private static final Logger log = Logger.get(DefaultMcpClientLogHandler.class); +public class McpClientLogHandler { + private static final Logger log = Logger.get(McpClientLogHandler.class); private final String clientId; /** - * Constructs a new instance of DefaultMcpClientLogHandler. + * Constructs a new instance of McpClientLogHandler. * * @param clientId The unique identifier of the MCP client. */ - public DefaultMcpClientLogHandler(String clientId) { + public McpClientLogHandler(String clientId) { this.clientId = clientId; } diff --git a/framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/handler/DefaultMcpElicitationHandler.java b/framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/handler/McpElicitationHandler.java similarity index 89% rename from framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/handler/DefaultMcpElicitationHandler.java rename to framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/handler/McpElicitationHandler.java index 0b468171f..68976b9ce 100644 --- a/framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/handler/DefaultMcpElicitationHandler.java +++ b/framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/handler/McpElicitationHandler.java @@ -14,16 +14,16 @@ import java.util.function.Function; /** - * Default MCP elicitation handler that delegates to an external handler function. + * MCP elicitation handler that delegates to an external handler function. * *

Converts {@link McpSchema.ElicitRequest} to {@link ElicitRequest}, - * calls the user's handler, and converts {@link ElicitResult} back to {@link McpSchema.ElicitResult}. + * calls the user's handler, and converts {@link ElicitResult} back to {@link McpSchema.ElicitResult}.

* * @author 黄可欣 * @since 2025-11-25 */ -public class DefaultMcpElicitationHandler { - private static final Logger log = Logger.get(DefaultMcpElicitationHandler.class); +public class McpElicitationHandler { + private static final Logger log = Logger.get(McpElicitationHandler.class); private final String clientId; private final Function elicitationHandler; @@ -34,7 +34,7 @@ public class DefaultMcpElicitationHandler { * @param elicitationHandler The user's handler function that processes {@link ElicitRequest} * and returns {@link ElicitResult}. */ - public DefaultMcpElicitationHandler(String clientId, Function elicitationHandler) { + public McpElicitationHandler(String clientId, Function elicitationHandler) { this.clientId = clientId; this.elicitationHandler = elicitationHandler; } diff --git a/framework/fel/java/services/tool-mcp-client-service/src/main/java/modelengine/fel/tool/mcp/client/McpClientFactory.java b/framework/fel/java/services/tool-mcp-client-service/src/main/java/modelengine/fel/tool/mcp/client/McpClientFactory.java index aad61c75d..a6006ad2c 100644 --- a/framework/fel/java/services/tool-mcp-client-service/src/main/java/modelengine/fel/tool/mcp/client/McpClientFactory.java +++ b/framework/fel/java/services/tool-mcp-client-service/src/main/java/modelengine/fel/tool/mcp/client/McpClientFactory.java @@ -29,7 +29,7 @@ public interface McpClientFactory { * If null, elicitation will not be supported in MCP client. * @return The created {@link McpClient} instance. */ - public McpClient createStreamable(String baseUri, String sseEndpoint, + McpClient createStreamable(String baseUri, String sseEndpoint, @Nullable Function elicitationFunction); /** @@ -41,17 +41,39 @@ public McpClient createStreamable(String baseUri, String sseEndpoint, * If null, elicitation will not be supported in MCP client. * @return The created {@link McpClient} instance. */ - public McpClient createSse(String baseUri, String sseEndpoint, + McpClient createSse(String baseUri, String sseEndpoint, @Nullable Function elicitationFunction); /** - * Creates a client with streamable HTTP transport. + * Creates a client with streamable HTTP transport (default). No elicitation support. + * + * @param baseUri The base URI of the MCP server. + * @param sseEndpoint The SSE endpoint of the MCP server. + * @return The created {@link McpClient} instance. + */ + default McpClient create(String baseUri, String sseEndpoint) { + return this.createStreamable(baseUri, sseEndpoint, null); + } + + /** + * Creates a client with streamable HTTP transport. No elicitation support. + * + * @param baseUri The base URI of the MCP server. + * @param sseEndpoint The SSE endpoint of the MCP server. + * @return The created {@link McpClient} instance. + */ + default McpClient createStreamable(String baseUri, String sseEndpoint) { + return this.createStreamable(baseUri, sseEndpoint, null); + } + + /** + * Creates a client with SSE transport. No elicitation support. * * @param baseUri The base URI of the MCP server. * @param sseEndpoint The SSE endpoint of the MCP server. * @return The created {@link McpClient} instance. - * @deprecated Use {@link #createStreamable(String, String, Function)} instead. */ - @Deprecated - public McpClient create(String baseUri, String sseEndpoint); + default McpClient createSse(String baseUri, String sseEndpoint) { + return this.createSse(baseUri, sseEndpoint, null); + } } \ No newline at end of file diff --git a/framework/fel/java/services/tool-mcp-client-service/src/main/java/modelengine/fel/tool/mcp/client/elicitation/ElicitRequest.java b/framework/fel/java/services/tool-mcp-client-service/src/main/java/modelengine/fel/tool/mcp/client/elicitation/ElicitRequest.java index 36e48f418..9c63acac9 100644 --- a/framework/fel/java/services/tool-mcp-client-service/src/main/java/modelengine/fel/tool/mcp/client/elicitation/ElicitRequest.java +++ b/framework/fel/java/services/tool-mcp-client-service/src/main/java/modelengine/fel/tool/mcp/client/elicitation/ElicitRequest.java @@ -12,9 +12,12 @@ * Represents an elicitation request from an MCP server. * This is a simplified version that doesn't depend on MCP SDK types. * - * @param message The message describing what information is needed from the user - * @param requestedSchema The JSON schema defining the expected data structure + * @param message The {@link String} message describing what information is needed from the user. + * @param requestedSchema The {@link Map}{@code <}{@link String}{@code , }{@link Object}{@code >} JSON schema defining + * the elicitation request data structure. * @author 黄可欣 + * @see MCP + * Protocol * @since 2025-11-25 */ public record ElicitRequest(String message, Map requestedSchema) {} diff --git a/framework/fel/java/services/tool-mcp-client-service/src/main/java/modelengine/fel/tool/mcp/client/elicitation/ElicitResult.java b/framework/fel/java/services/tool-mcp-client-service/src/main/java/modelengine/fel/tool/mcp/client/elicitation/ElicitResult.java index c39f43945..ce8460b50 100644 --- a/framework/fel/java/services/tool-mcp-client-service/src/main/java/modelengine/fel/tool/mcp/client/elicitation/ElicitResult.java +++ b/framework/fel/java/services/tool-mcp-client-service/src/main/java/modelengine/fel/tool/mcp/client/elicitation/ElicitResult.java @@ -12,9 +12,12 @@ * Represents the result of handling an elicitation request. * This is a simplified version that doesn't depend on MCP SDK types. * - * @param action The action to take - * @param content The user-provided data matching the requested schema + * @param action The {@link ElicitResult.Action} to take in elicitation result. + * @param content The elicitation result {@link Map}{@code <}{@link String}{@code , }{@link Object}{@code >} data + * matching the requested schema. * @author 黄可欣 + * @see MCP + * Protocol * @since 2025-11-25 */ public record ElicitResult(Action action, Map content) { @@ -22,8 +25,19 @@ public record ElicitResult(Action action, Map content) { * Action types for elicitation results. */ public enum Action { + /** + * User explicitly approved and submitted with data. + */ ACCEPT, + + /** + * User explicitly declined the request. + */ DECLINE, + + /** + * User dismissed without making an explicit choice. + */ CANCEL } }