diff --git a/framework/fel/java/services/tool-mcp-client-service/src/main/java/modelengine/fel/tool/mcp/client/McpClient.java b/framework/fel/java/services/tool-mcp-client-service/src/main/java/modelengine/fel/tool/mcp/client/McpClient.java
index 6a50201b..98318b0a 100644
--- a/framework/fel/java/services/tool-mcp-client-service/src/main/java/modelengine/fel/tool/mcp/client/McpClient.java
+++ b/framework/fel/java/services/tool-mcp-client-service/src/main/java/modelengine/fel/tool/mcp/client/McpClient.java
@@ -22,6 +22,13 @@
* @since 2025-05-21
*/
public interface McpClient extends Closeable {
+ /**
+ * Gets the unique identifier of this MCP client instance.
+ *
+ * @return The client ID as a {@link String}.
+ */
+ String getClientId();
+
/**
* Initializes the MCP Client.
*/
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 d52c639d..c6dfc6fb 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,6 +6,11 @@
package modelengine.fel.tool.mcp.client;
+import io.modelcontextprotocol.spec.McpSchema;
+
+import java.util.function.Consumer;
+import java.util.function.Function;
+
/**
* Indicates the factory of {@link McpClient}.
*
@@ -16,11 +21,43 @@
*/
public interface McpClientFactory {
/**
- * Creates a {@link McpClient} instance.
+ * Creates a {@link McpClient} instance with default logging consumer but without elicitation ability.
*
* @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 connected {@link McpClient} instance with default logging consumer but without elicitation ability.
*/
McpClient create(String baseUri, String sseEndpoint);
+
+ /**
+ * Creates a {@link McpClient} instance with custom logging consumer but without elicitation ability.
+ *
+ * @param baseUri The base URI of the MCP server.
+ * @param sseEndpoint The SSE endpoint of the MCP server.
+ * @param loggingConsumer The consumer to handle logging messages from the MCP server.
+ * @return The connected {@link McpClient} instance with custom logging consumer but without elicitation ability.
+ */
+ McpClient create(String baseUri, String sseEndpoint, Consumer loggingConsumer);
+
+ /**
+ * Creates a {@link McpClient} instance with default logging consumer and elicitation ability.
+ *
+ * @param baseUri The base URI of the MCP server.
+ * @param sseEndpoint The SSE endpoint of the MCP server.
+ * @param elicitationHandler The function to handle elicitation requests from the MCP server.
+ * @return The connected {@link McpClient} instance with default logging consumer and elicitation ability.
+ */
+ McpClient create(String baseUri, String sseEndpoint, Function elicitationHandler);
+
+ /**
+ * Creates a {@link McpClient} instance with custom message handlers and elicitation ability.
+ *
+ * @param baseUri The base URI of the MCP server.
+ * @param sseEndpoint The SSE endpoint of the MCP server.
+ * @param loggingConsumer The consumer to handle logging messages from the MCP server.
+ * @param elicitationHandler The function to handle elicitation requests from the MCP server.
+ * @return The connected {@link McpClient} instance with custom message handlers and elicitation ability.
+ */
+ McpClient create(String baseUri, String sseEndpoint, Consumer loggingConsumer,
+ Function elicitationHandler);
}
\ No newline at end of file
From 9d5e552ce3cea6dfafaa9177889ed49704e0e8e0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E2=80=9C=C3=A2=E9=BB=84=E5=8F=AF=E6=AC=A3?=
<3200105739@zju.edu.cn>
Date: Sat, 8 Nov 2025 22:18:43 +0800
Subject: [PATCH 10/16] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=B5=8B=E8=AF=95?=
=?UTF-8?q?=EF=BC=8C=E4=BF=AE=E6=94=B9log=E6=A0=BC=E5=BC=8F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../support/DefaultMcpClientFactory.java | 11 ++-
.../DefaultMcpClientMessageHandler.java | 4 +-
.../support/DefaultMcpStreamableClient.java | 91 ++++++++++---------
.../fel/tool/mcp/test/TestController.java | 86 +++++++++++++++++-
.../fel/tool/mcp/client/McpClientFactory.java | 6 +-
5 files changed, 146 insertions(+), 52 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 4791828d..202b4b65 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
@@ -41,13 +41,18 @@ public McpClient create(String baseUri, String sseEndpoint) {
}
@Override
- public McpClient create(String baseUri, String sseEndpoint, Consumer loggingConsumer) {
+ public McpClient create(String baseUri, String sseEndpoint,
+ Consumer loggingConsumer) {
return create(baseUri, sseEndpoint, loggingConsumer, null);
}
@Override
- public McpClient create(String baseUri, String sseEndpoint, Function elicitationHandler) {
- return create(baseUri, sseEndpoint, DefaultMcpClientMessageHandler::defaultLoggingMessageHandler, elicitationHandler);
+ public McpClient create(String baseUri, String sseEndpoint,
+ Function elicitationHandler) {
+ return create(baseUri,
+ sseEndpoint,
+ DefaultMcpClientMessageHandler::defaultLoggingMessageHandler,
+ elicitationHandler);
}
@Override
diff --git a/framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/DefaultMcpClientMessageHandler.java b/framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/DefaultMcpClientMessageHandler.java
index 091b100a..f8209e33 100644
--- a/framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/DefaultMcpClientMessageHandler.java
+++ b/framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/DefaultMcpClientMessageHandler.java
@@ -31,6 +31,8 @@ public class DefaultMcpClientMessageHandler {
* @param notification The {@link McpSchema.LoggingMessageNotification} containing the log level and data.
*/
public static void defaultLoggingMessageHandler(McpSchema.LoggingMessageNotification notification) {
- log.info("[Client] log: {}-{}", notification.level(), notification.data());
+ log.info("Received logging message from MCP server. [level={}, data={}]",
+ notification.level(),
+ notification.data());
}
}
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 3565096d..88cf6891 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
@@ -58,7 +58,7 @@ public DefaultMcpStreamableClient(String baseUri, String sseEndpoint, int reques
this.clientId = UUID.randomUUID().toString();
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 [{}] for server: {}", clientId, baseUri);
+ log.info("Creating MCP client. [clientId={}, baseUri={}]", clientId, baseUri);
ObjectMapper mapper = new ObjectMapper();
HttpClientStreamableHttpTransport transport = HttpClientStreamableHttpTransport.builder(baseUri)
.jsonMapper(new JacksonMcpJsonMapper(mapper))
@@ -99,7 +99,7 @@ public void initialize() {
ensureNotClosed();
mcpSyncClient.initialize();
this.initialized = true;
- log.info("MCP client [{}] initialized successfully.", clientId);
+ log.info("MCP client initialized successfully. [clientId={}]", clientId);
}
/**
@@ -112,22 +112,23 @@ public void initialize() {
@Override
public List getTools() {
ensureReady();
-
try {
McpSchema.ListToolsResult result = this.mcpSyncClient.listTools();
if (result == null || result.tools() == null) {
- log.warn("MCP client [{}] failed to get tools: result is null.", clientId);
+ log.warn("Failed to get tools: result is null. [clientId={}]", clientId);
throw new IllegalStateException("Failed to get tools from MCP server: result is null.");
}
List tools = result.tools().stream().map(this::convertToFelTool).collect(Collectors.toList());
- log.info("MCP client [{}] successfully retrieved {} tools.", clientId, tools.size());
- tools.forEach(tool -> log.debug("Tool - Name: {}, Description: {}", tool.getName(), tool.getDescription()));
+ log.info("Successfully retrieved tools. [clientId={}, count={}]", clientId, tools.size());
+ tools.forEach(tool -> log.debug("Tool information. [name={}, description={}]",
+ tool.getName(),
+ tool.getDescription()));
return tools;
} catch (Exception e) {
- log.error("MCP client [{}] failed to get tools: {}", clientId, e);
- throw new IllegalStateException("Failed to get tools from MCP server: " + e.getMessage(), e);
+ log.error("Failed to get tools. [clientId={}, error={}]", clientId, e.getMessage());
+ throw new IllegalStateException("Failed to get tools from MCP server. [error=" + e.getMessage() + "]", e);
}
}
@@ -146,39 +147,20 @@ public List getTools() {
public Object callTool(String name, Map arguments) {
ensureReady();
try {
- log.info("MCP client [{}] calling tool: {} with arguments: {}", clientId, name, arguments);
+ log.info("Calling tool. [clientId={}, name={}, arguments={}]", clientId, name, arguments);
McpSchema.CallToolResult result =
this.mcpSyncClient.callTool(new McpSchema.CallToolRequest(name, arguments));
if (result == null) {
- log.error("MCP client [{}] failed to call tool '{}': result is null.", clientId, name);
- throw new IllegalStateException("Failed to call tool '" + name + "': result is null.");
+ log.error("Failed to call tool: result is null. [clientId={}, name={}]", clientId, name);
+ throw new IllegalStateException("Failed to call tool: result is null. [name=" + name + "]");
}
return processToolResult(result, name);
} catch (Exception e) {
- log.error("MCP client [{}] failed to call tool '{}': {}", clientId, name, e);
- throw new IllegalStateException("Failed to call tool '" + name + "': " + e.getMessage(), e);
- }
- }
-
- /**
- * Builds an error message from tool result content.
- *
- * @param name The name of the tool that was called.
- * @param content The content list from the tool result.
- * @return The formatted error message.
- */
- private String buildToolErrorMessage(String name, List content) {
- String errorMsg = "Tool '" + name + "' returned an error";
- if (content != null && !content.isEmpty()) {
- McpSchema.Content errorContent = content.get(0);
- if (errorContent instanceof McpSchema.TextContent textContent) {
- errorMsg += ": " + textContent.text();
- } else {
- errorMsg += ": " + errorContent;
- }
+ log.error("Failed to call tool. [clientId={}, name={}, error={}]", clientId, name, e.getMessage());
+ throw new IllegalStateException("Failed to call tool. [name=" + name + ", error=" + e.getMessage() + "]",
+ e);
}
- return errorMsg;
}
/**
@@ -194,25 +176,29 @@ private String buildToolErrorMessage(String name, List conten
*/
private Object processToolResult(McpSchema.CallToolResult result, String name) {
if (result.isError() != null && result.isError()) {
- String errorMsg = buildToolErrorMessage(name, result.content());
- log.error("MCP client [{}]: {}", clientId, errorMsg);
- throw new IllegalStateException(errorMsg);
+ String errorDetails = extractErrorDetails(result.content());
+ log.error("Tool returned an error. [clientId={}, name={}, details={}]", clientId, name, errorDetails);
+ throw new IllegalStateException(
+ "Tool returned an error. [name=" + name + ", details=" + errorDetails + "]");
}
if (result.content() == null || result.content().isEmpty()) {
- log.warn("MCP client [{}] tool '{}' returned empty content.", clientId, name);
+ log.warn("Tool returned empty content. [clientId={}, name={}]", clientId, name);
return null;
}
Object content = result.content().get(0);
if (content instanceof McpSchema.TextContent textContent) {
- log.info("MCP client [{}] successfully called tool '{}', result: {}", clientId, name, textContent.text());
+ log.info("Successfully called tool. [clientId={}, name={}, result={}]", clientId, name, textContent.text());
return textContent.text();
} else if (content instanceof McpSchema.ImageContent imageContent) {
- log.info("MCP client [{}] successfully called tool '{}', returned image content.", clientId, name);
+ log.info("Successfully called tool: image content. [clientId={}, name={}]", clientId, name);
return imageContent;
} else {
- log.info("MCP client [{}] successfully called tool '{}', content type: {}", clientId, name, content.getClass().getSimpleName());
+ log.info("Successfully called tool. [clientId={}, name={}, contentType={}]",
+ clientId,
+ name,
+ content.getClass().getSimpleName());
return content;
}
}
@@ -227,7 +213,7 @@ public void close() throws IOException {
ensureNotClosed();
this.closed = true;
this.mcpSyncClient.closeGracefully();
- log.info("MCP client [{}] closed.", clientId);
+ log.info("MCP client closed. [clientId={}]", clientId);
}
/**
@@ -265,7 +251,7 @@ private Tool convertToFelTool(McpSchema.Tool mcpTool) {
*/
private void ensureNotClosed() {
if (this.closed) {
- throw new IllegalStateException("The MCP client is closed.");
+ throw new IllegalStateException("The MCP client is closed. [clientId=" + clientId + "]");
}
}
@@ -277,7 +263,26 @@ private void ensureNotClosed() {
private void ensureReady() {
ensureNotClosed();
if (!this.initialized) {
- throw new IllegalStateException("MCP client is not initialized. Please wait a moment.");
+ throw new IllegalStateException(
+ "MCP client is not initialized. Please wait a moment. [clientId=" + clientId + "]");
+ }
+ }
+
+ /**
+ * Extracts error details from tool result content.
+ *
+ * @param content The content list from the tool result.
+ * @return The error details as a string.
+ */
+ private String extractErrorDetails(List content) {
+ if (content != null && !content.isEmpty()) {
+ McpSchema.Content errorContent = content.get(0);
+ if (errorContent instanceof McpSchema.TextContent textContent) {
+ return textContent.text();
+ } else {
+ return errorContent.toString();
+ }
}
+ return "";
}
}
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 f6cb6b39..fc765f10 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
@@ -6,6 +6,7 @@
package modelengine.fel.tool.mcp.test;
+import io.modelcontextprotocol.spec.McpSchema;
import modelengine.fel.tool.mcp.client.McpClient;
import modelengine.fel.tool.mcp.client.McpClientFactory;
import modelengine.fel.tool.mcp.entity.Tool;
@@ -15,8 +16,10 @@
import modelengine.fit.http.annotation.RequestMapping;
import modelengine.fit.http.annotation.RequestQuery;
import modelengine.fitframework.annotation.Component;
+import modelengine.fitframework.log.Logger;
import java.io.IOException;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -30,6 +33,8 @@
@Component
@RequestMapping(path = "/mcp-test")
public class TestController {
+ private static final Logger log = Logger.get(TestController.class);
+
private final McpClientFactory mcpClientFactory;
private McpClient client;
@@ -43,10 +48,12 @@ public TestController(McpClientFactory mcpClientFactory) {
}
/**
- * Initializes the MCP client by creating an instance using the provided factory and initializing it.
- * This method sets up the connection to the MCP server and prepares it for further interactions.
+ * Initializes the MCP client with default settings (default logging, no elicitation).
+ * This method creates an instance using the provided factory and initializes it.
*
- * @return A string indicating that the initialization was successful.
+ * @param baseUri The base URI of the MCP server.
+ * @param sseEndpoint The SSE endpoint of the MCP server.
+ * @return A map with clientId and status message.
*/
@PostMapping(path = "/initialize")
public String initialize(@RequestQuery(name = "baseUri") String baseUri,
@@ -56,6 +63,55 @@ public String initialize(@RequestQuery(name = "baseUri") String baseUri,
return "Initialized";
}
+ /**
+ * Initializes the MCP client with custom logging consumer but without elicitation.
+ * This demonstrates using a custom logging handler.
+ *
+ * @param baseUri The base URI of the MCP server.
+ * @param sseEndpoint The SSE endpoint of the MCP server.
+ * @return A string indicating that the initialization was successful.
+ */
+ @PostMapping(path = "/initialize-with-log")
+ public String initializeWithCustomLogging(@RequestQuery(name = "baseUri") String baseUri,
+ @RequestQuery(name = "sseEndpoint") String sseEndpoint) {
+ this.client = this.mcpClientFactory.create(baseUri, sseEndpoint, this::loggingConsumer);
+ this.client.initialize();
+ return "Initialized with custom logging";
+ }
+
+ /**
+ * Initializes the MCP client with elicitation capability.
+ * This demonstrates enabling elicitation with default logging.
+ *
+ * @param baseUri The base URI of the MCP server.
+ * @param sseEndpoint The SSE endpoint of the MCP server.
+ * @return A string indicating that the initialization was successful.
+ */
+ @PostMapping(path = "/initialize-with-elicitation")
+ public String initializeWithElicitation(@RequestQuery(name = "baseUri") String baseUri,
+ @RequestQuery(name = "sseEndpoint") String sseEndpoint) {
+ this.client = this.mcpClientFactory.create(baseUri, sseEndpoint, this::elicitationHandler);
+ this.client.initialize();
+ return "Initialized with elicitation";
+ }
+
+ /**
+ * Initializes the MCP client with both custom logging and elicitation.
+ * This demonstrates full customization of the client.
+ *
+ * @param baseUri The base URI of the MCP server.
+ * @param sseEndpoint The SSE endpoint of the MCP server.
+ * @return A string indicating that the initialization was successful.
+ */
+ @PostMapping(path = "/initialize-full")
+ public String initializeFullCustom(@RequestQuery(name = "baseUri") String baseUri,
+ @RequestQuery(name = "sseEndpoint") String sseEndpoint) {
+ this.client =
+ this.mcpClientFactory.create(baseUri, sseEndpoint, this::loggingConsumer, this::elicitationHandler);
+ this.client.initialize();
+ return "Initialized with full customization";
+ }
+
/**
* Closes the MCP client and releases any resources associated with it.
* This method ensures that the MCP client is properly closed and resources are released.
@@ -95,4 +151,28 @@ public List toolsList() {
public Object toolsCall(@RequestQuery(name = "name") String name, @RequestBody Map jsonArgs) {
return this.client.callTool(name, jsonArgs);
}
+
+ /**
+ * Custom logging consumer for MCP client.
+ *
+ * @param notification The logging message notification from MCP server.
+ */
+ private void loggingConsumer(McpSchema.LoggingMessageNotification notification) {
+ log.info("Custom logging handler received message. [level={}, data={}]",
+ notification.level(),
+ notification.data());
+ }
+
+ /**
+ * Elicitation handler for MCP client.
+ *
+ * @param request The elicitation request from MCP server.
+ * @return The elicitation result with action and user data.
+ */
+ private McpSchema.ElicitResult elicitationHandler(McpSchema.ElicitRequest request) {
+ log.info("Elicitation request received. [message={}, schema={}]", request.message(), request.requestedSchema());
+ Map userData = new HashMap<>();
+ userData.put("response", "Auto-accepted by test controller");
+ return new McpSchema.ElicitResult(McpSchema.ElicitResult.Action.ACCEPT, userData);
+ }
}
\ 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/McpClientFactory.java b/framework/fel/java/services/tool-mcp-client-service/src/main/java/modelengine/fel/tool/mcp/client/McpClientFactory.java
index c6dfc6fb..8f0a87d4 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
@@ -37,7 +37,8 @@ public interface McpClientFactory {
* @param loggingConsumer The consumer to handle logging messages from the MCP server.
* @return The connected {@link McpClient} instance with custom logging consumer but without elicitation ability.
*/
- McpClient create(String baseUri, String sseEndpoint, Consumer loggingConsumer);
+ McpClient create(String baseUri, String sseEndpoint,
+ Consumer loggingConsumer);
/**
* Creates a {@link McpClient} instance with default logging consumer and elicitation ability.
@@ -47,7 +48,8 @@ public interface McpClientFactory {
* @param elicitationHandler The function to handle elicitation requests from the MCP server.
* @return The connected {@link McpClient} instance with default logging consumer and elicitation ability.
*/
- McpClient create(String baseUri, String sseEndpoint, Function elicitationHandler);
+ McpClient create(String baseUri, String sseEndpoint,
+ Function elicitationHandler);
/**
* Creates a {@link McpClient} instance with custom message handlers and elicitation ability.
From 2cb20b36e70c65cbb69ffe64c10eea0a35a7cb6c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E2=80=9C=C3=A2=E9=BB=84=E5=8F=AF=E6=AC=A3?=
<3200105739@zju.edu.cn>
Date: Sat, 8 Nov 2025 22:42:01 +0800
Subject: [PATCH 11/16] =?UTF-8?q?=E4=BF=AE=E6=94=B9client=E5=92=8Cserver?=
=?UTF-8?q?=E9=83=A8=E5=88=86=E6=97=A5=E5=BF=97=E8=BE=93=E5=87=BA?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../client/support/DefaultMcpStreamableClient.java | 12 ++++++------
.../FitMcpStreamableServerTransportProvider.java | 8 ++++----
2 files changed, 10 insertions(+), 10 deletions(-)
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 88cf6891..5e028706 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
@@ -115,19 +115,19 @@ public List getTools() {
try {
McpSchema.ListToolsResult result = this.mcpSyncClient.listTools();
if (result == null || result.tools() == null) {
- log.warn("Failed to get tools: result is null. [clientId={}]", clientId);
- throw new IllegalStateException("Failed to get tools from MCP server: result is null.");
+ log.warn("Failed to get tools list: result is null. [clientId={}]", clientId);
+ throw new IllegalStateException("Failed to get tools list from MCP server: result is null.");
}
List tools = result.tools().stream().map(this::convertToFelTool).collect(Collectors.toList());
- log.info("Successfully retrieved tools. [clientId={}, count={}]", clientId, tools.size());
+ log.info("Successfully retrieved tools list. [clientId={}, count={}]", clientId, tools.size());
tools.forEach(tool -> log.debug("Tool information. [name={}, description={}]",
tool.getName(),
tool.getDescription()));
return tools;
} catch (Exception e) {
- log.error("Failed to get tools. [clientId={}, error={}]", clientId, e.getMessage());
+ log.error("Failed to get tools list. [clientId={}, error={}]", clientId, e.getMessage());
throw new IllegalStateException("Failed to get tools from MCP server. [error=" + e.getMessage() + "]", e);
}
}
@@ -251,7 +251,7 @@ private Tool convertToFelTool(McpSchema.Tool mcpTool) {
*/
private void ensureNotClosed() {
if (this.closed) {
- throw new IllegalStateException("The MCP client is closed. [clientId=" + clientId + "]");
+ throw new IllegalStateException("The MCP client is already closed. [clientId=" + clientId + "]");
}
}
@@ -264,7 +264,7 @@ private void ensureReady() {
ensureNotClosed();
if (!this.initialized) {
throw new IllegalStateException(
- "MCP client is not initialized. Please wait a moment. [clientId=" + clientId + "]");
+ "MCP client is not initialized. [clientId=" + clientId + "]");
}
}
diff --git a/framework/fel/java/plugins/tool-mcp-server/src/main/java/modelengine/fel/tool/mcp/server/transport/FitMcpStreamableServerTransportProvider.java b/framework/fel/java/plugins/tool-mcp-server/src/main/java/modelengine/fel/tool/mcp/server/transport/FitMcpStreamableServerTransportProvider.java
index 324c427d..d3da8acf 100644
--- a/framework/fel/java/plugins/tool-mcp-server/src/main/java/modelengine/fel/tool/mcp/server/transport/FitMcpStreamableServerTransportProvider.java
+++ b/framework/fel/java/plugins/tool-mcp-server/src/main/java/modelengine/fel/tool/mcp/server/transport/FitMcpStreamableServerTransportProvider.java
@@ -204,7 +204,7 @@ public Object handleGet(HttpClassicServerRequest request, HttpClassicServerRespo
}
String sessionId = request.headers().first(HttpHeaders.MCP_SESSION_ID).orElse("");
McpStreamableServerSession session = this.sessions.get(sessionId);
- logger.info("[GET] Handling GET request for session: {}", sessionId);
+ logger.info("[GET] Receiving GET request from session: {}", sessionId);
McpTransportContext transportContext = this.contextExtractor.extract(request);
try {
@@ -392,7 +392,7 @@ private void handleReplaySseRequest(HttpClassicServerRequest request, McpTranspo
String sessionId, McpStreamableServerSession session, FitStreamableMcpSessionTransport sessionTransport,
Emitter emitter) {
String lastId = request.headers().first(HttpHeaders.LAST_EVENT_ID).orElse("0");
- logger.info("[GET] Receiving replay request from session: {}", sessionId);
+ logger.info("[GET] Handling replay request from session: {}", sessionId);
try {
session.replay(lastId)
@@ -426,7 +426,7 @@ private void handleReplaySseRequest(HttpClassicServerRequest request, McpTranspo
*/
private void handleEstablishSseRequest(String sessionId, McpStreamableServerSession session,
FitStreamableMcpSessionTransport sessionTransport, Emitter emitter) {
- logger.info("[GET] Receiving Get request to establish new SSE for session: {}", sessionId);
+ logger.info("[GET] Handling Get request to establish new SSE for session: {}", sessionId);
McpStreamableServerSession.McpStreamableServerSessionStream listeningStream =
session.listeningStream(sessionTransport);
@@ -643,7 +643,7 @@ private class FitStreamableMcpSessionTransport implements McpStreamableServerTra
this.sessionId = sessionId;
this.emitter = emitter;
this.response = response;
- logger.info("[SSE] Building SSE for session: {} ", sessionId);
+ logger.info("[SSE] Building SSE emitter for session: {} ", sessionId);
}
/**
From e492942cd8a63e38f085fd1ca559fc66aec8dcbf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=BB=84=E5=8F=AF=E6=AC=A3?= <3200105739@zju.edu.cn>
Date: Sun, 9 Nov 2025 15:55:47 +0800
Subject: [PATCH 12/16] =?UTF-8?q?=E7=A7=BB=E9=99=A4elicitation=E8=83=BD?=
=?UTF-8?q?=E5=8A=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../support/DefaultMcpClientFactory.java | 32 +-----
...r.java => DefaultMcpClientLogHandler.java} | 26 +++--
.../support/DefaultMcpStreamableClient.java | 97 +++++++++----------
...tMcpStreamableServerTransportProvider.java | 92 +++++++++++-------
.../fel/tool/mcp/test/TestController.java | 51 +---------
.../fel/tool/mcp/client/McpClientFactory.java | 43 +-------
6 files changed, 121 insertions(+), 220 deletions(-)
rename framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/{DefaultMcpClientMessageHandler.java => DefaultMcpClientLogHandler.java} (63%)
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 202b4b65..330a3172 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,15 +6,11 @@
package modelengine.fel.tool.mcp.client.support;
-import io.modelcontextprotocol.spec.McpSchema;
import modelengine.fel.tool.mcp.client.McpClient;
import modelengine.fel.tool.mcp.client.McpClientFactory;
import modelengine.fitframework.annotation.Component;
import modelengine.fitframework.annotation.Value;
-import java.util.function.Consumer;
-import java.util.function.Function;
-
/**
* Represents a factory for creating instances of the {@link DefaultMcpStreamableClient}.
* This class is responsible for initializing and configuring.
@@ -37,32 +33,6 @@ public DefaultMcpClientFactory(@Value("${mcp.client.request.timeout-seconds}") i
@Override
public McpClient create(String baseUri, String sseEndpoint) {
- return create(baseUri, sseEndpoint, DefaultMcpClientMessageHandler::defaultLoggingMessageHandler, null);
- }
-
- @Override
- public McpClient create(String baseUri, String sseEndpoint,
- Consumer loggingConsumer) {
- return create(baseUri, sseEndpoint, loggingConsumer, null);
- }
-
- @Override
- public McpClient create(String baseUri, String sseEndpoint,
- Function elicitationHandler) {
- return create(baseUri,
- sseEndpoint,
- DefaultMcpClientMessageHandler::defaultLoggingMessageHandler,
- elicitationHandler);
- }
-
- @Override
- public McpClient create(String baseUri, String sseEndpoint,
- Consumer loggingConsumer,
- Function elicitationHandler) {
- return new DefaultMcpStreamableClient(baseUri,
- sseEndpoint,
- requestTimeoutSeconds,
- loggingConsumer,
- elicitationHandler);
+ return new DefaultMcpStreamableClient(baseUri, sseEndpoint, requestTimeoutSeconds);
}
}
diff --git a/framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/DefaultMcpClientMessageHandler.java b/framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/DefaultMcpClientLogHandler.java
similarity index 63%
rename from framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/DefaultMcpClientMessageHandler.java
rename to framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/DefaultMcpClientLogHandler.java
index f8209e33..bffb88df 100644
--- a/framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/DefaultMcpClientMessageHandler.java
+++ b/framework/fel/java/plugins/tool-mcp-client/src/main/java/modelengine/fel/tool/mcp/client/support/DefaultMcpClientLogHandler.java
@@ -7,13 +7,8 @@
package modelengine.fel.tool.mcp.client.support;
import io.modelcontextprotocol.spec.McpSchema;
-import modelengine.fitframework.annotation.Component;
import modelengine.fitframework.log.Logger;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Scanner;
-
/**
* Handles MCP client messages received from MCP server,
* including logging notifications and elicitation requests.
@@ -21,17 +16,28 @@
* @author 黄可欣
* @since 2025-11-03
*/
-@Component
-public class DefaultMcpClientMessageHandler {
- private static final Logger log = Logger.get(DefaultMcpClientMessageHandler.class);
+public class DefaultMcpClientLogHandler {
+ private static final Logger log = Logger.get(DefaultMcpClientLogHandler.class);
+ private final String clientId;
+
+ /**
+ * Constructs a new instance of DefaultMcpClientLogHandler.
+ *
+ * @param clientId The unique identifier of the MCP client.
+ */
+ public DefaultMcpClientLogHandler(String clientId) {
+ this.clientId = clientId;
+ }
/**
* Handles logging messages received from the MCP server.
+ * Includes the client UUID in the log message for tracking.
*
* @param notification The {@link McpSchema.LoggingMessageNotification} containing the log level and data.
*/
- public static void defaultLoggingMessageHandler(McpSchema.LoggingMessageNotification notification) {
- log.info("Received logging message from MCP server. [level={}, data={}]",
+ public void handleLoggingMessage(McpSchema.LoggingMessageNotification notification) {
+ log.info("Received logging message from MCP server. [clientId={}, level={}, data={}]",
+ this.clientId,
notification.level(),
notification.data());
}
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 5e028706..9546c0f0 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
@@ -18,15 +18,14 @@
import modelengine.fel.tool.mcp.client.McpClient;
import modelengine.fel.tool.mcp.entity.Tool;
import modelengine.fitframework.log.Logger;
+import modelengine.fitframework.util.StringUtils;
+import modelengine.fitframework.util.UuidUtils;
import java.io.IOException;
import java.time.Duration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.UUID;
-import java.util.function.Consumer;
-import java.util.function.Function;
import java.util.stream.Collectors;
/**
@@ -40,6 +39,8 @@ public class DefaultMcpStreamableClient implements McpClient {
private final String clientId;
private final McpSyncClient mcpSyncClient;
+ private final DefaultMcpClientLogHandler logHandler;
+
private volatile boolean initialized = false;
private volatile boolean closed = false;
@@ -49,44 +50,30 @@ public class DefaultMcpStreamableClient implements McpClient {
* @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.
- * @param loggingConsumer The consumer to handle logging messages from the MCP server.
- * @param elicitationHandler The function to handle elicitation requests from the MCP server.
*/
- public DefaultMcpStreamableClient(String baseUri, String sseEndpoint, int requestTimeoutSeconds,
- Consumer loggingConsumer,
- Function elicitationHandler) {
- this.clientId = UUID.randomUUID().toString();
+ public DefaultMcpStreamableClient(String baseUri, String sseEndpoint, int requestTimeoutSeconds) {
+ 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={}]", clientId, baseUri);
+ 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();
- if (elicitationHandler != null) {
- this.mcpSyncClient = io.modelcontextprotocol.client.McpClient.sync(transport)
- .requestTimeout(Duration.ofSeconds(requestTimeoutSeconds))
- .capabilities(McpSchema.ClientCapabilities.builder().elicitation().build())
- .loggingConsumer(loggingConsumer)
- .elicitation(elicitationHandler)
- .jsonSchemaValidator(new DefaultJsonSchemaValidator(mapper))
- .build();
- } else {
- this.mcpSyncClient = io.modelcontextprotocol.client.McpClient.sync(transport)
- .requestTimeout(Duration.ofSeconds(requestTimeoutSeconds))
- .capabilities(McpSchema.ClientCapabilities.builder().build())
- .loggingConsumer(loggingConsumer)
- .jsonSchemaValidator(new DefaultJsonSchemaValidator(mapper))
- .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))
+ .build();
}
@Override
public String getClientId() {
- return clientId;
+ return this.clientId;
}
/**
@@ -97,9 +84,9 @@ public String getClientId() {
@Override
public void initialize() {
ensureNotClosed();
- mcpSyncClient.initialize();
+ this.mcpSyncClient.initialize();
this.initialized = true;
- log.info("MCP client initialized successfully. [clientId={}]", clientId);
+ log.info("MCP client initialized successfully. [clientId={}]", this.clientId);
}
/**
@@ -115,20 +102,21 @@ public List getTools() {
try {
McpSchema.ListToolsResult result = this.mcpSyncClient.listTools();
if (result == null || result.tools() == null) {
- log.warn("Failed to get tools list: result is null. [clientId={}]", clientId);
+ log.warn("Failed to get tools list: result is null. [clientId={}]", this.clientId);
throw new IllegalStateException("Failed to get tools list from MCP server: result is null.");
}
List tools = result.tools().stream().map(this::convertToFelTool).collect(Collectors.toList());
- log.info("Successfully retrieved tools list. [clientId={}, count={}]", clientId, tools.size());
+ log.info("Successfully retrieved tools list. [clientId={}, count={}]", this.clientId, tools.size());
tools.forEach(tool -> log.debug("Tool information. [name={}, description={}]",
tool.getName(),
tool.getDescription()));
return tools;
} catch (Exception e) {
- log.error("Failed to get tools list. [clientId={}, error={}]", clientId, e.getMessage());
- throw new IllegalStateException("Failed to get tools from MCP server. [error=" + e.getMessage() + "]", e);
+ log.error("Failed to get tools list. [clientId={}, error={}]", this.clientId, e.getMessage());
+ throw new IllegalStateException(StringUtils.format("Failed to get tools from MCP server. [error={0}]",
+ e.getMessage()), e);
}
}
@@ -147,19 +135,21 @@ public List getTools() {
public Object callTool(String name, Map arguments) {
ensureReady();
try {
- log.info("Calling tool. [clientId={}, name={}, arguments={}]", clientId, name, arguments);
+ log.info("Calling tool. [clientId={}, name={}, arguments={}]", this.clientId, name, arguments);
McpSchema.CallToolResult result =
this.mcpSyncClient.callTool(new McpSchema.CallToolRequest(name, arguments));
if (result == null) {
- log.error("Failed to call tool: result is null. [clientId={}, name={}]", clientId, name);
- throw new IllegalStateException("Failed to call tool: result is null. [name=" + name + "]");
+ log.error("Failed to call tool: result is null. [clientId={}, name={}]", this.clientId, name);
+ throw new IllegalStateException(StringUtils.format("Failed to call tool: result is null. [name={0}]",
+ name));
}
return processToolResult(result, name);
} catch (Exception e) {
- log.error("Failed to call tool. [clientId={}, name={}, error={}]", clientId, name, e.getMessage());
- throw new IllegalStateException("Failed to call tool. [name=" + name + ", error=" + e.getMessage() + "]",
- e);
+ log.error("Failed to call tool. [clientId={}, name={}, error={}]", this.clientId, name, e.getMessage());
+ throw new IllegalStateException(StringUtils.format("Failed to call tool. [name={0}, error={1}]",
+ name,
+ e.getMessage()), e);
}
}
@@ -177,26 +167,30 @@ public Object callTool(String name, Map arguments) {
private Object processToolResult(McpSchema.CallToolResult result, String name) {
if (result.isError() != null && result.isError()) {
String errorDetails = extractErrorDetails(result.content());
- log.error("Tool returned an error. [clientId={}, name={}, details={}]", clientId, name, errorDetails);
- throw new IllegalStateException(
- "Tool returned an error. [name=" + name + ", details=" + errorDetails + "]");
+ log.error("Tool returned an error. [clientId={}, name={}, details={}]", this.clientId, name, errorDetails);
+ throw new IllegalStateException(StringUtils.format("Tool returned an error. [name={0}, details={1}]",
+ name,
+ errorDetails));
}
if (result.content() == null || result.content().isEmpty()) {
- log.warn("Tool returned empty content. [clientId={}, name={}]", clientId, name);
+ log.warn("Tool returned empty content. [clientId={}, name={}]", this.clientId, name);
return null;
}
Object content = result.content().get(0);
if (content instanceof McpSchema.TextContent textContent) {
- log.info("Successfully called tool. [clientId={}, name={}, result={}]", clientId, name, textContent.text());
+ log.info("Successfully called tool. [clientId={}, name={}, result={}]",
+ this.clientId,
+ name,
+ textContent.text());
return textContent.text();
} else if (content instanceof McpSchema.ImageContent imageContent) {
- log.info("Successfully called tool: image content. [clientId={}, name={}]", clientId, name);
+ log.info("Successfully called tool: image content. [clientId={}, name={}]", this.clientId, name);
return imageContent;
} else {
log.info("Successfully called tool. [clientId={}, name={}, contentType={}]",
- clientId,
+ this.clientId,
name,
content.getClass().getSimpleName());
return content;
@@ -213,7 +207,7 @@ public void close() throws IOException {
ensureNotClosed();
this.closed = true;
this.mcpSyncClient.closeGracefully();
- log.info("MCP client closed. [clientId={}]", clientId);
+ log.info("MCP client closed. [clientId={}]", this.clientId);
}
/**
@@ -251,7 +245,8 @@ private Tool convertToFelTool(McpSchema.Tool mcpTool) {
*/
private void ensureNotClosed() {
if (this.closed) {
- throw new IllegalStateException("The MCP client is already closed. [clientId=" + clientId + "]");
+ throw new IllegalStateException(StringUtils.format("The MCP client is already closed. [clientId={0}]",
+ this.clientId));
}
}
@@ -263,8 +258,8 @@ private void ensureNotClosed() {
private void ensureReady() {
ensureNotClosed();
if (!this.initialized) {
- throw new IllegalStateException(
- "MCP client is not initialized. [clientId=" + clientId + "]");
+ throw new IllegalStateException(StringUtils.format("MCP client is not initialized. [clientId={0}]",
+ this.clientId));
}
}
diff --git a/framework/fel/java/plugins/tool-mcp-server/src/main/java/modelengine/fel/tool/mcp/server/transport/FitMcpStreamableServerTransportProvider.java b/framework/fel/java/plugins/tool-mcp-server/src/main/java/modelengine/fel/tool/mcp/server/transport/FitMcpStreamableServerTransportProvider.java
index d3da8acf..9bf5bbfe 100644
--- a/framework/fel/java/plugins/tool-mcp-server/src/main/java/modelengine/fel/tool/mcp/server/transport/FitMcpStreamableServerTransportProvider.java
+++ b/framework/fel/java/plugins/tool-mcp-server/src/main/java/modelengine/fel/tool/mcp/server/transport/FitMcpStreamableServerTransportProvider.java
@@ -133,18 +133,21 @@ public void setSessionFactory(McpStreamableServerSession.Factory sessionFactory)
@Override
public Mono notifyClients(String method, Object params) {
if (this.sessions.isEmpty()) {
- logger.debug("No active sessions to broadcast message to");
+ logger.debug("No active sessions to broadcast message.");
return Mono.empty();
}
- logger.info("Attempting to broadcast message to {} active sessions", this.sessions.size());
+ logger.info("Attempting to broadcast message. [sessionCount={}]", this.sessions.size());
return Mono.fromRunnable(() -> {
this.sessions.values().parallelStream().forEach(session -> {
try {
session.sendNotification(method, params).block();
} catch (Exception e) {
- logger.error("Failed to send message to session {}: {}", session.getId(), e.getMessage(), e);
+ logger.error("Failed to send message to session. [sessionId={}, error={}]",
+ session.getId(),
+ e.getMessage(),
+ e);
}
});
});
@@ -159,18 +162,21 @@ public Mono notifyClients(String method, Object params) {
public Mono closeGracefully() {
return Mono.fromRunnable(() -> {
this.isClosing = true;
- logger.info("Initiating graceful shutdown with {} active sessions", this.sessions.size());
+ logger.info("Initiating graceful shutdown. [sessionCount={}]", this.sessions.size());
this.sessions.values().parallelStream().forEach(session -> {
try {
session.closeGracefully().block();
} catch (Exception e) {
- logger.error("Failed to close session {}: {}", session.getId(), e.getMessage(), e);
+ logger.error("Failed to close session. [sessionId={}, error={}]",
+ session.getId(),
+ e.getMessage(),
+ e);
}
});
this.sessions.clear();
- logger.info("Graceful shutdown completed");
+ logger.info("Graceful shutdown completed.");
}).then().doOnSuccess(v -> {
if (this.keepAliveScheduler != null) {
this.keepAliveScheduler.shutdown();
@@ -204,7 +210,7 @@ public Object handleGet(HttpClassicServerRequest request, HttpClassicServerRespo
}
String sessionId = request.headers().first(HttpHeaders.MCP_SESSION_ID).orElse("");
McpStreamableServerSession session = this.sessions.get(sessionId);
- logger.info("[GET] Receiving GET request from session: {}", sessionId);
+ logger.info("[GET] Receiving GET request. [sessionId={}]", sessionId);
McpTransportContext transportContext = this.contextExtractor.extract(request);
try {
@@ -220,7 +226,7 @@ public Object handleGet(HttpClassicServerRequest request, HttpClassicServerRespo
}
});
} catch (Exception e) {
- logger.error("Failed to handle GET request for session {}: {}", sessionId, e.getMessage(), e);
+ logger.error("[GET] Failed to handle GET request. [sessionId={}, error={}]", sessionId, e.getMessage(), e);
response.statusCode(HttpResponseStatus.INTERNAL_SERVER_ERROR.statusCode());
return null;
}
@@ -252,18 +258,18 @@ public Object handlePost(HttpClassicServerRequest request, HttpClassicServerResp
// Handle JSONRPCMessage
if (message instanceof McpSchema.JSONRPCRequest jsonrpcRequest && jsonrpcRequest.method()
.equals(McpSchema.METHOD_INITIALIZE)) {
- logger.info("[POST] Handling initialize method, with receiving message: {}", requestBody);
+ logger.info("[POST] Handling initialize method. [requestBody={}]", requestBody);
return handleInitializeRequest(request, response, jsonrpcRequest);
} else {
return handleJsonRpcMessage(message, request, requestBody, transportContext, response);
}
} catch (IllegalArgumentException | IOException e) {
- logger.error("Failed to deserialize message: {}", e.getMessage(), e);
+ logger.error("[POST] Failed to deserialize message. [error={}]", e.getMessage(), e);
response.statusCode(HttpResponseStatus.BAD_REQUEST.statusCode());
return Entity.createObject(response,
McpError.builder(McpSchema.ErrorCodes.PARSE_ERROR).message("Invalid message format").build());
} catch (Exception e) {
- logger.error("Error handling message: {}", e.getMessage(), e);
+ logger.error("[POST] Error handling message. [error={}]", e.getMessage(), e);
response.statusCode(HttpResponseStatus.INTERNAL_SERVER_ERROR.statusCode());
return Entity.createObject(response,
McpError.builder(McpSchema.ErrorCodes.INTERNAL_ERROR).message(e.getMessage()).build());
@@ -295,7 +301,7 @@ public Object handleDelete(HttpClassicServerRequest request, HttpClassicServerRe
}
String sessionId = request.headers().first(HttpHeaders.MCP_SESSION_ID).orElse("");
McpStreamableServerSession session = this.sessions.get(sessionId);
- logger.info("[DELETE] Receiving delete request from session: {}", sessionId);
+ logger.info("[DELETE] Receiving delete request. [sessionId={}]", sessionId);
McpTransportContext transportContext = this.contextExtractor.extract(request);
try {
@@ -304,7 +310,7 @@ public Object handleDelete(HttpClassicServerRequest request, HttpClassicServerRe
response.statusCode(HttpResponseStatus.OK.statusCode());
return null;
} catch (Exception e) {
- logger.error("Failed to delete session {}: {}", sessionId, e.getMessage(), e);
+ logger.error("[DELETE] Failed to delete session. [sessionId={}, error={}]", sessionId, e.getMessage(), e);
response.statusCode(HttpResponseStatus.INTERNAL_SERVER_ERROR.statusCode());
return Entity.createObject(response,
McpError.builder(McpSchema.ErrorCodes.INTERNAL_ERROR).message(e.getMessage()).build());
@@ -392,7 +398,7 @@ private void handleReplaySseRequest(HttpClassicServerRequest request, McpTranspo
String sessionId, McpStreamableServerSession session, FitStreamableMcpSessionTransport sessionTransport,
Emitter emitter) {
String lastId = request.headers().first(HttpHeaders.LAST_EVENT_ID).orElse("0");
- logger.info("[GET] Handling replay request from session: {}", sessionId);
+ logger.info("[GET] Handling replay request. [sessionId={}]", sessionId);
try {
session.replay(lastId)
@@ -404,12 +410,12 @@ private void handleReplaySseRequest(HttpClassicServerRequest request, McpTranspo
.contextWrite(ctx -> ctx.put(McpTransportContext.KEY, transportContext))
.block();
} catch (Exception e) {
- logger.error("Failed to replay message: {}", e.getMessage(), e);
+ logger.error("[GET] Failed to replay message. [error={}]", e.getMessage(), e);
emitter.fail(e);
}
});
} catch (Exception e) {
- logger.error("Failed to replay messages: {}", e.getMessage(), e);
+ logger.error("[GET] Failed to replay messages. [error={}]", e.getMessage(), e);
emitter.fail(e);
}
}
@@ -426,7 +432,7 @@ private void handleReplaySseRequest(HttpClassicServerRequest request, McpTranspo
*/
private void handleEstablishSseRequest(String sessionId, McpStreamableServerSession session,
FitStreamableMcpSessionTransport sessionTransport, Emitter emitter) {
- logger.info("[GET] Handling Get request to establish new SSE for session: {}", sessionId);
+ logger.info("[GET] Handling GET request to establish new SSE. [sessionId={}]", sessionId);
McpStreamableServerSession.McpStreamableServerSessionStream listeningStream =
session.listeningStream(sessionTransport);
@@ -438,21 +444,25 @@ public void onEmittedData(TextEvent data) {
@Override
public void onCompleted() {
- logger.info("[SSE] Completed SSE emitting for session: {}", sessionId);
+ logger.info("[SSE] Completed SSE emitting. [sessionId={}]", sessionId);
try {
listeningStream.close();
} catch (Exception e) {
- logger.warn("[SSE] Error closing listeningStream on complete: {}", e.getMessage());
+ logger.warn("[SSE] Error closing listeningStream on complete. [sessionId={}, error={}]",
+ sessionId,
+ e.getMessage());
}
}
@Override
public void onFailed(Exception cause) {
- logger.warn("[SSE] SSE failed for session: {}, cause: {}", sessionId, cause.getMessage());
+ logger.warn("[SSE] SSE failed. [sessionId={}, cause={}]", sessionId, cause.getMessage());
try {
listeningStream.close();
} catch (Exception e) {
- logger.warn("[SSE] Error closing listeningStream on failure: {}", e.getMessage());
+ logger.warn("[SSE] Error closing listeningStream on failure. [sessionId={}, error={}]",
+ sessionId,
+ e.getMessage());
}
}
});
@@ -484,11 +494,11 @@ private Object handleInitializeRequest(HttpClassicServerRequest request, HttpCla
response.statusCode(HttpResponseStatus.OK.statusCode());
response.headers().set("Content-Type", MimeType.APPLICATION_JSON.value());
response.headers().set(HttpHeaders.MCP_SESSION_ID, init.session().getId());
- logger.info("[POST] Sending initialize message via HTTP response to session {}", init.session().getId());
+ logger.info("[POST] Sending initialize message via HTTP response. [sessionId={}]", init.session().getId());
return Entity.createObject(response,
new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, jsonrpcRequest.id(), initResult, null));
} catch (Exception e) {
- logger.error("Failed to initialize session: {}", e.getMessage(), e);
+ logger.error("[POST] Failed to initialize session. [error={}]", e.getMessage(), e);
response.statusCode(HttpResponseStatus.INTERNAL_SERVER_ERROR.statusCode());
return Entity.createObject(response,
McpError.builder(McpSchema.ErrorCodes.INTERNAL_ERROR).message(e.getMessage()).build());
@@ -515,7 +525,7 @@ private Object handleJsonRpcMessage(McpSchema.JSONRPCMessage message, HttpClassi
}
String sessionId = request.headers().first(HttpHeaders.MCP_SESSION_ID).orElse("");
McpStreamableServerSession session = this.sessions.get(sessionId);
- logger.info("[POST] Receiving message from session {}: {}", sessionId, requestBody);
+ logger.info("[POST] Receiving message from session. [sessionId={}, requestBody={}]", sessionId, requestBody);
if (message instanceof McpSchema.JSONRPCResponse jsonrpcResponse) {
handleJsonRpcResponse(jsonrpcResponse, session, transportContext, response);
@@ -590,12 +600,12 @@ public void onEmittedData(TextEvent data) {
@Override
public void onCompleted() {
- logger.info("[SSE] Completed SSE emitting for session: {}", sessionId);
+ logger.info("[SSE] Completed SSE emitting. [sessionId={}]", sessionId);
}
@Override
public void onFailed(Exception e) {
- logger.warn("[SSE] SSE failed for session: {}, cause: {}", sessionId, e.getMessage());
+ logger.warn("[SSE] SSE failed. [sessionId={}, cause={}]", sessionId, e.getMessage());
}
});
@@ -607,7 +617,7 @@ public void onFailed(Exception e) {
.contextWrite(ctx -> ctx.put(McpTransportContext.KEY, transportContext))
.block();
} catch (Exception e) {
- logger.error("Failed to handle request stream: {}", e.getMessage(), e);
+ logger.error("[POST] Failed to handle request stream. [error={}]", e.getMessage(), e);
emitter.fail(e);
}
});
@@ -643,7 +653,7 @@ private class FitStreamableMcpSessionTransport implements McpStreamableServerTra
this.sessionId = sessionId;
this.emitter = emitter;
this.response = response;
- logger.info("[SSE] Building SSE emitter for session: {} ", sessionId);
+ logger.info("[SSE] Building SSE emitter. [sessionId={}]", sessionId);
}
/**
@@ -669,20 +679,21 @@ public Mono sendMessage(McpSchema.JSONRPCMessage message) {
public Mono sendMessage(McpSchema.JSONRPCMessage message, String messageId) {
return Mono.fromRunnable(() -> {
if (this.closed) {
- logger.info("Attempted to send message to closed session: {}", this.sessionId);
+ logger.info("[SSE] Attempted to send message to closed session. [sessionId={}]", this.sessionId);
return;
}
this.lock.lock();
try {
if (this.closed) {
- logger.info("Session {} was closed during message send attempt", this.sessionId);
+ logger.info("[SSE] Session was closed during message send attempt. [sessionId={}]",
+ this.sessionId);
return;
}
// Check if connection is still active before sending
if (!this.response.isActive()) {
- logger.warn("[SSE] Connection inactive detected while sending message for session: {}",
+ logger.warn("[SSE] Connection inactive detected while sending message. [sessionId={}]",
this.sessionId);
this.close();
return;
@@ -693,13 +704,18 @@ public Mono sendMessage(McpSchema.JSONRPCMessage message, String messageId
TextEvent.custom().id(this.sessionId).event(Event.MESSAGE.code()).data(jsonText).build();
this.emitter.emit(textEvent);
- logger.info("[SSE] Sending message to session {}: {}", this.sessionId, jsonText);
+ logger.info("[SSE] Sending message to session. [sessionId={}, jsonText={}]",
+ this.sessionId,
+ jsonText);
} catch (Exception e) {
- logger.error("Failed to send message to session {}: {}", this.sessionId, e.getMessage(), e);
+ logger.error("[SSE] Failed to send message to session. [sessionId={}, error={}]",
+ this.sessionId,
+ e.getMessage(),
+ e);
try {
this.emitter.fail(e);
} catch (Exception errorException) {
- logger.error("Failed to send error to SSE builder for session {}: {}",
+ logger.error("[SSE] Failed to send error to SSE builder. [sessionId={}, error={}]",
this.sessionId,
errorException.getMessage(),
errorException);
@@ -741,16 +757,18 @@ public void close() {
this.lock.lock();
try {
if (this.closed) {
- logger.info("Session transport {} already closed", this.sessionId);
+ logger.info("[SSE] Session transport already closed. [sessionId={}]", this.sessionId);
return;
}
this.closed = true;
this.emitter.complete();
- logger.info("[SSE] Closed SSE builder successfully for session {}", sessionId);
+ logger.info("[SSE] Closed SSE builder successfully. [sessionId={}]", sessionId);
} catch (Exception e) {
- logger.warn("Failed to complete SSE builder for session {}: {}", sessionId, e.getMessage());
+ logger.warn("[SSE] Failed to complete SSE builder. [sessionId={}, error={}]",
+ sessionId,
+ e.getMessage());
} finally {
this.lock.unlock();
}
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 fc765f10..a8d1e59b 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
@@ -48,7 +48,7 @@ public TestController(McpClientFactory mcpClientFactory) {
}
/**
- * Initializes the MCP client with default settings (default logging, no elicitation).
+ * Initializes the MCP client .
* This method creates an instance using the provided factory and initializes it.
*
* @param baseUri The base URI of the MCP server.
@@ -63,55 +63,6 @@ public String initialize(@RequestQuery(name = "baseUri") String baseUri,
return "Initialized";
}
- /**
- * Initializes the MCP client with custom logging consumer but without elicitation.
- * This demonstrates using a custom logging handler.
- *
- * @param baseUri The base URI of the MCP server.
- * @param sseEndpoint The SSE endpoint of the MCP server.
- * @return A string indicating that the initialization was successful.
- */
- @PostMapping(path = "/initialize-with-log")
- public String initializeWithCustomLogging(@RequestQuery(name = "baseUri") String baseUri,
- @RequestQuery(name = "sseEndpoint") String sseEndpoint) {
- this.client = this.mcpClientFactory.create(baseUri, sseEndpoint, this::loggingConsumer);
- this.client.initialize();
- return "Initialized with custom logging";
- }
-
- /**
- * Initializes the MCP client with elicitation capability.
- * This demonstrates enabling elicitation with default logging.
- *
- * @param baseUri The base URI of the MCP server.
- * @param sseEndpoint The SSE endpoint of the MCP server.
- * @return A string indicating that the initialization was successful.
- */
- @PostMapping(path = "/initialize-with-elicitation")
- public String initializeWithElicitation(@RequestQuery(name = "baseUri") String baseUri,
- @RequestQuery(name = "sseEndpoint") String sseEndpoint) {
- this.client = this.mcpClientFactory.create(baseUri, sseEndpoint, this::elicitationHandler);
- this.client.initialize();
- return "Initialized with elicitation";
- }
-
- /**
- * Initializes the MCP client with both custom logging and elicitation.
- * This demonstrates full customization of the client.
- *
- * @param baseUri The base URI of the MCP server.
- * @param sseEndpoint The SSE endpoint of the MCP server.
- * @return A string indicating that the initialization was successful.
- */
- @PostMapping(path = "/initialize-full")
- public String initializeFullCustom(@RequestQuery(name = "baseUri") String baseUri,
- @RequestQuery(name = "sseEndpoint") String sseEndpoint) {
- this.client =
- this.mcpClientFactory.create(baseUri, sseEndpoint, this::loggingConsumer, this::elicitationHandler);
- this.client.initialize();
- return "Initialized with full customization";
- }
-
/**
* Closes the MCP client and releases any resources associated with it.
* This method ensures that the MCP client is properly closed and resources are released.
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 8f0a87d4..d52c639d 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,11 +6,6 @@
package modelengine.fel.tool.mcp.client;
-import io.modelcontextprotocol.spec.McpSchema;
-
-import java.util.function.Consumer;
-import java.util.function.Function;
-
/**
* Indicates the factory of {@link McpClient}.
*
@@ -21,45 +16,11 @@
*/
public interface McpClientFactory {
/**
- * Creates a {@link McpClient} instance with default logging consumer but without elicitation ability.
+ * Creates a {@link McpClient} instance.
*
* @param baseUri The base URI of the MCP server.
* @param sseEndpoint The SSE endpoint of the MCP server.
- * @return The connected {@link McpClient} instance with default logging consumer but without elicitation ability.
+ * @return The connected {@link McpClient} instance.
*/
McpClient create(String baseUri, String sseEndpoint);
-
- /**
- * Creates a {@link McpClient} instance with custom logging consumer but without elicitation ability.
- *
- * @param baseUri The base URI of the MCP server.
- * @param sseEndpoint The SSE endpoint of the MCP server.
- * @param loggingConsumer The consumer to handle logging messages from the MCP server.
- * @return The connected {@link McpClient} instance with custom logging consumer but without elicitation ability.
- */
- McpClient create(String baseUri, String sseEndpoint,
- Consumer loggingConsumer);
-
- /**
- * Creates a {@link McpClient} instance with default logging consumer and elicitation ability.
- *
- * @param baseUri The base URI of the MCP server.
- * @param sseEndpoint The SSE endpoint of the MCP server.
- * @param elicitationHandler The function to handle elicitation requests from the MCP server.
- * @return The connected {@link McpClient} instance with default logging consumer and elicitation ability.
- */
- McpClient create(String baseUri, String sseEndpoint,
- Function elicitationHandler);
-
- /**
- * Creates a {@link McpClient} instance with custom message handlers and elicitation ability.
- *
- * @param baseUri The base URI of the MCP server.
- * @param sseEndpoint The SSE endpoint of the MCP server.
- * @param loggingConsumer The consumer to handle logging messages from the MCP server.
- * @param elicitationHandler The function to handle elicitation requests from the MCP server.
- * @return The connected {@link McpClient} instance with custom message handlers and elicitation ability.
- */
- McpClient create(String baseUri, String sseEndpoint, Consumer loggingConsumer,
- Function elicitationHandler);
}
\ No newline at end of file
From 9c03bb171c7a8ae4e240f3ae75a9b874d4326eb2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=BB=84=E5=8F=AF=E6=AC=A3?= <3200105739@zju.edu.cn>
Date: Sun, 9 Nov 2025 16:00:53 +0800
Subject: [PATCH 13/16] =?UTF-8?q?=E6=81=A2=E5=A4=8Dtestcontroller?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../fel/tool/mcp/test/TestController.java | 32 ++-----------------
1 file changed, 3 insertions(+), 29 deletions(-)
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 a8d1e59b..ead53548 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
@@ -6,7 +6,6 @@
package modelengine.fel.tool.mcp.test;
-import io.modelcontextprotocol.spec.McpSchema;
import modelengine.fel.tool.mcp.client.McpClient;
import modelengine.fel.tool.mcp.client.McpClientFactory;
import modelengine.fel.tool.mcp.entity.Tool;
@@ -19,7 +18,6 @@
import modelengine.fitframework.log.Logger;
import java.io.IOException;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -48,12 +46,12 @@ public TestController(McpClientFactory mcpClientFactory) {
}
/**
- * Initializes the MCP client .
- * This method creates an instance using the provided factory and initializes it.
+ * Initializes the MCP client by creating an instance using the provided factory and initializing it.
+ * This method sets up the connection to the MCP server and prepares it for further interactions.
*
* @param baseUri The base URI of the MCP server.
* @param sseEndpoint The SSE endpoint of the MCP server.
- * @return A map with clientId and status message.
+ * @return A string indicating that the initialization was successful.
*/
@PostMapping(path = "/initialize")
public String initialize(@RequestQuery(name = "baseUri") String baseUri,
@@ -102,28 +100,4 @@ public List toolsList() {
public Object toolsCall(@RequestQuery(name = "name") String name, @RequestBody Map jsonArgs) {
return this.client.callTool(name, jsonArgs);
}
-
- /**
- * Custom logging consumer for MCP client.
- *
- * @param notification The logging message notification from MCP server.
- */
- private void loggingConsumer(McpSchema.LoggingMessageNotification notification) {
- log.info("Custom logging handler received message. [level={}, data={}]",
- notification.level(),
- notification.data());
- }
-
- /**
- * Elicitation handler for MCP client.
- *
- * @param request The elicitation request from MCP server.
- * @return The elicitation result with action and user data.
- */
- private McpSchema.ElicitResult elicitationHandler(McpSchema.ElicitRequest request) {
- log.info("Elicitation request received. [message={}, schema={}]", request.message(), request.requestedSchema());
- Map userData = new HashMap<>();
- userData.put("response", "Auto-accepted by test controller");
- return new McpSchema.ElicitResult(McpSchema.ElicitResult.Action.ACCEPT, userData);
- }
}
\ No newline at end of file
From 89c4443013bfe4cfa1d2353ab37b648e5c563244 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=BB=84=E5=8F=AF=E6=AC=A3?= <3200105739@zju.edu.cn>
Date: Sun, 9 Nov 2025 16:01:48 +0800
Subject: [PATCH 14/16] =?UTF-8?q?=E5=88=A0=E9=99=A4testcontroller=E7=9A=84?=
=?UTF-8?q?log=E4=BE=9D=E8=B5=96?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../java/modelengine/fel/tool/mcp/test/TestController.java | 3 ---
1 file changed, 3 deletions(-)
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 ead53548..110dc3cb 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
@@ -15,7 +15,6 @@
import modelengine.fit.http.annotation.RequestMapping;
import modelengine.fit.http.annotation.RequestQuery;
import modelengine.fitframework.annotation.Component;
-import modelengine.fitframework.log.Logger;
import java.io.IOException;
import java.util.List;
@@ -31,8 +30,6 @@
@Component
@RequestMapping(path = "/mcp-test")
public class TestController {
- private static final Logger log = Logger.get(TestController.class);
-
private final McpClientFactory mcpClientFactory;
private McpClient client;
From a5438f6981c1a84fead934de1810e47145a14ab9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=BB=84=E5=8F=AF=E6=AC=A3?= <3200105739@zju.edu.cn>
Date: Sun, 9 Nov 2025 16:02:44 +0800
Subject: [PATCH 15/16] =?UTF-8?q?=E5=88=A0=E9=99=A4service=E7=9A=84sdk?=
=?UTF-8?q?=E4=BE=9D=E8=B5=96?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
framework/fel/java/services/tool-mcp-client-service/pom.xml | 5 -----
1 file changed, 5 deletions(-)
diff --git a/framework/fel/java/services/tool-mcp-client-service/pom.xml b/framework/fel/java/services/tool-mcp-client-service/pom.xml
index a2f0cb0a..8a2fb8f1 100644
--- a/framework/fel/java/services/tool-mcp-client-service/pom.xml
+++ b/framework/fel/java/services/tool-mcp-client-service/pom.xml
@@ -33,11 +33,6 @@
org.fitframework.fel
tool-mcp-common
-