This method generates a unique session ID and registers an emitter to send events.
+ * + * @param response The HTTP server response object used to manage the SSE connection as a + * {@link HttpClassicServerResponse}. + * @return A {@link Choir}{@code <}{@link TextEvent}{@code >} object that emits text events to the connected client. + */ + @GetMapping(path = "/sse") + public ChoirThis method handles incoming JSON-RPC requests, routes them to the appropriate handler, + * and returns a response via the associated event emitter.
+ * + * @param sessionId The session ID used to identify the current client session. + * @param request The JSON-RPC request entity containing the method name and parameters. + * @return Always returns an empty string ({@value #RESPONSE_OK}) to indicate success. + */ + @PostMapping(path = MESSAGE_PATH) + public Object receiveMcpMessage(@RequestQuery(name = "sessionId") String sessionId, + @RequestBody JsonRpcEntity request) { + log.info("Receive MCP message. [sessionId={}, request={}]", sessionId, request); + Object id = request.getId(); + if (id == null) { + // Request without an ID indicates a notification message, ignore. + return RESPONSE_OK; + } + MessageHandler handler = this.methodHandlers.getOrDefault(request.getMethod(), this.unsupportedMethodHandler); + JsonRpcEntity response = new JsonRpcEntity(); + response.setId(id); + try { + Object result = handler.handle(request.getParams()); + response.setResult(result); + } catch (Exception e) { + log.error("Failed to handle MCP message.", e); + response.setError(e.getMessage()); + } + String serialized = this.serializer.serialize(response); + TextEvent textEvent = TextEvent.custom().id(sessionId).event(EVENT_MESSAGE).data(serialized).build(); + EmitterEach content item has a type and text value, which can be used to represent + * the result or error message from the tool execution.
+ * + * @author 季聿阶 + * @since 2025-05-15 + */ + public static class ToolCallResponse extends MessageResponse { + @Property(name = "content") + private ListThis class supports multiple content formats, allowing flexible representation + * of the tool's output.
+ * + * @author 季聿阶 + * @since 2025-05-15 + */ + public static class Content { + private String type; + private String text; + + /** + * Gets the type of the content item. + * + * @return The type of the content as a {@link String}. + */ + public String getType() { + return this.type; + } + + /** + * Sets the type of the content item. + * + * @param type The type of the content as a {@link String}. + */ + public void setType(String type) { + this.type = type; + } + + /** + * Gets the text value of the content item. + * + * @return The text value as a {@link String}. + */ + public String getText() { + return this.text; + } + + /** + * Sets the text value of the content item. + * + * @param text The text value as a {@link String}. + */ + public void setText(String text) { + this.text = text; + } + } + } +} diff --git a/framework/fel/java/plugins/tool-mcp-server/src/main/java/modelengine/fel/tool/mcp/server/handler/ToolListHandler.java b/framework/fel/java/plugins/tool-mcp-server/src/main/java/modelengine/fel/tool/mcp/server/handler/ToolListHandler.java new file mode 100644 index 00000000..e21fa574 --- /dev/null +++ b/framework/fel/java/plugins/tool-mcp-server/src/main/java/modelengine/fel/tool/mcp/server/handler/ToolListHandler.java @@ -0,0 +1,52 @@ +/*--------------------------------------------------------------------------------------------- + * 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.server.handler; + +import static modelengine.fitframework.inspection.Validation.notNull; + +import modelengine.fel.tool.mcp.server.McpServer; +import modelengine.fel.tool.mcp.server.MessageRequest; +import modelengine.fitframework.util.MapBuilder; + +/** + * A handler for processing tool list requests in the MCP server. + * This class extends {@link AbstractMessageHandler} and is responsible for handling + * {@link ToolListRequest} messages by retrieving the list of tools from the associated {@link McpServer} + * and returning them in a structured map format. + * + * @author 季聿阶 + * @since 2025-05-15 + */ +public class ToolListHandler extends AbstractMessageHandlerThis handler is used to handle requests for methods that are not supported or implemented.
+ */ + public UnsupportedMethodHandler() { + super(UnsupportedMethodRequest.class); + } + + @Override + public MessageResponse handle(UnsupportedMethodRequest request) { + throw new UnsupportedOperationException("Not supported request method."); + } + + /** + * Represents a request for an operation that is not supported by the current handler. + * This class is used in conjunction with {@link UnsupportedMethodHandler} to signal + * that the requested method has no implementation. + * + * @author 季聿阶 + * @since 2025-05-15 + */ + public static class UnsupportedMethodRequest extends MessageRequest {} +} diff --git a/framework/fel/java/plugins/tool-mcp-server/src/main/java/modelengine/fel/tool/mcp/server/support/DefaultMcpServer.java b/framework/fel/java/plugins/tool-mcp-server/src/main/java/modelengine/fel/tool/mcp/server/support/DefaultMcpServer.java new file mode 100644 index 00000000..76f765b2 --- /dev/null +++ b/framework/fel/java/plugins/tool-mcp-server/src/main/java/modelengine/fel/tool/mcp/server/support/DefaultMcpServer.java @@ -0,0 +1,106 @@ +/*--------------------------------------------------------------------------------------------- + * 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.server.support; + +import static modelengine.fitframework.inspection.Validation.notNull; + +import modelengine.fel.tool.mcp.server.McpServer; +import modelengine.fel.tool.mcp.server.entity.ToolEntity; +import modelengine.fel.tool.service.ToolChangedObserver; +import modelengine.fel.tool.service.ToolExecuteService; +import modelengine.fitframework.annotation.Component; +import modelengine.fitframework.log.Logger; +import modelengine.fitframework.util.MapBuilder; +import modelengine.fitframework.util.MapUtils; +import modelengine.fitframework.util.StringUtils; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * The default implementation of {@link McpServer}. + * + * @author 季聿阶 + * @since 2025-05-15 + */ +@Component +public class DefaultMcpServer implements McpServer, ToolChangedObserver { + private static final Logger log = Logger.get(DefaultMcpServer.class); + + private final ToolExecuteService toolExecuteService; + private final MapAn inactive request typically indicates that the underlying connection has been closed or reset.
+ * + * @return true if the request is active; false otherwise. + */ + boolean isActive(); + + /** + * Creates an instance of a classic HTTP server request. + * + * @param httpResource The HTTP resource associated with the request, as a {@link HttpResource}. + * @param serverRequest The underlying server request, as a {@link ServerRequest}. + * @return A newly created instance of {@link HttpClassicServerRequest}. */ static HttpClassicServerRequest create(HttpResource httpResource, ServerRequest serverRequest) { return new DefaultHttpClassicServerRequest(httpResource, serverRequest); diff --git a/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/server/HttpClassicServerResponse.java b/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/server/HttpClassicServerResponse.java index 6c531f86..f74edc45 100644 --- a/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/server/HttpClassicServerResponse.java +++ b/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/server/HttpClassicServerResponse.java @@ -19,71 +19,85 @@ import java.io.IOException; /** - * 表示经典的服务端的 Http 响应。 + * Represents a classic HTTP server response. * * @author 季聿阶 * @since 2022-11-25 */ public interface HttpClassicServerResponse extends HttpClassicResponse, Closeable { /** - * 设置 Http 响应的状态码。 + * Sets the status code for the HTTP response. * - * @param statusCode 表示 Http 响应的状态码的 {@code int}。 + * @param statusCode The HTTP status code as an {@code int}. */ void statusCode(int statusCode); /** - * 设置 Http 响应的状态信息。 + * Sets the reason phrase for the HTTP response. * - * @param reasonPhrase 表示 Http 响应的状态信息的 {@link String}。 + * @param reasonPhrase The reason phrase describing the status, as a {@link String}. */ void reasonPhrase(String reasonPhrase); /** - * 获取 Http 响应的消息头集合。 - *注意:如果要修改 Cookie 相关的信息,请不要直接在当前对象中操作,请使用 {@link #cookies()} 进行修改。
+ * Gets the collection of message headers for the HTTP response. * - * @return 表示 Http 响应的消息头集合的 {@link ConfigurableMessageHeaders}。 + *Note: To modify cookie-related information, do not operate directly on this object. + * Use {@link #cookies()} instead.
+ * + * @return The configurable message headers of the HTTP response as a {@link ConfigurableMessageHeaders}. */ @Override ConfigurableMessageHeaders headers(); /** - * 获取 Http 响应的 Cookie 集合。 + * Gets the collection of cookies included in the HTTP response. * - * @return 表示 Http 响应的 Cookie 集合的 {@link ConfigurableCookieCollection}。 + * @return The configurable cookie collection of the HTTP response as a {@link ConfigurableCookieCollection}. */ @Override ConfigurableCookieCollection cookies(); /** - * 设置 Http 响应的消息体的结构化数据。 - *注意:该方法与 {@link #writableBinaryEntity()} 不能同时使用。
+ * Sets the structured data of the HTTP response body. + * + *Note: This method cannot be used together with {@link #writableBinaryEntity()}.
* - * @param entity 表示待设置的 Http 响应的消息体的结构化数据的 {@link Entity}。 + * @param entity The structured data to be set as the HTTP response body, as an {@link Entity}. */ void entity(Entity entity); /** - * 获取 Http 响应的返回输出流。 - *注意:该方法与 {@link #entity(Entity)} 不能同时使用,调用该方法时,会立即将 Http 消息头发送。
+ * Gets the output stream for writing binary data to the HTTP response body. * - * @return 表示 Http 响应的返回输出流的 {@link WritableBinaryEntity}。 - * @throws IOException 写入过程发生 IO 异常。 + *Note: This method cannot be used together with {@link #entity(Entity)}. + * Invoking this method will immediately send the HTTP headers.
+ * + * @return A {@link WritableBinaryEntity} representing the output stream for the response body. + * @throws IOException If an I/O error occurs during writing. */ WritableBinaryEntity writableBinaryEntity() throws IOException; /** - * 发送当前 Http 响应。 + * Sends the current HTTP response to the client. */ void send(); /** - * 创建经典的服务端的 Http 响应对象。 + * Checks whether the current response is active. + * + *An inactive response typically indicates that the underlying connection has been closed or reset.
+ * + * @return true if the response is active; false otherwise. + */ + boolean isActive(); + + /** + * Creates an instance of a classic HTTP server response. * - * @param httpResource 表示 Http 的资源的 {@link HttpResource}。 - * @param serverResponse 表示服务端的 Http 响应的 {@link ServerResponse}。 - * @return 表示创建的经典的服务端的 Http 响应对象的 {@link HttpClassicServerResponse}。 + * @param httpResource The HTTP resource associated with the response, as a {@link HttpResource}. + * @param serverResponse The underlying server response, as a {@link ServerResponse}. + * @return A newly created instance of {@link HttpClassicServerResponse}. */ static HttpClassicServerResponse create(HttpResource httpResource, ServerResponse serverResponse) { return new DefaultHttpClassicServerResponse(httpResource, serverResponse); diff --git a/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/server/support/DefaultHttpClassicServerRequest.java b/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/server/support/DefaultHttpClassicServerRequest.java index aa4c251e..4804c61b 100644 --- a/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/server/support/DefaultHttpClassicServerRequest.java +++ b/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/server/support/DefaultHttpClassicServerRequest.java @@ -86,6 +86,11 @@ public byte[] entityBytes() { return this.entityBytesLoader.get(); } + @Override + public boolean isActive() { + return this.serverRequest.isActive(); + } + private OptionalAn inactive request typically indicates that the underlying connection has been closed or reset.
+ * + * @return true if the request is active; false otherwise. + */ + boolean isActive(); } diff --git a/framework/fit/java/fit-builtin/services/fit-http-protocol/definition/src/main/java/modelengine/fit/http/protocol/ServerResponse.java b/framework/fit/java/fit-builtin/services/fit-http-protocol/definition/src/main/java/modelengine/fit/http/protocol/ServerResponse.java index 6d3ba00d..c8e886b4 100644 --- a/framework/fit/java/fit-builtin/services/fit-http-protocol/definition/src/main/java/modelengine/fit/http/protocol/ServerResponse.java +++ b/framework/fit/java/fit-builtin/services/fit-http-protocol/definition/src/main/java/modelengine/fit/http/protocol/ServerResponse.java @@ -12,7 +12,7 @@ import java.io.OutputStream; /** - * 表示服务端的 Http 响应。 + * Represents an HTTP response on the server side. * * @author 季聿阶 * @since 2022-07-05 @@ -20,27 +20,26 @@ public interface ServerResponse extends Message实际上,这里的数据 {@code b} 一定是一个 {@code byte}。
- * @throws IOException 当发生 I/O 异常时。 + * @param b The data to be written, represented as an int. Only the least significant byte is used. + * @throws IOException If an I/O error occurs. */ void writeBody(int b) throws IOException; /** - * 向 Http 消息体中写入数据。 + * Writes the entire contents of the specified byte array to the HTTP message body. * - * @param bytes 表示待写入数据的 {@code byte[]}。 - * @throws IOException 当发生 I/O 异常时。 - * @throws IllegalArgumentException 当 {@code bytes} 为 {@code null} 时。 + * @param bytes The byte array containing the data to be written. + * @throws IOException If an I/O error occurs. + * @throws IllegalArgumentException If {@code bytes} is null. * @see #writeBody(byte[], int, int) */ default void writeBody(byte[] bytes) throws IOException { @@ -48,29 +47,39 @@ default void writeBody(byte[] bytes) throws IOException { } /** - * 向 Http 消息体中写入数据。 + * Writes up to {@code len} bytes from the specified byte array, + * starting at offset {@code off}, to the HTTP message body. * - * @param bytes 表示待写入数据所在数组的 {@code byte[]}。 - * @param off 表示待写入数据的偏移量的 {@code int}。 - * @param len 表示待写入数据的数量的 {@code int}。 - * @throws IOException 当发生 I/O 异常时。 - * @throws IllegalArgumentException 当 {@code bytes} 为 {@code null} 时。 - * @throws IndexOutOfBoundsException 当 {@code off} 或 {@code len} 为负数时,或 {@code off + len} - * 超过了 {@code bytes} 的长度时。 + * @param bytes The byte array containing the data to be written. + * @param off The start offset in the source array {@code bytes}. + * @param len The number of bytes to write. + * @throws IOException If an I/O error occurs. + * @throws IllegalArgumentException If {@code bytes} is null. + * @throws IndexOutOfBoundsException If {@code off} or {@code len} is negative, + * or if {@code off + len} exceeds the length of {@code bytes}. */ void writeBody(byte[] bytes, int off, int len) throws IOException; /** - * 强制已经写入的数据执行写出,也就是说将之前写入到缓冲区的数据全部对外输出;同时发送响应结束标识符。 + * Forces any buffered data to be written out immediately and sends the response end marker. * - * @throws IOException 当发生 I/O 异常时。 + * @throws IOException If an I/O error occurs. */ void flush() throws IOException; /** - * 获取消息体的输出流。 + * Gets the output stream for writing the body of the HTTP message. * - * @return 表示消息体的输出流的 {@link OutputStream}。 + * @return An {@link OutputStream} representing the body content of the HTTP response. */ OutputStream getBodyOutputStream(); + + /** + * Checks whether the current response is active. + * + *An inactive response typically indicates that the underlying connection has been closed or reset.
+ * + * @return true if the response is active; false otherwise. + */ + boolean isActive(); }