protocolVersions) {
+ this.protocolVersions = protocolVersions;
+ }
+
+}
diff --git a/labs/arthas-mcp-server/src/main/java/com/taobao/arthas/mcp/server/protocol/server/McpNettyServerExchange.java b/labs/arthas-mcp-server/src/main/java/com/taobao/arthas/mcp/server/protocol/server/McpNettyServerExchange.java
new file mode 100644
index 000000000..45a31035b
--- /dev/null
+++ b/labs/arthas-mcp-server/src/main/java/com/taobao/arthas/mcp/server/protocol/server/McpNettyServerExchange.java
@@ -0,0 +1,214 @@
+package com.taobao.arthas.mcp.server.protocol.server;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.taobao.arthas.mcp.server.protocol.spec.McpError;
+import com.taobao.arthas.mcp.server.protocol.spec.McpSchema;
+import com.taobao.arthas.mcp.server.protocol.spec.McpSchema.LoggingLevel;
+import com.taobao.arthas.mcp.server.protocol.spec.McpSchema.LoggingMessageNotification;
+import com.taobao.arthas.mcp.server.protocol.spec.McpServerSession;
+import com.taobao.arthas.mcp.server.util.Assert;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.atomic.AtomicLong;
+
+
+
+/**
+ * Represents the interaction between MCP server and client. Provides methods for communication, logging, and context management.
+ *
+ * @author Yeaury
+ *
+ * McpNettyServerExchange provides various methods for communicating with the client, including:
+ *
+ * - Sending requests and notifications
+ *
- Getting client capabilities and information
+ *
- Handling logging notifications
+ *
- Creating client messages
+ *
- Managing root directories
+ *
+ *
+ * Each exchange object is associated with a specific client session, providing context and capabilities for that session.
+ */
+public class McpNettyServerExchange {
+
+ private static final Logger logger = LoggerFactory.getLogger(McpNettyServerExchange.class);
+
+ private final McpServerSession session;
+
+ private final McpSchema.ClientCapabilities clientCapabilities;
+
+ private final McpSchema.Implementation clientInfo;
+
+ private volatile LoggingLevel minLoggingLevel = LoggingLevel.INFO;
+
+ private static final TypeReference CREATE_MESSAGE_RESULT_TYPE_REF =
+ new TypeReference() {
+ };
+
+ private static final TypeReference LIST_ROOTS_RESULT_TYPE_REF =
+ new TypeReference() {
+ };
+
+ /**
+ * Create a new server exchange object.
+ * @param session Session associated with the client
+ * @param clientCapabilities Client capabilities
+ * @param clientInfo Client information
+ */
+ public McpNettyServerExchange(McpServerSession session, McpSchema.ClientCapabilities clientCapabilities,
+ McpSchema.Implementation clientInfo) {
+ Assert.notNull(session, "Session cannot be null");
+ this.session = session;
+ this.clientCapabilities = clientCapabilities;
+ this.clientInfo = clientInfo;
+ logger.debug("Created new server exchange, session ID: {}, client: {}", session.getId(), clientInfo);
+ }
+
+ /**
+ * Create a new server exchange object. This constructor is used when the session exists but client capabilities and information are unknown.
+ * For example, for exchange objects created in request handlers.
+ * @param session Server session
+ * @param objectMapper JSON object mapper
+ */
+ public McpNettyServerExchange(McpServerSession session, ObjectMapper objectMapper) {
+ Assert.notNull(session, "Session cannot be null");
+ this.session = session;
+ this.clientCapabilities = null;
+ this.clientInfo = null;
+ logger.debug("Created new server exchange, session ID: {}, client info unknown", session.getId());
+ }
+
+ /**
+ * Get client capabilities.
+ * @return Client capabilities
+ */
+ public McpSchema.ClientCapabilities getClientCapabilities() {
+ return this.clientCapabilities;
+ }
+
+ /**
+ * Get client information.
+ * @return Client information
+ */
+ public McpSchema.Implementation getClientInfo() {
+ return this.clientInfo;
+ }
+
+ /**
+ * Send a notification with parameters to the client.
+ * @param method Notification method name to send to the client
+ * @param params Parameters to send with the notification
+ * @return A CompletableFuture that completes when the notification is sent
+ */
+ public CompletableFuture sendNotification(String method, Object params) {
+ Assert.hasText(method, "Method name cannot be empty");
+
+ logger.debug("Sending notification to client - method: {}, session: {}", method, this.session.getId());
+ return this.session.sendNotification(method, params).whenComplete((result, error) -> {
+ if (error != null) {
+ logger.error("Notification failed - method: {}, session: {}, error: {}", method, this.session.getId(), error.getMessage());
+ }
+ else {
+ logger.debug("Notification sent successfully - method: {}, session: {}", method, this.session.getId());
+ }
+ });
+ }
+
+ /**
+ * Create a new message using client sampling capability. MCP provides a standardized way for servers to request
+ * LLM sampling ("completion" or "generation") through the client. This flow allows clients to maintain control
+ * over model access, selection, and permissions while enabling servers to leverage AI capabilities—without server
+ * API keys. Servers can request text or image-based interactions and can optionally include context from the MCP
+ * server in their prompts.
+ * @param createMessageRequest Request to create a new message
+ * @return A CompletableFuture that completes when the message is created
+ */
+ public CompletableFuture createMessage(
+ McpSchema.CreateMessageRequest createMessageRequest) {
+ if (this.clientCapabilities == null) {
+ logger.error("Client not initialized, cannot create message");
+ CompletableFuture future = new CompletableFuture<>();
+ future.completeExceptionally(new McpError("Client must be initialized first. Please call initialize method!"));
+ return future;
+ }
+ if (this.clientCapabilities.getSampling() == null) {
+ logger.error("Client not configured with sampling capability, cannot create message");
+ CompletableFuture future = new CompletableFuture<>();
+ future.completeExceptionally(new McpError("Client must be configured with sampling capability"));
+ return future;
+ }
+
+ logger.debug("Creating client message, session ID: {}", this.session.getId());
+ return this.session
+ .sendRequest(McpSchema.METHOD_SAMPLING_CREATE_MESSAGE, createMessageRequest, CREATE_MESSAGE_RESULT_TYPE_REF)
+ .whenComplete((result, error) -> {
+ if (error != null) {
+ logger.error("Failed to create message, session ID: {}, error: {}", this.session.getId(), error.getMessage());
+ }
+ else {
+ logger.debug("Message created successfully, session ID: {}", this.session.getId());
+ }
+ });
+ }
+
+ /**
+ * Get a list of all root directories provided by the client.
+ * @return Sends out the CompletableFuture of the root list result
+ */
+ public CompletableFuture listRoots() {
+ return this.listRoots(null);
+ }
+
+ /**
+ * Get the client-provided list of pagination roots.
+ * Optional pagination cursor @param cursor for the previous list request
+ * @return Emits a CompletableFuture containing the results of the root list
+ */
+ public CompletableFuture listRoots(String cursor) {
+ logger.debug("Requesting root list, session ID: {}, cursor: {}", this.session.getId(), cursor);
+ return this.session
+ .sendRequest(McpSchema.METHOD_ROOTS_LIST, new McpSchema.PaginatedRequest(cursor),
+ LIST_ROOTS_RESULT_TYPE_REF)
+ .whenComplete((result, error) -> {
+ if (error != null) {
+ logger.error("Failed to get root list, session ID: {}, error: {}", this.session.getId(), error.getMessage());
+ }
+ else {
+ logger.debug("Root list retrieved successfully, session ID: {}", this.session.getId());
+ }
+ });
+ }
+
+ public CompletableFuture loggingNotification(LoggingMessageNotification loggingMessageNotification) {
+ if (loggingMessageNotification == null) {
+ CompletableFuture future = new CompletableFuture<>();
+ future.completeExceptionally(new McpError("日志消息不能为空"));
+ return future;
+ }
+
+ if (this.isNotificationForLevelAllowed(loggingMessageNotification.getLevel())) {
+ return this.session.sendNotification(McpSchema.METHOD_NOTIFICATION_MESSAGE, loggingMessageNotification)
+ .whenComplete((result, error) -> {
+ if (error != null) {
+ logger.error("Failed to send logging notification, level: {}, session ID: {}, error: {}", loggingMessageNotification.getLevel(),
+ this.session.getId(), error.getMessage());
+ }
+ });
+ }
+ return CompletableFuture.completedFuture(null);
+ }
+
+ public void setMinLoggingLevel(LoggingLevel minLoggingLevel) {
+ Assert.notNull(minLoggingLevel, "最低日志级别不能为空");
+ logger.debug("Setting minimum logging level: {}, session ID: {}", minLoggingLevel, this.session.getId());
+ this.minLoggingLevel = minLoggingLevel;
+ }
+
+ private boolean isNotificationForLevelAllowed(LoggingLevel loggingLevel) {
+ return loggingLevel.level() >= this.minLoggingLevel.level();
+ }
+
+}
diff --git a/labs/arthas-mcp-server/src/main/java/com/taobao/arthas/mcp/server/protocol/server/McpServer.java b/labs/arthas-mcp-server/src/main/java/com/taobao/arthas/mcp/server/protocol/server/McpServer.java
new file mode 100644
index 000000000..10dc77374
--- /dev/null
+++ b/labs/arthas-mcp-server/src/main/java/com/taobao/arthas/mcp/server/protocol/server/McpServer.java
@@ -0,0 +1,242 @@
+package com.taobao.arthas.mcp.server.protocol.server;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.taobao.arthas.mcp.server.protocol.spec.*;
+import com.taobao.arthas.mcp.server.util.Assert;
+
+import java.time.Duration;
+import java.util.*;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.BiFunction;
+
+/**
+ * MCP server interface and builder for Netty-based implementation.
+ *
+ * @author Yeaury
+ */
+public interface McpServer {
+
+ static NettySpecification netty(McpServerTransportProvider transportProvider) {
+ return new NettySpecification(transportProvider);
+ }
+
+ /**
+ * serverSpecification
+ */
+ class NettySpecification {
+
+ private static final McpSchema.Implementation DEFAULT_SERVER_INFO = new McpSchema.Implementation("mcp-server",
+ "1.0.0");
+
+ private final McpServerTransportProvider transportProvider;
+
+ private ObjectMapper objectMapper;
+
+ private McpSchema.Implementation serverInfo = DEFAULT_SERVER_INFO;
+
+ private McpSchema.ServerCapabilities serverCapabilities;
+
+ private String instructions;
+
+ /**
+ * The Model Context Protocol (MCP) allows servers to expose tools that can be
+ * invoked by language models. Tools enable models to interact with external
+ * systems, such as querying databases, calling APIs, or performing computations.
+ * Each tool is uniquely identified by a name and includes metadata describing its
+ * schema.
+ */
+ private final List tools = new ArrayList<>();
+
+ /**
+ * The Model Context Protocol (MCP) provides a standardized way for servers to
+ * expose resources to clients. Resources allow servers to share data that
+ * provides context to language models, such as files, database schemas, or
+ * application-specific information. Each resource is uniquely identified by a
+ * URI.
+ */
+ private final Map resources = new HashMap<>();
+
+ private final List resourceTemplates = new ArrayList<>();
+
+ /**
+ * The Model Context Protocol (MCP) provides a standardized way for servers to
+ * expose prompt templates to clients. Prompts allow servers to provide structured
+ * messages and instructions for interacting with language models. Clients can
+ * discover available prompts, retrieve their contents, and provide arguments to
+ * customize them.
+ */
+ private final Map prompts = new HashMap<>();
+
+ private final List, CompletableFuture>> rootsChangeHandlers = new ArrayList<>();
+
+ private Duration requestTimeout = Duration.ofSeconds(10); // Default timeout
+
+ private NettySpecification(McpServerTransportProvider transportProvider) {
+ Assert.notNull(transportProvider, "Transport provider must not be null");
+ this.transportProvider = transportProvider;
+ }
+
+ /**
+ * Sets the server implementation information that will be shared with clients
+ * during connection initialization. This helps with version compatibility,
+ * debugging, and server identification.
+ * @param serverInfo The server implementation details including name and version.
+ * Must not be null.
+ * @return This builder instance for method chaining
+ * @throws IllegalArgumentException if serverInfo is null
+ */
+ public NettySpecification serverInfo(McpSchema.Implementation serverInfo) {
+ Assert.notNull(serverInfo, "Server info must not be null");
+ this.serverInfo = serverInfo;
+ return this;
+ }
+
+ public NettySpecification requestTimeout(Duration requestTimeout) {
+ Assert.notNull(requestTimeout, "Request timeout must not be null");
+ this.requestTimeout = requestTimeout;
+ return this;
+ }
+
+ public NettySpecification serverInfo(String name, String version) {
+ Assert.hasText(name, "Name must not be null or empty");
+ Assert.hasText(version, "Version must not be null or empty");
+ this.serverInfo = new McpSchema.Implementation(name, version);
+ return this;
+ }
+
+ public NettySpecification instructions(String instructions) {
+ this.instructions = instructions;
+ return this;
+ }
+
+ public NettySpecification capabilities(McpSchema.ServerCapabilities serverCapabilities) {
+ this.serverCapabilities = serverCapabilities;
+ return this;
+ }
+
+ public NettySpecification tool(McpSchema.Tool tool,
+ BiFunction, CompletableFuture> handler) {
+ Assert.notNull(tool, "Tool must not be null");
+ Assert.notNull(handler, "Handler must not be null");
+
+ this.tools.add(new McpServerFeatures.ToolSpecification(tool, handler));
+
+ return this;
+ }
+
+ public NettySpecification tools(List toolRegistrations) {
+ Assert.notNull(toolRegistrations, "Tool handlers list must not be null");
+ this.tools.addAll(toolRegistrations);
+ return this;
+ }
+
+ public NettySpecification tools(McpServerFeatures.ToolSpecification... toolRegistrations) {
+ for (McpServerFeatures.ToolSpecification tool : toolRegistrations) {
+ this.tools.add(tool);
+ }
+ return this;
+ }
+
+ public NettySpecification resources(Map resourceSpecifications) {
+ Assert.notNull(resourceSpecifications, "Resource handlers map must not be null");
+ this.resources.putAll(resourceSpecifications);
+ return this;
+ }
+
+ public NettySpecification resources(List resourceSpecifications) {
+ Assert.notNull(resourceSpecifications, "Resource handlers list must not be null");
+ for (McpServerFeatures.ResourceSpecification resource : resourceSpecifications) {
+ this.resources.put(resource.getResource().getUri(), resource);
+ }
+ return this;
+ }
+
+ public NettySpecification resources(McpServerFeatures.ResourceSpecification... resourceSpecifications) {
+ Assert.notNull(resourceSpecifications, "Resource handlers list must not be null");
+ for (McpServerFeatures.ResourceSpecification resource : resourceSpecifications) {
+ this.resources.put(resource.getResource().getUri(), resource);
+ }
+ return this;
+ }
+
+ public NettySpecification resourceTemplates(List resourceTemplates) {
+ Assert.notNull(resourceTemplates, "Resource templates must not be null");
+ this.resourceTemplates.addAll(resourceTemplates);
+ return this;
+ }
+
+ public NettySpecification resourceTemplates(McpSchema.ResourceTemplate... resourceTemplates) {
+ Assert.notNull(resourceTemplates, "Resource templates must not be null");
+ this.resourceTemplates.addAll(Arrays.asList(resourceTemplates));
+ return this;
+ }
+
+ public NettySpecification prompts(Map prompts) {
+ Assert.notNull(prompts, "Prompts map must not be null");
+ this.prompts.putAll(prompts);
+ return this;
+ }
+
+ public NettySpecification prompts(List prompts) {
+ Assert.notNull(prompts, "Prompts map must not be null");
+ for (McpServerFeatures.PromptSpecification prompt : prompts) {
+ this.prompts.put(prompt.getPrompt().getName(), prompt);
+ }
+ return this;
+ }
+
+ public NettySpecification prompts(McpServerFeatures.PromptSpecification... prompts) {
+ Assert.notNull(prompts, "Prompts map must not be null");
+ for (McpServerFeatures.PromptSpecification prompt : prompts) {
+ this.prompts.put(prompt.getPrompt().getName(), prompt);
+ }
+ return this;
+ }
+
+ public NettySpecification rootsChangeHandler(
+ BiFunction, CompletableFuture> handler) {
+ Assert.notNull(handler, "Consumer must not be null");
+ this.rootsChangeHandlers.add(handler);
+ return this;
+ }
+
+
+ public NettySpecification rootsChangeHandlers(
+ List, CompletableFuture>> handlers) {
+ Assert.notNull(handlers, "Handlers list must not be null");
+ this.rootsChangeHandlers.addAll(handlers);
+ return this;
+ }
+
+ public NettySpecification rootsChangeHandlers(
+ @SuppressWarnings("unchecked") BiFunction, CompletableFuture>... handlers) {
+ Assert.notNull(handlers, "Handlers list must not be null");
+ return this.rootsChangeHandlers(Arrays.asList(handlers));
+ }
+
+ public NettySpecification objectMapper(ObjectMapper objectMapper) {
+ Assert.notNull(objectMapper, "ObjectMapper must not be null");
+ this.objectMapper = objectMapper;
+ return this;
+ }
+
+ public McpNettyServer build() {
+ ObjectMapper mapper = this.objectMapper != null ? this.objectMapper : new ObjectMapper();
+ return new McpNettyServer(
+ this.transportProvider,
+ mapper,
+ this.requestTimeout,
+ new McpServerFeatures.McpServerConfig(
+ this.serverInfo,
+ this.serverCapabilities,
+ this.tools,
+ this.resources,
+ this.resourceTemplates,
+ this.prompts,
+ this.rootsChangeHandlers,
+ this.instructions
+ )
+ );
+ }
+ }
+}
diff --git a/labs/arthas-mcp-server/src/main/java/com/taobao/arthas/mcp/server/protocol/server/McpServerFeatures.java b/labs/arthas-mcp-server/src/main/java/com/taobao/arthas/mcp/server/protocol/server/McpServerFeatures.java
new file mode 100644
index 000000000..efc75014d
--- /dev/null
+++ b/labs/arthas-mcp-server/src/main/java/com/taobao/arthas/mcp/server/protocol/server/McpServerFeatures.java
@@ -0,0 +1,219 @@
+package com.taobao.arthas.mcp.server.protocol.server;
+
+import com.taobao.arthas.mcp.server.protocol.spec.*;
+import com.taobao.arthas.mcp.server.util.Assert;
+import com.taobao.arthas.mcp.server.util.Utils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.BiFunction;
+
+/**
+ * MCP server function specification, the server can choose the supported features.
+ * This implementation only provides an asynchronous API.
+ *
+ * @author Yeaury
+ */
+public class McpServerFeatures {
+
+ public static class McpServerConfig {
+ private final McpSchema.Implementation serverInfo;
+ private final McpSchema.ServerCapabilities serverCapabilities;
+ private final List tools;
+ private final Map resources;
+ private final List resourceTemplates;
+ private final Map prompts;
+ private final List, CompletableFuture>> rootsChangeConsumers;
+ private final String instructions;
+
+ public McpServerConfig(
+ McpSchema.Implementation serverInfo,
+ McpSchema.ServerCapabilities serverCapabilities,
+ List tools,
+ Map resources,
+ List resourceTemplates,
+ Map prompts,
+ List, CompletableFuture>> rootsChangeConsumers,
+ String instructions) {
+
+ Assert.notNull(serverInfo, "The server information cannot be empty");
+
+ // If serverCapabilities is empty, the appropriate capability configuration
+ // is automatically built based on the provided capabilities
+ if (serverCapabilities == null) {
+ serverCapabilities = new McpSchema.ServerCapabilities(
+ null, // experimental
+ new McpSchema.ServerCapabilities.LoggingCapabilities(),
+ !Utils.isEmpty(prompts) ? new McpSchema.ServerCapabilities.PromptCapabilities(false) : null,
+ !Utils.isEmpty(resources) ? new McpSchema.ServerCapabilities.ResourceCapabilities(false, false) : null,
+ !Utils.isEmpty(tools) ? new McpSchema.ServerCapabilities.ToolCapabilities(false) : null);
+ }
+
+ this.tools = (tools != null) ? tools : Collections.emptyList();
+ this.resources = (resources != null) ? resources : Collections.emptyMap();
+ this.resourceTemplates = (resourceTemplates != null) ? resourceTemplates : Collections.emptyList();
+ this.prompts = (prompts != null) ? prompts : Collections.emptyMap();
+ this.rootsChangeConsumers = (rootsChangeConsumers != null) ? rootsChangeConsumers : Collections.emptyList();
+ this.serverInfo = serverInfo;
+ this.serverCapabilities = serverCapabilities;
+ this.instructions = instructions;
+ }
+
+ public McpSchema.Implementation getServerInfo() {
+ return serverInfo;
+ }
+
+ public McpSchema.ServerCapabilities getServerCapabilities() {
+ return serverCapabilities;
+ }
+
+ public List getTools() {
+ return tools;
+ }
+
+ public Map getResources() {
+ return resources;
+ }
+
+ public List getResourceTemplates() {
+ return resourceTemplates;
+ }
+
+ public Map getPrompts() {
+ return prompts;
+ }
+
+ public List, CompletableFuture>> getRootsChangeConsumers() {
+ return rootsChangeConsumers;
+ }
+
+ public String getInstructions() {
+ return instructions;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+ private McpSchema.Implementation serverInfo;
+ private McpSchema.ServerCapabilities serverCapabilities;
+ private final List tools = new ArrayList<>();
+ private final Map resources = new HashMap<>();
+ private final List resourceTemplates = new ArrayList<>();
+ private final Map prompts = new HashMap<>();
+ private final List, CompletableFuture>> rootsChangeConsumers = new ArrayList<>();
+ private String instructions;
+
+ public Builder serverInfo(McpSchema.Implementation serverInfo) {
+ this.serverInfo = serverInfo;
+ return this;
+ }
+
+ public Builder serverCapabilities(McpSchema.ServerCapabilities serverCapabilities) {
+ this.serverCapabilities = serverCapabilities;
+ return this;
+ }
+
+ public Builder addTool(ToolSpecification tool) {
+ this.tools.add(tool);
+ return this;
+ }
+
+ public Builder addResource(String key, ResourceSpecification resource) {
+ this.resources.put(key, resource);
+ return this;
+ }
+
+ public Builder addResourceTemplate(McpSchema.ResourceTemplate template) {
+ this.resourceTemplates.add(template);
+ return this;
+ }
+
+ public Builder addPrompt(String key, PromptSpecification prompt) {
+ this.prompts.put(key, prompt);
+ return this;
+ }
+
+ public Builder addRootsChangeConsumer(
+ BiFunction, CompletableFuture> consumer) {
+ this.rootsChangeConsumers.add(consumer);
+ return this;
+ }
+
+ public Builder instructions(String instructions) {
+ this.instructions = instructions;
+ return this;
+ }
+
+ public McpServerConfig build() {
+ return new McpServerConfig(serverInfo, serverCapabilities, tools, resources, resourceTemplates, prompts,
+ rootsChangeConsumers, instructions);
+ }
+ }
+ }
+
+ public static class ToolSpecification {
+ private final McpSchema.Tool tool;
+ private final BiFunction, CompletableFuture> call;
+
+ public ToolSpecification(
+ McpSchema.Tool tool,
+ BiFunction, CompletableFuture> call) {
+ this.tool = tool;
+ this.call = call;
+ }
+
+ public McpSchema.Tool getTool() {
+ return tool;
+ }
+
+ public BiFunction, CompletableFuture> getCall() {
+ return call;
+ }
+ }
+
+ public static class ResourceSpecification {
+ private final McpSchema.Resource resource;
+ private final BiFunction> readHandler;
+
+ public ResourceSpecification(
+ McpSchema.Resource resource,
+ BiFunction> readHandler) {
+ this.resource = resource;
+ this.readHandler = readHandler;
+ }
+
+ public McpSchema.Resource getResource() {
+ return resource;
+ }
+
+ public BiFunction> getReadHandler() {
+ return readHandler;
+ }
+ }
+
+ public static class PromptSpecification {
+ private final McpSchema.Prompt prompt;
+ private final BiFunction> promptHandler;
+
+ public PromptSpecification(
+ McpSchema.Prompt prompt,
+ BiFunction> promptHandler) {
+ this.prompt = prompt;
+ this.promptHandler = promptHandler;
+ }
+
+ public McpSchema.Prompt getPrompt() {
+ return prompt;
+ }
+
+ public BiFunction> getPromptHandler() {
+ return promptHandler;
+ }
+ }
+}
diff --git a/labs/arthas-mcp-server/src/main/java/com/taobao/arthas/mcp/server/protocol/server/handler/McpRequestHandler.java b/labs/arthas-mcp-server/src/main/java/com/taobao/arthas/mcp/server/protocol/server/handler/McpRequestHandler.java
new file mode 100644
index 000000000..9c2d3eebf
--- /dev/null
+++ b/labs/arthas-mcp-server/src/main/java/com/taobao/arthas/mcp/server/protocol/server/handler/McpRequestHandler.java
@@ -0,0 +1,379 @@
+package com.taobao.arthas.mcp.server.protocol.server.handler;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.taobao.arthas.mcp.server.protocol.spec.McpSchema;
+import com.taobao.arthas.mcp.server.protocol.spec.McpServerSession;
+import com.taobao.arthas.mcp.server.protocol.spec.McpServerTransport;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.*;
+import io.netty.handler.codec.http.*;
+import io.netty.util.CharsetUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * MCP (Model Context Protocol) Request Handler
+ * Used to handle model context protocol requests for Arthas.
+ *
+ * @author Yeaury
+ */
+public class McpRequestHandler {
+
+ private static final Logger logger = LoggerFactory.getLogger(McpRequestHandler.class);
+
+ /** Message event type */
+ public static final String MESSAGE_EVENT_TYPE = "message";
+
+ /** Endpoint event type */
+ public static final String ENDPOINT_EVENT_TYPE = "endpoint";
+
+ /** Active session mapping: session ID -> session object */
+ private final Map sessions = new ConcurrentHashMap<>();
+
+ /** SSE connection mapping: session ID -> Channel */
+ private final Map sseChannels = new ConcurrentHashMap<>();
+
+ /** Message endpoint path */
+ private final String messageEndpoint;
+
+ /** SSE endpoint path */
+ private final String sseEndpoint;
+
+ /** Object mapper for JSON serialization/deserialization */
+ private final ObjectMapper objectMapper;
+
+ /** Session factory */
+ private McpServerSession.Factory sessionFactory;
+
+ /**
+ * Create a new McpRequestHandler instance.
+ * @param messageEndpoint Message endpoint path, e.g. "/mcp/message"
+ * @param sseEndpoint SSE endpoint path, e.g. "/mcp"
+ * @param objectMapper Object mapper for JSON serialization/deserialization
+ */
+ public McpRequestHandler(String messageEndpoint,
+ String sseEndpoint, ObjectMapper objectMapper) {
+ this.messageEndpoint = messageEndpoint;
+ this.sseEndpoint = sseEndpoint;
+ this.objectMapper = objectMapper;
+ }
+
+ public void handle(ChannelHandlerContext ctx, FullHttpRequest request){
+ String uri = request.uri();
+
+ if (request.method() == HttpMethod.GET && uri.endsWith(sseEndpoint)) {
+ handleSseRequest(ctx);
+ return;
+ }
+
+ if (request.method() == HttpMethod.POST && uri.contains(messageEndpoint)) {
+ handleMessageRequest(ctx, request);
+ return;
+ }
+
+ if (request.method() == HttpMethod.OPTIONS) {
+ sendOptionsResponse(ctx);
+ return;
+ }
+
+ sendNotFoundResponse(ctx);
+ }
+
+ private void handleSseRequest(ChannelHandlerContext ctx) {
+ String sessionId = UUID.randomUUID().toString();
+ HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
+ response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/event-stream");
+ response.headers().set(HttpHeaderNames.CACHE_CONTROL, "no-cache");
+ response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
+ response.headers().set(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN, "*");
+ response.headers().set(HttpHeaderNames.ACCESS_CONTROL_ALLOW_HEADERS, "*");
+ response.headers().set(HttpHeaderNames.ACCESS_CONTROL_ALLOW_METHODS, "GET, POST, OPTIONS");
+
+ HttpUtil.setTransferEncodingChunked(response, true);
+ ctx.writeAndFlush(response);
+
+ sseChannels.put(sessionId, ctx.channel());
+
+ HttpServerTransport transport = new HttpServerTransport(sessionId, ctx.channel());
+
+ McpServerSession session = sessionFactory.create(transport);
+ sessions.put(sessionId, session);
+
+ String endpointInfo = messageEndpoint + "?sessionId=" + sessionId;
+ sendSseEvent(ctx, ENDPOINT_EVENT_TYPE, endpointInfo);
+
+ logger.debug("SSE connection established, session ID: {}", sessionId);
+ }
+
+ private void handleMessageRequest(ChannelHandlerContext ctx, FullHttpRequest request) {
+ String uri = request.uri();
+ String sessionIdParam = "sessionId=";
+ int sessionIdStart = uri.indexOf(sessionIdParam);
+
+ if (sessionIdStart == -1) {
+ sendErrorResponse(ctx, HttpResponseStatus.BAD_REQUEST, "Missing sessionId parameter");
+ return;
+ }
+ String sessionId = uri.substring(sessionIdStart + sessionIdParam.length());
+
+ McpServerSession session = sessions.get(sessionId);
+ if (session == null) {
+ sendErrorResponse(ctx, HttpResponseStatus.NOT_FOUND, "Session not found");
+ return;
+ }
+
+ ByteBuf content = request.content();
+ String jsonContent = content.toString(CharsetUtil.UTF_8);
+ try {
+ McpSchema.JSONRPCMessage message = McpSchema.deserializeJsonRpcMessage(objectMapper, jsonContent);
+
+ session.handle(message).whenComplete((result, ex) -> {
+ if (ex != null) {
+ logger.error("Error processing message", ex);
+ sendErrorResponse(ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR, ex.getMessage());
+ }
+ else {
+ sendSuccessResponse(ctx);
+ }
+ });
+ }
+ catch (Exception e) {
+ logger.error("Error parsing message", e);
+ sendErrorResponse(ctx, HttpResponseStatus.BAD_REQUEST, "Invalid message format");
+ }
+ }
+
+ private void sendSseEvent(ChannelHandlerContext ctx, String eventType, String data) {
+ StringBuilder eventBuilder = new StringBuilder();
+ eventBuilder.append("event: ").append(eventType).append("\n");
+ eventBuilder.append("data: ").append(data).append("\n\n");
+
+ ByteBuf buffer = Unpooled.copiedBuffer(eventBuilder.toString(), CharsetUtil.UTF_8);
+ ctx.writeAndFlush(new DefaultHttpContent(buffer));
+ }
+
+ private void sendOptionsResponse(ChannelHandlerContext ctx) {
+ FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
+ response.headers().set(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN, "*");
+ response.headers().set(HttpHeaderNames.ACCESS_CONTROL_ALLOW_METHODS, "GET, POST, OPTIONS");
+ response.headers().set(HttpHeaderNames.ACCESS_CONTROL_ALLOW_HEADERS, "*");
+ response.headers().set(HttpHeaderNames.ACCESS_CONTROL_MAX_AGE, "86400");
+ response.headers().set(HttpHeaderNames.CONTENT_LENGTH, 0);
+
+ ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
+ }
+
+ private void sendSuccessResponse(ChannelHandlerContext ctx) {
+ FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
+ response.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json");
+ response.headers().set(HttpHeaderNames.CONTENT_LENGTH, 0);
+ response.headers().set(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN, "*");
+
+ ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
+ }
+
+ private void sendErrorResponse(ChannelHandlerContext ctx, HttpResponseStatus status, String message) {
+ ByteBuf content = Unpooled.copiedBuffer("{\"error\":\"" + message + "\"}", CharsetUtil.UTF_8);
+
+ FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, content);
+ response.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json");
+ response.headers().set(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes());
+ response.headers().set(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN, "*");
+
+ ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
+ }
+
+ private void sendNotFoundResponse(ChannelHandlerContext ctx) {
+ ByteBuf content = Unpooled.copiedBuffer("{\"error\":\"Resource not found\"}", CharsetUtil.UTF_8);
+
+ FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.NOT_FOUND,
+ content);
+ response.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json");
+ response.headers().set(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes());
+ response.headers().set(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN, "*");
+
+ ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
+ }
+
+ public CompletableFuture notifyClients(String method, Object params) {
+ CompletableFuture future = new CompletableFuture<>();
+
+ if (sessions.isEmpty()) {
+ logger.debug("No active sessions to broadcast message");
+ future.complete(null);
+ return future;
+ }
+
+ logger.debug("Attempting to broadcast message to {} active sessions", sessions.size());
+
+ CompletableFuture>[] futures = sessions.values()
+ .stream()
+ .map(session -> session.sendNotification(method, params).exceptionally(e -> {
+ logger.error("Failed to send message to session {}: {}", session.getId(), e.getMessage());
+ return null;
+ }))
+ .toArray(CompletableFuture[]::new);
+
+ CompletableFuture.allOf(futures).whenComplete((result, ex) -> {
+ if (ex != null) {
+ logger.error("Error broadcasting message", ex);
+ future.completeExceptionally(ex);
+ }
+ else {
+ future.complete(null);
+ }
+ });
+
+ return future;
+ }
+
+ public CompletableFuture closeGracefully() {
+ CompletableFuture future = new CompletableFuture<>();
+
+ try {
+ this.shutdown();
+ future.complete(null);
+ }
+ catch (Exception e) {
+ future.completeExceptionally(e);
+ }
+
+ return future;
+ }
+
+ private void shutdown() {
+ for (McpServerSession session : this.sessions.values()) {
+ try {
+ session.close();
+ }
+ catch (Exception e) {
+ logger.warn("Error closing session: {}", e.getMessage());
+ }
+ }
+ logger.info("MCP HTTP server closed");
+ }
+
+ public void setSessionFactory(McpServerSession.Factory sessionFactory) {
+ this.sessionFactory = sessionFactory;
+ }
+
+ public String getMessageEndpoint() {
+ return messageEndpoint;
+ }
+
+ public String getSseEndpoint() {
+ return sseEndpoint;
+ }
+
+ private class HttpServerTransport implements McpServerTransport {
+
+ private final String sessionId;
+
+ private final Channel channel;
+
+ public HttpServerTransport(String sessionId, Channel channel) {
+ this.sessionId = sessionId;
+ this.channel = channel;
+ }
+
+ public String getSessionId() {
+ return sessionId;
+ }
+
+ @Override
+ public Channel getChannel() {
+ return channel;
+ }
+
+ @Override
+ public CompletableFuture sendMessage(McpSchema.JSONRPCMessage message) {
+ CompletableFuture future = new CompletableFuture<>();
+
+ if (!channel.isActive()) {
+ future.completeExceptionally(new RuntimeException("Channel is not active"));
+ return future;
+ }
+
+ try {
+ String jsonMessage = objectMapper.writeValueAsString(message);
+ logger.debug("sendMessage: {}", jsonMessage);
+
+ StringBuilder eventBuilder = new StringBuilder();
+ eventBuilder.append("event: ").append(MESSAGE_EVENT_TYPE).append("\n");
+ eventBuilder.append("data: ").append(jsonMessage).append("\n\n");
+
+ ByteBuf buffer = Unpooled.copiedBuffer(eventBuilder.toString(), CharsetUtil.UTF_8);
+
+ channel.writeAndFlush(new DefaultHttpContent(buffer))
+ .addListener((ChannelFutureListener) channelFuture -> {
+ if (channelFuture.isSuccess()) {
+ future.complete(null);
+ }
+ else {
+ future.completeExceptionally(channelFuture.cause());
+ }
+ });
+ }
+ catch (Exception e) {
+ logger.error("Failed to send message to session {}: {}", sessionId, e.getMessage());
+ sessions.remove(sessionId);
+ future.completeExceptionally(e);
+ }
+
+ return future;
+ }
+
+ @Override
+ public T unmarshalFrom(Object data, TypeReference typeRef) {
+ return objectMapper.convertValue(data, typeRef);
+ }
+
+ @Override
+ public CompletableFuture closeGracefully() {
+ CompletableFuture future = new CompletableFuture<>();
+
+ try {
+ sessions.remove(sessionId);
+ sseChannels.remove(sessionId);
+
+ if (channel.isActive()) {
+ channel.close().addListener((ChannelFutureListener) channelFuture -> {
+ if (channelFuture.isSuccess()) {
+ future.complete(null);
+ }
+ else {
+ future.completeExceptionally(channelFuture.cause());
+ }
+ });
+ }
+ else {
+ future.complete(null);
+ }
+ }
+ catch (Exception e) {
+ future.completeExceptionally(e);
+ }
+
+ return future;
+ }
+
+ @Override
+ public void close() {
+ sessions.remove(sessionId);
+ sseChannels.remove(sessionId);
+
+ if (channel.isActive()) {
+ channel.close();
+ }
+ }
+
+ }
+
+}
diff --git a/labs/arthas-mcp-server/src/main/java/com/taobao/arthas/mcp/server/protocol/server/transport/HttpNettyServerTransportProvider.java b/labs/arthas-mcp-server/src/main/java/com/taobao/arthas/mcp/server/protocol/server/transport/HttpNettyServerTransportProvider.java
new file mode 100644
index 000000000..a21259dca
--- /dev/null
+++ b/labs/arthas-mcp-server/src/main/java/com/taobao/arthas/mcp/server/protocol/server/transport/HttpNettyServerTransportProvider.java
@@ -0,0 +1,90 @@
+package com.taobao.arthas.mcp.server.protocol.server.transport;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.taobao.arthas.mcp.server.protocol.server.handler.McpRequestHandler;
+import com.taobao.arthas.mcp.server.protocol.spec.McpServerSession;
+import com.taobao.arthas.mcp.server.protocol.spec.McpServerTransportProvider;
+import com.taobao.arthas.mcp.server.util.Assert;
+
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * HTTP MCP server transport provider implementation based on Netty,
+ * supporting server-to-client communication via SSE.
+ *
+ * @author Yeaury
+ */
+public class HttpNettyServerTransportProvider implements McpServerTransportProvider {
+ public static final String DEFAULT_SSE_ENDPOINT = "/mcp";
+ public static final String DEFAULT_MESSAGE_ENDPOINT = "/mcp/message";
+ private final McpRequestHandler mcpRequestHandler;
+
+ /**
+ * Create a new HttpServerTransportProvider instance.
+ * @param messageEndpoint Message endpoint path, e.g. "/mcp/message"
+ * @param sseEndpoint SSE endpoint path, e.g. "/mcp"
+ * @param objectMapper Object mapper for JSON serialization/deserialization
+ */
+ public HttpNettyServerTransportProvider(String messageEndpoint, String sseEndpoint,
+ ObjectMapper objectMapper) {
+ Assert.hasText(messageEndpoint, "Message endpoint path cannot be empty");
+ Assert.hasText(sseEndpoint, "SSE endpoint path cannot be empty");
+ Assert.notNull(objectMapper, "Object mapper cannot be null");
+
+ this.mcpRequestHandler = new McpRequestHandler(messageEndpoint, sseEndpoint, objectMapper);
+ }
+
+ @Override
+ public void setSessionFactory(McpServerSession.Factory sessionFactory) {
+ this.mcpRequestHandler.setSessionFactory(sessionFactory);
+ }
+
+ @Override
+ public CompletableFuture notifyClients(String method, Object params) {
+ return mcpRequestHandler.notifyClients(method, params);
+ }
+
+ @Override
+ public CompletableFuture closeGracefully() {
+ return mcpRequestHandler.closeGracefully();
+ }
+
+ @Override
+ public McpRequestHandler getMcpRequestHandler() {
+ return mcpRequestHandler;
+ }
+
+ public static class Builder {
+
+ private String messageEndpoint = DEFAULT_MESSAGE_ENDPOINT;
+
+ private String sseEndpoint = DEFAULT_SSE_ENDPOINT;
+
+ private ObjectMapper objectMapper = new ObjectMapper();
+
+ public Builder messageEndpoint(String messageEndpoint) {
+ this.messageEndpoint = messageEndpoint;
+ return this;
+ }
+
+ public Builder sseEndpoint(String sseEndpoint) {
+ this.sseEndpoint = sseEndpoint;
+ return this;
+ }
+
+ public Builder objectMapper(ObjectMapper objectMapper) {
+ this.objectMapper = objectMapper;
+ return this;
+ }
+
+ public HttpNettyServerTransportProvider build() {
+ return new HttpNettyServerTransportProvider(messageEndpoint, sseEndpoint, objectMapper);
+ }
+
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+}
diff --git a/labs/arthas-mcp-server/src/main/java/com/taobao/arthas/mcp/server/protocol/spec/McpError.java b/labs/arthas-mcp-server/src/main/java/com/taobao/arthas/mcp/server/protocol/spec/McpError.java
new file mode 100644
index 000000000..2c93d646e
--- /dev/null
+++ b/labs/arthas-mcp-server/src/main/java/com/taobao/arthas/mcp/server/protocol/spec/McpError.java
@@ -0,0 +1,27 @@
+package com.taobao.arthas.mcp.server.protocol.spec;
+
+import com.taobao.arthas.mcp.server.protocol.spec.McpSchema.JSONRPCResponse.JSONRPCError;
+
+/**
+ * Exception class for representing JSON-RPC errors in MCP protocol.
+ *
+ * @author Yeaury
+ */
+public class McpError extends RuntimeException {
+
+ private JSONRPCError jsonRpcError;
+
+ public McpError(JSONRPCError jsonRpcError) {
+ super(jsonRpcError.getMessage());
+ this.jsonRpcError = jsonRpcError;
+ }
+
+ public McpError(Object error) {
+ super(error.toString());
+ }
+
+ public JSONRPCError getJsonRpcError() {
+ return jsonRpcError;
+ }
+
+}
diff --git a/labs/arthas-mcp-server/src/main/java/com/taobao/arthas/mcp/server/protocol/spec/McpSchema.java b/labs/arthas-mcp-server/src/main/java/com/taobao/arthas/mcp/server/protocol/spec/McpSchema.java
new file mode 100644
index 000000000..ab7b5ca64
--- /dev/null
+++ b/labs/arthas-mcp-server/src/main/java/com/taobao/arthas/mcp/server/protocol/spec/McpSchema.java
@@ -0,0 +1,2039 @@
+package com.taobao.arthas.mcp.server.protocol.spec;
+
+import com.fasterxml.jackson.annotation.*;
+import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.taobao.arthas.mcp.server.util.Assert;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Based on the JSON-RPC 2.0 specification
+ * and the
+ * Model Context Protocol Schema.
+ *
+ * @author Yeaury
+ */
+public final class McpSchema {
+
+ private static final Logger logger = LoggerFactory.getLogger(McpSchema.class);
+
+ private McpSchema() {
+ }
+
+ public static final String LATEST_PROTOCOL_VERSION = "2024-11-05";
+
+ public static final String JSONRPC_VERSION = "2.0";
+
+ // ---------------------------
+ // Method Names
+ // ---------------------------
+
+ // Lifecycle Methods
+ public static final String METHOD_INITIALIZE = "initialize";
+
+ public static final String METHOD_NOTIFICATION_INITIALIZED = "notifications/initialized";
+
+ public static final String METHOD_PING = "ping";
+
+ // Tool Methods
+ public static final String METHOD_TOOLS_LIST = "tools/list";
+
+ public static final String METHOD_TOOLS_CALL = "tools/call";
+
+ public static final String METHOD_NOTIFICATION_TOOLS_LIST_CHANGED = "notifications/tools/list_changed";
+
+ // Resources Methods
+ public static final String METHOD_RESOURCES_LIST = "resources/list";
+
+ public static final String METHOD_RESOURCES_READ = "resources/read";
+
+ public static final String METHOD_NOTIFICATION_RESOURCES_LIST_CHANGED = "notifications/resources/list_changed";
+
+ public static final String METHOD_RESOURCES_TEMPLATES_LIST = "resources/templates/list";
+
+ // Prompt Methods
+ public static final String METHOD_PROMPT_LIST = "prompts/list";
+
+ public static final String METHOD_PROMPT_GET = "prompts/get";
+
+ public static final String METHOD_NOTIFICATION_PROMPTS_LIST_CHANGED = "notifications/prompts/list_changed";
+
+ // Logging Methods
+ public static final String METHOD_LOGGING_SET_LEVEL = "logging/setLevel";
+
+ public static final String METHOD_NOTIFICATION_MESSAGE = "notifications/message";
+
+ // Roots Methods
+ public static final String METHOD_ROOTS_LIST = "roots/list";
+
+ public static final String METHOD_NOTIFICATION_ROOTS_LIST_CHANGED = "notifications/roots/list_changed";
+
+ // Sampling Methods
+ public static final String METHOD_SAMPLING_CREATE_MESSAGE = "sampling/createMessage";
+
+ private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+
+ // Streamable Methods
+ public static final String METHOD_PROGRESS_UPDATE = "notifications/progress";
+
+ public static final String METHOD_STATUS_UPDATE = "notifications/status";
+
+ public static final String METHOD_INTERMEDIATE_RESULT = "notifications/intermediate";
+
+ public static final String METHOD_TOOL_ERROR = "notifications/error";
+
+ // ---------------------------
+ // JSON-RPC Error Codes
+ // ---------------------------
+ /**
+ * Standard error codes used in MCP JSON-RPC responses.
+ */
+ public static final class ErrorCodes {
+
+ /**
+ * The JSON received by the server is invalid.
+ */
+ public static final int PARSE_ERROR = -32700;
+
+ /**
+ * The JSON sent is not a valid Request object.
+ */
+ public static final int INVALID_REQUEST = -32600;
+
+ /**
+ * The method does not exist or is unavailable.
+ */
+ public static final int METHOD_NOT_FOUND = -32601;
+
+ /**
+ * Invalid method parameters.
+ */
+ public static final int INVALID_PARAMS = -32602;
+
+ /**
+ * Internal JSON-RPC error.
+ */
+ public static final int INTERNAL_ERROR = -32603;
+
+ }
+
+ public interface Request {
+ }
+
+ private static final TypeReference> MAP_TYPE_REF = new TypeReference>() {
+ };
+
+ /**
+ * Deserializes a JSON string into a JSONRPCMessage object.
+ * @param objectMapper The ObjectMapper instance to use for deserialization
+ * @param jsonText The JSON string to deserialize
+ * @return A JSONRPCMessage instance using either the {@link JSONRPCRequest},
+ * {@link JSONRPCNotification}, or {@link JSONRPCResponse} classes.
+ * @throws IOException If there's an error during deserialization
+ * @throws IllegalArgumentException If the JSON structure doesn't match any known
+ * message type
+ */
+ public static JSONRPCMessage deserializeJsonRpcMessage(ObjectMapper objectMapper, String jsonText)
+ throws IOException {
+
+ logger.debug("Received JSON message: {}", jsonText);
+
+ Map map = objectMapper.readValue(jsonText, MAP_TYPE_REF);
+
+ // Determine message type based on specific JSON structure
+ if (map.containsKey("method") && map.containsKey("id")) {
+ return objectMapper.convertValue(map, JSONRPCRequest.class);
+ }
+ else if (map.containsKey("method") && !map.containsKey("id")) {
+ return objectMapper.convertValue(map, JSONRPCNotification.class);
+ }
+ else if (map.containsKey("result") || map.containsKey("error")) {
+ return objectMapper.convertValue(map, JSONRPCResponse.class);
+ }
+
+ throw new IllegalArgumentException("Cannot deserialize JSONRPCMessage: " + jsonText);
+ }
+
+ // ---------------------------
+ // JSON-RPC Message Types
+ // ---------------------------
+
+ /**
+ * Represents an event message with associated event ID.
+ */
+ public static class EventMessage {
+ private final String eventId;
+ private final JSONRPCMessage message;
+
+ public EventMessage(
+ @JsonProperty("eventId") String eventId,
+ @JsonProperty("message") JSONRPCMessage message) {
+ this.eventId = eventId;
+ this.message = message;
+ }
+
+ public String getEventId() {
+ return eventId;
+ }
+
+ public JSONRPCMessage getMessage() {
+ return message;
+ }
+ }
+
+ public interface JSONRPCMessage {
+ String getJsonrpc();
+ }
+
+ @JsonInclude(JsonInclude.Include.NON_ABSENT)
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class JSONRPCRequest implements JSONRPCMessage {
+ private final String jsonrpc;
+ private final String method;
+ private final Object id;
+ private final Object params;
+
+ public JSONRPCRequest(
+ @JsonProperty("jsonrpc") String jsonrpc,
+ @JsonProperty("method") String method,
+ @JsonProperty("id") Object id,
+ @JsonProperty("params") Object params) {
+ this.jsonrpc = jsonrpc;
+ this.method = method;
+ this.id = id;
+ this.params = params;
+ }
+
+ @Override
+ public String getJsonrpc() {
+ return jsonrpc;
+ }
+
+ public String getMethod() {
+ return method;
+ }
+
+ public Object getId() {
+ return id;
+ }
+
+ public Object getParams() {
+ return params;
+ }
+ }
+
+ @JsonInclude(JsonInclude.Include.NON_ABSENT)
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class JSONRPCNotification implements JSONRPCMessage {
+ private final String jsonrpc;
+ private final String method;
+ private final Object params;
+
+ public JSONRPCNotification(
+ @JsonProperty("jsonrpc") String jsonrpc,
+ @JsonProperty("method") String method,
+ @JsonProperty("params") Object params) {
+ this.jsonrpc = jsonrpc;
+ this.method = method;
+ this.params = params;
+ }
+
+ @Override
+ public String getJsonrpc() {
+ return jsonrpc;
+ }
+
+ public String getMethod() {
+ return method;
+ }
+
+ public Object getParams() {
+ return params;
+ }
+ }
+
+ @JsonInclude(JsonInclude.Include.NON_ABSENT)
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class JSONRPCResponse implements JSONRPCMessage {
+ private final String jsonrpc;
+ private final Object id;
+ private final Object result;
+ private final JSONRPCError error;
+
+ public JSONRPCResponse(
+ @JsonProperty("jsonrpc") String jsonrpc,
+ @JsonProperty("id") Object id,
+ @JsonProperty("result") Object result,
+ @JsonProperty("error") JSONRPCError error) {
+ this.jsonrpc = jsonrpc;
+ this.id = id;
+ this.result = result;
+ this.error = error;
+ }
+
+ @Override
+ public String getJsonrpc() {
+ return jsonrpc;
+ }
+
+ public Object getId() {
+ return id;
+ }
+
+ public Object getResult() {
+ return result;
+ }
+
+ public JSONRPCError getError() {
+ return error;
+ }
+
+ @JsonInclude(JsonInclude.Include.NON_ABSENT)
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class JSONRPCError {
+ private final int code;
+ private final String message;
+ private final Object data;
+
+ public JSONRPCError(
+ @JsonProperty("code") int code,
+ @JsonProperty("message") String message,
+ @JsonProperty("data") Object data) {
+ this.code = code;
+ this.message = message;
+ this.data = data;
+ }
+
+ public int getCode() {
+ return code;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public Object getData() {
+ return data;
+ }
+ }
+ }
+
+ // ---------------------------
+ // Initialization
+ // ---------------------------
+ @JsonInclude(JsonInclude.Include.NON_ABSENT)
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class InitializeRequest implements Request {
+ private final String protocolVersion;
+ private final ClientCapabilities capabilities;
+ private final Implementation clientInfo;
+
+ public InitializeRequest(
+ @JsonProperty("protocolVersion") String protocolVersion,
+ @JsonProperty("capabilities") ClientCapabilities capabilities,
+ @JsonProperty("clientInfo") Implementation clientInfo) {
+ this.protocolVersion = protocolVersion;
+ this.capabilities = capabilities;
+ this.clientInfo = clientInfo;
+ }
+
+ public String getProtocolVersion() {
+ return protocolVersion;
+ }
+
+ public ClientCapabilities getCapabilities() {
+ return capabilities;
+ }
+
+ public Implementation getClientInfo() {
+ return clientInfo;
+ }
+ }
+
+ @JsonInclude(JsonInclude.Include.NON_ABSENT)
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class InitializeResult {
+ private final String protocolVersion;
+ private final ServerCapabilities capabilities;
+ private final Implementation serverInfo;
+ private final String instructions;
+
+ public InitializeResult(
+ @JsonProperty("protocolVersion") String protocolVersion,
+ @JsonProperty("capabilities") ServerCapabilities capabilities,
+ @JsonProperty("serverInfo") Implementation serverInfo,
+ @JsonProperty("instructions") String instructions) {
+ this.protocolVersion = protocolVersion;
+ this.capabilities = capabilities;
+ this.serverInfo = serverInfo;
+ this.instructions = instructions;
+ }
+
+ public String getProtocolVersion() {
+ return protocolVersion;
+ }
+
+ public ServerCapabilities getCapabilities() {
+ return capabilities;
+ }
+
+ public Implementation getServerInfo() {
+ return serverInfo;
+ }
+
+ public String getInstructions() {
+ return instructions;
+ }
+ }
+
+ /**
+ * Clients can implement additional features to enrich connected MCP servers with
+ * additional capabilities. These capabilities can be used to extend the functionality
+ * of the server, or to provide additional information to the server about the
+ * client's capabilities.
+ */
+ @JsonInclude(JsonInclude.Include.NON_ABSENT)
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class ClientCapabilities {
+
+ private final Map experimental;
+ private final RootCapabilities roots;
+ private final Sampling sampling;
+
+ public ClientCapabilities(
+ @JsonProperty("experimental") Map experimental,
+ @JsonProperty("roots") RootCapabilities roots,
+ @JsonProperty("sampling") Sampling sampling) {
+ this.experimental = experimental;
+ this.roots = roots;
+ this.sampling = sampling;
+ }
+
+ /**
+ * Roots define the boundaries of where servers can operate within the filesystem,
+ * allowing them to understand which directories and files they have access to.
+ * Servers can request the list of roots from supporting clients and
+ * receive notifications when that list changes.
+ */
+ @JsonInclude(JsonInclude.Include.NON_ABSENT)
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class RootCapabilities {
+ private final Boolean listChanged;
+
+ public RootCapabilities(
+ @JsonProperty("listChanged") Boolean listChanged) {
+ this.listChanged = listChanged;
+ }
+
+ public Boolean getListChanged() {
+ return listChanged;
+ }
+ }
+
+ /**
+ * Provides a standardized way for servers to request LLM
+ * sampling ("completions" or "generations") from language
+ * models via clients. This flow allows clients to maintain
+ * control over model access, selection, and permissions
+ * while enabling servers to leverage AI capabilities—with
+ * no server API keys necessary. Servers can request text or
+ * image-based interactions and optionally include context
+ * from MCP servers in their prompts.
+ */
+ @JsonInclude(JsonInclude.Include.NON_ABSENT)
+ public static class Sampling {
+ }
+
+ public Map getExperimental() {
+ return experimental;
+ }
+
+ public RootCapabilities getRoots() {
+ return roots;
+ }
+
+ public Sampling getSampling() {
+ return sampling;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+ private Map experimental;
+ private RootCapabilities roots;
+ private Sampling sampling;
+
+ public Builder experimental(Map experimental) {
+ this.experimental = experimental;
+ return this;
+ }
+
+ public Builder roots(Boolean listChanged) {
+ this.roots = new RootCapabilities(listChanged);
+ return this;
+ }
+
+ public Builder sampling() {
+ this.sampling = new Sampling();
+ return this;
+ }
+
+ public ClientCapabilities build() {
+ return new ClientCapabilities(experimental, roots, sampling);
+ }
+ }
+ }
+
+ @JsonInclude(JsonInclude.Include.NON_ABSENT)
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class ServerCapabilities {
+ private final Map experimental;
+ private final LoggingCapabilities logging;
+ private final PromptCapabilities prompts;
+ private final ResourceCapabilities resources;
+ private final ToolCapabilities tools;
+
+ public ServerCapabilities(
+ @JsonProperty("experimental") Map experimental,
+ @JsonProperty("logging") LoggingCapabilities logging,
+ @JsonProperty("prompts") PromptCapabilities prompts,
+ @JsonProperty("resources") ResourceCapabilities resources,
+ @JsonProperty("tools") ToolCapabilities tools) {
+ this.experimental = experimental;
+ this.logging = logging;
+ this.prompts = prompts;
+ this.resources = resources;
+ this.tools = tools;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+ private Map experimental;
+ private LoggingCapabilities logging;
+ private PromptCapabilities prompts;
+ private ResourceCapabilities resources;
+ private ToolCapabilities tools;
+
+ public Builder experimental(Map experimental) {
+ this.experimental = experimental;
+ return this;
+ }
+
+ public Builder logging(LoggingCapabilities logging) {
+ this.logging = logging;
+ return this;
+ }
+
+ public Builder prompts(PromptCapabilities prompts) {
+ this.prompts = prompts;
+ return this;
+ }
+
+ public Builder resources(ResourceCapabilities resources) {
+ this.resources = resources;
+ return this;
+ }
+
+ public Builder tools(ToolCapabilities tools) {
+ this.tools = tools;
+ return this;
+ }
+
+ public ServerCapabilities build() {
+ return new ServerCapabilities(experimental, logging, prompts, resources, tools);
+ }
+ }
+
+ @JsonInclude(JsonInclude.Include.NON_ABSENT)
+ public static class LoggingCapabilities {
+ }
+
+ @JsonInclude(JsonInclude.Include.NON_ABSENT)
+ public static class PromptCapabilities {
+ private final Boolean listChanged;
+
+ public PromptCapabilities(@JsonProperty("listChanged") Boolean listChanged) {
+ this.listChanged = listChanged;
+ }
+
+ public Boolean getListChanged() {
+ return listChanged;
+ }
+ }
+
+ @JsonInclude(JsonInclude.Include.NON_ABSENT)
+ public static class ResourceCapabilities {
+ private final Boolean subscribe;
+ private final Boolean listChanged;
+
+ public ResourceCapabilities(
+ @JsonProperty("subscribe") Boolean subscribe,
+ @JsonProperty("listChanged") Boolean listChanged) {
+ this.subscribe = subscribe;
+ this.listChanged = listChanged;
+ }
+
+ public Boolean getSubscribe() {
+ return subscribe;
+ }
+
+ public Boolean getListChanged() {
+ return listChanged;
+ }
+ }
+
+ @JsonInclude(JsonInclude.Include.NON_ABSENT)
+ public static class ToolCapabilities {
+ private final Boolean listChanged;
+
+ public ToolCapabilities(@JsonProperty("listChanged") Boolean listChanged) {
+ this.listChanged = listChanged;
+ }
+
+ public Boolean getListChanged() {
+ return listChanged;
+ }
+ }
+
+ public Map getExperimental() {
+ return experimental;
+ }
+
+ public LoggingCapabilities getLogging() {
+ return logging;
+ }
+
+ public PromptCapabilities getPrompts() {
+ return prompts;
+ }
+
+ public ResourceCapabilities getResources() {
+ return resources;
+ }
+
+ public ToolCapabilities getTools() {
+ return tools;
+ }
+ }
+
+
+ @JsonInclude(JsonInclude.Include.NON_ABSENT)
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class Implementation {
+ private final String name;
+ private final String version;
+
+ public Implementation(
+ @JsonProperty("name") String name,
+ @JsonProperty("version") String version) {
+ this.name = name;
+ this.version = version;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getVersion() {
+ return version;
+ }
+ }
+
+ // Existing Enums and Base Types
+ public enum Role {
+ @JsonProperty("user") USER,
+ @JsonProperty("assistant") ASSISTANT
+ }
+
+ public enum StopReason {
+ @JsonProperty("stop") STOP,
+ @JsonProperty("length") LENGTH,
+ @JsonProperty("content_filter") CONTENT_FILTER
+ }
+
+ public enum ContextInclusionStrategy {
+ @JsonProperty("none") NONE,
+ @JsonProperty("all") ALL,
+ @JsonProperty("relevant") RELEVANT
+ }
+
+ // ---------------------------
+ // Resource Interfaces
+ // ---------------------------
+ /**
+ * Base for objects that include optional annotations for the client. The client can
+ * use annotations to inform how objects are used or displayed
+ */
+ public interface Annotated {
+
+ Annotations annotations();
+
+ }
+
+ /**
+ * Optional annotations for the client. The client can use annotations to inform how
+ * objects are used or displayed.
+ */
+ @JsonInclude(JsonInclude.Include.NON_ABSENT)
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class Annotations {
+ private final List audience;
+ private final Double priority;
+
+ public Annotations(
+ @JsonProperty("audience") List audience,
+ @JsonProperty("priority") Double priority) {
+ this.audience = audience;
+ this.priority = priority;
+ }
+
+ public List getAudience() {
+ return audience;
+ }
+
+ public Double getPriority() {
+ return priority;
+ }
+ }
+
+ /**
+ * A known resource that the server is capable of reading.
+ */
+ @JsonInclude(JsonInclude.Include.NON_ABSENT)
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class Resource implements Annotated {
+ private final String uri;
+ private final String name;
+ private final String description;
+ private final String mimeType;
+ private final Annotations annotations;
+
+ public Resource(
+ @JsonProperty("uri") String uri,
+ @JsonProperty("name") String name,
+ @JsonProperty("description") String description,
+ @JsonProperty("mimeType") String mimeType,
+ @JsonProperty("annotations") Annotations annotations) {
+ this.uri = uri;
+ this.name = name;
+ this.description = description;
+ this.mimeType = mimeType;
+ this.annotations = annotations;
+ }
+
+ @Override
+ public Annotations annotations() {
+ return annotations;
+ }
+
+ public String getUri() {
+ return uri;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public String getMimeType() {
+ return mimeType;
+ }
+ }
+
+ /**
+ * Resource templates allow servers to expose parameterized resources using URI
+ * templates.
+ *
+ * @see RFC 6570
+ */
+ @JsonInclude(JsonInclude.Include.NON_ABSENT)
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class ResourceTemplate implements Annotated {
+ private final String uriTemplate;
+ private final String name;
+ private final String description;
+ private final String mimeType;
+ private final Annotations annotations;
+
+ public ResourceTemplate(
+ @JsonProperty("uriTemplate") String uriTemplate,
+ @JsonProperty("name") String name,
+ @JsonProperty("description") String description,
+ @JsonProperty("mimeType") String mimeType,
+ @JsonProperty("annotations") Annotations annotations) {
+ this.uriTemplate = uriTemplate;
+ this.name = name;
+ this.description = description;
+ this.mimeType = mimeType;
+ this.annotations = annotations;
+ }
+
+ @Override
+ public Annotations annotations() {
+ return annotations;
+ }
+
+ public String getUriTemplate() {
+ return uriTemplate;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public String getMimeType() {
+ return mimeType;
+ }
+ }
+
+ @JsonInclude(JsonInclude.Include.NON_ABSENT)
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class ListResourcesResult {
+ private final List resources;
+ private final String nextCursor;
+
+ public ListResourcesResult(
+ @JsonProperty("resources") List resources,
+ @JsonProperty("nextCursor") String nextCursor) {
+ this.resources = resources;
+ this.nextCursor = nextCursor;
+ }
+
+ public List getResources() {
+ return resources;
+ }
+
+ public String getNextCursor() {
+ return nextCursor;
+ }
+ }
+
+ @JsonInclude(JsonInclude.Include.NON_ABSENT)
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class ListResourceTemplatesResult {
+ private final List resourceTemplates;
+ private final String nextCursor;
+
+ public ListResourceTemplatesResult(
+ @JsonProperty("resourceTemplates") List resourceTemplates,
+ @JsonProperty("nextCursor") String nextCursor) {
+ this.resourceTemplates = resourceTemplates;
+ this.nextCursor = nextCursor;
+ }
+
+ public List getResourceTemplates() {
+ return resourceTemplates;
+ }
+
+ public String getNextCursor() {
+ return nextCursor;
+ }
+ }
+
+ @JsonInclude(JsonInclude.Include.NON_ABSENT)
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class ReadResourceRequest {
+ private final String uri;
+
+ public ReadResourceRequest(
+ @JsonProperty("uri") String uri) {
+ this.uri = uri;
+ }
+
+ public String getUri() {
+ return uri;
+ }
+ }
+
+ @JsonInclude(JsonInclude.Include.NON_ABSENT)
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class ReadResourceResult {
+ private final List contents;
+
+ public ReadResourceResult(
+ @JsonProperty("contents") List contents) {
+ this.contents = contents;
+ }
+
+ public List getContents() {
+ return contents;
+ }
+ }
+
+ /**
+ * Sent from the client to request resources/updated notifications from the server
+ * whenever a particular resource changes.
+ *
+ */
+ @JsonInclude(JsonInclude.Include.NON_ABSENT)
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class SubscribeRequest {
+ private final String uri;
+
+ public SubscribeRequest(
+ @JsonProperty("uri") String uri) {
+ this.uri = uri;
+ }
+
+ public String getUri() {
+ return uri;
+ }
+ }
+
+ @JsonInclude(JsonInclude.Include.NON_ABSENT)
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class UnsubscribeRequest {
+ private final String uri;
+
+ public UnsubscribeRequest(
+ @JsonProperty("uri") String uri) {
+ this.uri = uri;
+ }
+
+ public String getUri() {
+ return uri;
+ }
+ }
+
+ /**
+ * The contents of a specific resource or sub-resource.
+ */
+ @JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION, include = As.PROPERTY)
+ @JsonSubTypes({ @JsonSubTypes.Type(value = TextResourceContents.class, name = "text"),
+ @JsonSubTypes.Type(value = BlobResourceContents.class, name = "blob") })
+ public interface ResourceContents {
+
+ /**
+ * The URI of this resource.
+ * @return the URI of this resource.
+ */
+ String uri();
+
+ /**
+ * The MIME type of this resource.
+ * @return the MIME type of this resource.
+ */
+ String mimeType();
+
+ }
+
+ /**
+ * Text contents of a resource.
+ *
+ */
+ @JsonInclude(JsonInclude.Include.NON_ABSENT)
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class TextResourceContents implements ResourceContents {
+ private final String uri;
+ private final String mimeType;
+ private final String text;
+
+ public TextResourceContents(
+ @JsonProperty("uri") String uri,
+ @JsonProperty("mimeType") String mimeType,
+ @JsonProperty("text") String text) {
+ this.uri = uri;
+ this.mimeType = mimeType;
+ this.text = text;
+ }
+
+ @Override
+ public String uri() {
+ return uri;
+ }
+
+ @Override
+ public String mimeType() {
+ return mimeType;
+ }
+
+ public String getText() {
+ return text;
+ }
+ }
+
+ /**
+ * Binary contents of a resource.
+ *
+ * This must only be set if the resource can actually be represented as binary data
+ * (not text).
+ */
+ @JsonInclude(JsonInclude.Include.NON_ABSENT)
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class BlobResourceContents implements ResourceContents {
+ private final String uri;
+ private final String mimeType;
+ private final String blob;
+
+ public BlobResourceContents(
+ @JsonProperty("uri") String uri,
+ @JsonProperty("mimeType") String mimeType,
+ @JsonProperty("blob") String blob) {
+ this.uri = uri;
+ this.mimeType = mimeType;
+ this.blob = blob;
+ }
+
+ @Override
+ public String uri() {
+ return uri;
+ }
+
+ @Override
+ public String mimeType() {
+ return mimeType;
+ }
+
+ public String getBlob() {
+ return blob;
+ }
+ }
+
+ // ---------------------------
+ // Prompt Interfaces
+ // ---------------------------
+ /**
+ * A prompt or prompt template that the server offers.
+ *
+ */
+ @JsonInclude(JsonInclude.Include.NON_ABSENT)
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class Prompt {
+ private final String name;
+ private final String description;
+ private final List arguments;
+
+ public Prompt(
+ @JsonProperty("name") String name,
+ @JsonProperty("description") String description,
+ @JsonProperty("arguments") List arguments) {
+ this.name = name;
+ this.description = description;
+ this.arguments = arguments;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public List getArguments() {
+ return arguments;
+ }
+ }
+
+ /**
+ * Describes an argument that a prompt can accept.
+ *
+ */
+ @JsonInclude(JsonInclude.Include.NON_ABSENT)
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class PromptArgument {
+ private final String name;
+ private final String description;
+ private final Boolean required;
+
+ public PromptArgument(
+ @JsonProperty("name") String name,
+ @JsonProperty("description") String description,
+ @JsonProperty("required") Boolean required) {
+ this.name = name;
+ this.description = description;
+ this.required = required;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public Boolean getRequired() {
+ return required;
+ }
+ }
+
+ /**
+ * Describes a message returned as part of a prompt.
+ *
+ * This is similar to `SamplingMessage`, but also supports the embedding of resources
+ * from the MCP server.
+ */
+ @JsonInclude(JsonInclude.Include.NON_ABSENT)
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class PromptMessage {
+ private final Role role;
+ private final Content content;
+
+ public PromptMessage(
+ @JsonProperty("role") Role role,
+ @JsonProperty("content") Content content) {
+ this.role = role;
+ this.content = content;
+ }
+
+ public Role getRole() {
+ return role;
+ }
+
+ public Content getContent() {
+ return content;
+ }
+ }
+
+ /**
+ * The server's response to a prompts/list request from the client.
+ */
+ @JsonInclude(JsonInclude.Include.NON_ABSENT)
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class ListPromptsResult {
+ private final List prompts;
+ private final String nextCursor;
+
+ public ListPromptsResult(
+ @JsonProperty("prompts") List prompts,
+ @JsonProperty("nextCursor") String nextCursor) {
+ this.prompts = prompts;
+ this.nextCursor = nextCursor;
+ }
+
+ public List getPrompts() {
+ return prompts;
+ }
+
+ public String getNextCursor() {
+ return nextCursor;
+ }
+ }
+
+ /**
+ * Used by the client to get a prompt provided by the server.
+ *
+ */
+ @JsonInclude(JsonInclude.Include.NON_ABSENT)
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class GetPromptRequest implements Request {
+ private final String name;
+ private final Map arguments;
+
+ public GetPromptRequest(
+ @JsonProperty("name") String name,
+ @JsonProperty("arguments") Map arguments) {
+ this.name = name;
+ this.arguments = arguments;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Map getArguments() {
+ return arguments;
+ }
+ }
+
+ /**
+ * The server's response to a prompts/get request from the client.
+ *
+ */
+ @JsonInclude(JsonInclude.Include.NON_ABSENT)
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class GetPromptResult {
+ private final String description;
+ private final List messages;
+
+ public GetPromptResult(
+ @JsonProperty("description") String description,
+ @JsonProperty("messages") List messages) {
+ this.description = description;
+ this.messages = messages;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public List getMessages() {
+ return messages;
+ }
+ }
+
+ // ---------------------------
+ // Tool Interfaces
+ // ---------------------------
+ /**
+ * The server's response to a tools/list request from the client.
+ *
+ */
+ @JsonInclude(JsonInclude.Include.NON_ABSENT)
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class ListToolsResult {
+ private final List tools;
+ private final String nextCursor;
+
+ public ListToolsResult(
+ @JsonProperty("tools") List tools,
+ @JsonProperty("nextCursor") String nextCursor) {
+ this.tools = tools;
+ this.nextCursor = nextCursor;
+ }
+
+ public List getTools() {
+ return tools;
+ }
+
+ public String getNextCursor() {
+ return nextCursor;
+ }
+ }
+
+ @JsonInclude(JsonInclude.Include.NON_ABSENT)
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class JsonSchema {
+ private final String type;
+ private final Map properties;
+ private final List required;
+ private final Boolean additionalProperties;
+
+ public JsonSchema(
+ @JsonProperty("type") String type,
+ @JsonProperty("properties") Map properties,
+ @JsonProperty("required") List required,
+ @JsonProperty("additionalProperties") Boolean additionalProperties) {
+ this.type = type;
+ this.properties = properties;
+ this.required = required;
+ this.additionalProperties = additionalProperties;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public Map getProperties() {
+ return properties;
+ }
+
+ public List getRequired() {
+ return required;
+ }
+
+ public Boolean getAdditionalProperties() {
+ return additionalProperties;
+ }
+ }
+
+ /**
+ * Represents a tool that the server provides. Tools enable servers to expose
+ * executable functionality to the system. Through these tools, you can interact with
+ * external systems, perform computations, and take actions in the real world.
+ *
+ */
+ @JsonInclude(JsonInclude.Include.NON_ABSENT)
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class Tool {
+ private final String name;
+ private final String description;
+ private final JsonSchema inputSchema;
+
+ public Tool(
+ @JsonProperty("name") String name,
+ @JsonProperty("description") String description,
+ @JsonProperty("inputSchema") JsonSchema inputSchema) {
+ this.name = name;
+ this.description = description;
+ this.inputSchema = inputSchema;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public JsonSchema getInputSchema() {
+ return inputSchema;
+ }
+ }
+
+ private static JsonSchema parseSchema(String schema) {
+ try {
+ return OBJECT_MAPPER.readValue(schema, JsonSchema.class);
+ }
+ catch (IOException e) {
+ throw new IllegalArgumentException("Invalid schema: " + schema, e);
+ }
+ }
+
+ /**
+ * Used by the client to call a tool provided by the server.
+ *
+ */
+ @JsonInclude(JsonInclude.Include.NON_ABSENT)
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class CallToolRequest implements Request {
+ private final String name;
+ private final Map arguments;
+
+ public CallToolRequest(
+ @JsonProperty("name") String name,
+ @JsonProperty("arguments") Map arguments) {
+ this.name = name;
+ this.arguments = arguments;
+ }
+
+ public CallToolRequest(String name, String jsonArguments) {
+ this(name, parseJsonArguments(jsonArguments));
+ }
+
+ private static Map parseJsonArguments(String jsonArguments) {
+ try {
+ return OBJECT_MAPPER.readValue(jsonArguments, MAP_TYPE_REF);
+ }
+ catch (IOException e) {
+ throw new IllegalArgumentException("Invalid arguments: " + jsonArguments, e);
+ }
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Map getArguments() {
+ return arguments;
+ }
+ }
+
+ /**
+ * The server's response to a tools/call request from the client.
+ *
+ */
+ @JsonInclude(JsonInclude.Include.NON_ABSENT)
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class CallToolResult {
+ private final List content;
+ private final Boolean isError;
+
+ public CallToolResult(
+ @JsonProperty("content") List content,
+ @JsonProperty("isError") Boolean isError) {
+ this.content = content;
+ this.isError = isError;
+ }
+
+ public CallToolResult(String content, Boolean isError) {
+ this(Collections.singletonList(new TextContent(content)), isError);
+ }
+
+ public List getContent() {
+ return content;
+ }
+
+ public Boolean getIsError() {
+ return isError;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+ private List content = new ArrayList<>();
+ private Boolean isError;
+
+ public Builder content(List content) {
+ Assert.notNull(content, "content must not be null");
+ this.content = content;
+ return this;
+ }
+
+ public Builder textContent(List textContent) {
+ Assert.notNull(textContent, "textContent must not be null");
+ textContent.stream()
+ .map(TextContent::new)
+ .forEach(this.content::add);
+ return this;
+ }
+
+ public Builder addContent(Content contentItem) {
+ Assert.notNull(contentItem, "contentItem must not be null");
+ if (this.content == null) {
+ this.content = new ArrayList<>();
+ }
+ this.content.add(contentItem);
+ return this;
+ }
+
+ public Builder addTextContent(String text) {
+ Assert.notNull(text, "text must not be null");
+ return addContent(new TextContent(text));
+ }
+
+ public Builder isError(Boolean isError) {
+ Assert.notNull(isError, "isError must not be null");
+ this.isError = isError;
+ return this;
+ }
+
+ public CallToolResult build() {
+ return new CallToolResult(content, isError);
+ }
+ }
+ }
+
+ // ---------------------------
+ // Sampling Interfaces
+ // ---------------------------
+ @JsonInclude(JsonInclude.Include.NON_ABSENT)
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class ModelPreferences {
+ private final List hints;
+ private final Double costPriority;
+ private final Double speedPriority;
+ private final Double intelligencePriority;
+
+ public ModelPreferences(
+ @JsonProperty("hints") List hints,
+ @JsonProperty("costPriority") Double costPriority,
+ @JsonProperty("speedPriority") Double speedPriority,
+ @JsonProperty("intelligencePriority") Double intelligencePriority) {
+ this.hints = hints;
+ this.costPriority = costPriority;
+ this.speedPriority = speedPriority;
+ this.intelligencePriority = intelligencePriority;
+ }
+
+ public List getHints() {
+ return hints;
+ }
+
+ public Double getCostPriority() {
+ return costPriority;
+ }
+
+ public Double getSpeedPriority() {
+ return speedPriority;
+ }
+
+ public Double getIntelligencePriority() {
+ return intelligencePriority;
+ }
+ }
+
+ @JsonInclude(JsonInclude.Include.NON_ABSENT)
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class ModelHint {
+ private final String name;
+
+ public ModelHint(
+ @JsonProperty("name") String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+ }
+
+ @JsonInclude(JsonInclude.Include.NON_ABSENT)
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class SamplingMessage {
+ private final Role role;
+ private final Content content;
+
+ public SamplingMessage(
+ @JsonProperty("role") Role role,
+ @JsonProperty("content") Content content) {
+ this.role = role;
+ this.content = content;
+ }
+
+ public Role getRole() {
+ return role;
+ }
+
+ public Content getContent() {
+ return content;
+ }
+ }
+
+ // Sampling and Message Creation
+ @JsonInclude(JsonInclude.Include.NON_ABSENT)
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class CreateMessageRequest implements Request {
+ private final List messages;
+ private final ModelPreferences modelPreferences;
+ private final String systemPrompt;
+ private final ContextInclusionStrategy includeContext;
+ private final Double temperature;
+ private final int maxTokens;
+ private final List stopSequences;
+ private final Map metadata;
+
+ public CreateMessageRequest(
+ @JsonProperty("messages") List messages,
+ @JsonProperty("modelPreferences") ModelPreferences modelPreferences,
+ @JsonProperty("systemPrompt") String systemPrompt,
+ @JsonProperty("includeContext") ContextInclusionStrategy includeContext,
+ @JsonProperty("temperature") Double temperature,
+ @JsonProperty("maxTokens") int maxTokens,
+ @JsonProperty("stopSequences") List stopSequences,
+ @JsonProperty("metadata") Map metadata) {
+ this.messages = messages;
+ this.modelPreferences = modelPreferences;
+ this.systemPrompt = systemPrompt;
+ this.includeContext = includeContext;
+ this.temperature = temperature;
+ this.maxTokens = maxTokens;
+ this.stopSequences = stopSequences;
+ this.metadata = metadata;
+ }
+
+ public List getMessages() {
+ return messages;
+ }
+
+ public ModelPreferences getModelPreferences() {
+ return modelPreferences;
+ }
+
+ public String getSystemPrompt() {
+ return systemPrompt;
+ }
+
+ public ContextInclusionStrategy getIncludeContext() {
+ return includeContext;
+ }
+
+ public Double getTemperature() {
+ return temperature;
+ }
+
+ public int getMaxTokens() {
+ return maxTokens;
+ }
+
+ public List getStopSequences() {
+ return stopSequences;
+ }
+
+ public Map getMetadata() {
+ return metadata;
+ }
+ }
+
+ @JsonInclude(JsonInclude.Include.NON_ABSENT)
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class CreateMessageResult {
+ private final Role role;
+ private final Content content;
+ private final String model;
+ private final StopReason stopReason;
+
+ public CreateMessageResult(
+ @JsonProperty("role") Role role,
+ @JsonProperty("content") Content content,
+ @JsonProperty("model") String model,
+ @JsonProperty("stopReason") StopReason stopReason) {
+ this.role = role;
+ this.content = content;
+ this.model = model;
+ this.stopReason = stopReason;
+ }
+
+ public Role getRole() {
+ return role;
+ }
+
+ public Content getContent() {
+ return content;
+ }
+
+ public String getModel() {
+ return model;
+ }
+
+ public StopReason getStopReason() {
+ return stopReason;
+ }
+ }
+
+ // ---------------------------
+ // Pagination Interfaces
+ // ---------------------------
+ @JsonInclude(JsonInclude.Include.NON_ABSENT)
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class PaginatedRequest {
+ private final String cursor;
+
+ public PaginatedRequest(
+ @JsonProperty("cursor") String cursor) {
+ this.cursor = cursor;
+ }
+
+ public String getCursor() {
+ return cursor;
+ }
+ }
+
+ @JsonInclude(JsonInclude.Include.NON_ABSENT)
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class PaginatedResult {
+ private final String nextCursor;
+
+ public PaginatedResult(
+ @JsonProperty("nextCursor") String nextCursor) {
+ this.nextCursor = nextCursor;
+ }
+
+ public String getNextCursor() {
+ return nextCursor;
+ }
+ }
+
+ // ---------------------------
+ // Progress and Logging
+ // ---------------------------
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class ProgressNotification {
+ private final String progressToken;
+ private final double progress;
+ private final Double total;
+
+ public ProgressNotification(
+ @JsonProperty("progressToken") String progressToken,
+ @JsonProperty("progress") double progress,
+ @JsonProperty("total") Double total) {
+ this.progressToken = progressToken;
+ this.progress = progress;
+ this.total = total;
+ }
+
+ public String getProgressToken() {
+ return progressToken;
+ }
+
+ public double getProgress() {
+ return progress;
+ }
+
+ public Double getTotal() {
+ return total;
+ }
+ }
+
+ /**
+ * The Model Context Protocol (MCP) provides a standardized way for servers to send
+ * structured log messages to clients. Clients can control logging verbosity by
+ * setting minimum log levels, with servers sending notifications containing severity
+ * levels, optional logger names, and arbitrary JSON-serializable data.
+ *
+ */
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class LoggingMessageNotification {
+ private final LoggingLevel level;
+ private final String logger;
+ private final String data;
+
+ public LoggingMessageNotification(
+ @JsonProperty("level") LoggingLevel level,
+ @JsonProperty("logger") String logger,
+ @JsonProperty("data") String data) {
+ this.level = level;
+ this.logger = logger;
+ this.data = data;
+ }
+
+ public LoggingLevel getLevel() {
+ return level;
+ }
+
+ public String getLogger() {
+ return logger;
+ }
+
+ public String getData() {
+ return data;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+ private LoggingLevel level = LoggingLevel.INFO;
+ private String logger = "server";
+ private String data;
+
+ public Builder level(LoggingLevel level) {
+ this.level = level;
+ return this;
+ }
+
+ public Builder logger(String logger) {
+ this.logger = logger;
+ return this;
+ }
+
+ public Builder data(String data) {
+ this.data = data;
+ return this;
+ }
+
+ public LoggingMessageNotification build() {
+ return new LoggingMessageNotification(level, logger, data);
+ }
+ }
+ }
+
+ public enum LoggingLevel {
+ @JsonProperty("debug") DEBUG(0),
+ @JsonProperty("info") INFO(1),
+ @JsonProperty("notice") NOTICE(2),
+ @JsonProperty("warning") WARNING(3),
+ @JsonProperty("error") ERROR(4),
+ @JsonProperty("critical") CRITICAL(5),
+ @JsonProperty("alert") ALERT(6),
+ @JsonProperty("emergency") EMERGENCY(7);
+
+ private final int level;
+
+ LoggingLevel(int level) {
+ this.level = level;
+ }
+
+ public int level() {
+ return level;
+ }
+ }
+
+ @JsonInclude(JsonInclude.Include.NON_ABSENT)
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class SetLevelRequest {
+ private final LoggingLevel level;
+
+ public SetLevelRequest(
+ @JsonProperty("level") LoggingLevel level) {
+ this.level = level;
+ }
+
+ public LoggingLevel getLevel() {
+ return level;
+ }
+ }
+
+ // ---------------------------
+ // Autocomplete
+ // ---------------------------
+ public enum CompleteArgument {
+ @JsonProperty("name") NAME,
+ @JsonProperty("description") DESCRIPTION,
+ @JsonProperty("uri") URI,
+ @JsonProperty("mimeType") MIME_TYPE
+ }
+
+ public static class CompleteRequest implements Request {
+ public static class PromptOrResourceReference {
+ private final String type;
+
+ public PromptOrResourceReference(
+ @JsonProperty("type") String type) {
+ this.type = type;
+ }
+
+ public String getType() {
+ return type;
+ }
+ }
+
+ public static class PromptReference extends PromptOrResourceReference {
+ private final String name;
+
+ public PromptReference(
+ @JsonProperty("type") String type,
+ @JsonProperty("name") String name) {
+ super(type);
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+ }
+
+ public static class ResourceReference extends PromptOrResourceReference {
+ private final String uri;
+
+ public ResourceReference(
+ @JsonProperty("type") String type,
+ @JsonProperty("uri") String uri) {
+ super(type);
+ this.uri = uri;
+ }
+
+ public String getUri() {
+ return uri;
+ }
+ }
+
+ private final PromptOrResourceReference ref;
+ private final CompleteArgument argument;
+
+ public CompleteRequest(
+ @JsonProperty("ref") PromptOrResourceReference ref,
+ @JsonProperty("argument") CompleteArgument argument) {
+ this.ref = ref;
+ this.argument = argument;
+ }
+
+ public PromptOrResourceReference getRef() {
+ return ref;
+ }
+
+ public CompleteArgument getArgument() {
+ return argument;
+ }
+ }
+
+ public static class CompleteResult {
+ private final CompleteCompletion completion;
+
+ public CompleteResult(
+ @JsonProperty("completion") CompleteCompletion completion) {
+ this.completion = completion;
+ }
+
+ public CompleteCompletion getCompletion() {
+ return completion;
+ }
+ }
+
+ public static class CompleteCompletion {
+ private final List values;
+ private final Integer total;
+ private final Boolean hasMore;
+
+ public CompleteCompletion(
+ @JsonProperty("values") List values,
+ @JsonProperty("total") Integer total,
+ @JsonProperty("hasMore") Boolean hasMore) {
+ this.values = values;
+ this.total = total;
+ this.hasMore = hasMore;
+ }
+
+ public List getValues() {
+ return values;
+ }
+
+ public Integer getTotal() {
+ return total;
+ }
+
+ public Boolean getHasMore() {
+ return hasMore;
+ }
+ }
+
+ // ---------------------------
+ // Content Types
+ // ---------------------------
+ @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = As.PROPERTY, property = "type")
+ @JsonSubTypes({ @JsonSubTypes.Type(value = TextContent.class, name = "text"),
+ @JsonSubTypes.Type(value = ImageContent.class, name = "image"),
+ @JsonSubTypes.Type(value = EmbeddedResource.class, name = "resource") })
+ public interface Content {
+
+ default String type() {
+ if (this instanceof TextContent) {
+ return "text";
+ }
+ else if (this instanceof ImageContent) {
+ return "image";
+ }
+ else if (this instanceof EmbeddedResource) {
+ return "resource";
+ }
+ throw new IllegalArgumentException("Unknown content type: " + this);
+ }
+
+ }
+
+ @JsonInclude(JsonInclude.Include.NON_ABSENT)
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class TextContent implements Content {
+ private final List audience;
+ private final Double priority;
+ private final String text;
+
+ public TextContent(
+ @JsonProperty("audience") List audience,
+ @JsonProperty("priority") Double priority,
+ @JsonProperty("text") String text) {
+ this.audience = audience;
+ this.priority = priority;
+ this.text = text;
+ }
+
+ public TextContent(String content) {
+ this(null, null, content);
+ }
+
+ public List getAudience() {
+ return audience;
+ }
+
+ public Double getPriority() {
+ return priority;
+ }
+
+ public String getText() {
+ return text;
+ }
+ }
+
+ @JsonInclude(JsonInclude.Include.NON_ABSENT)
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class ImageContent implements Content {
+ private final List audience;
+ private final Double priority;
+ private final String data;
+ private final String mimeType;
+
+ public ImageContent(
+ @JsonProperty("audience") List audience,
+ @JsonProperty("priority") Double priority,
+ @JsonProperty("data") String data,
+ @JsonProperty("mimeType") String mimeType) {
+ this.audience = audience;
+ this.priority = priority;
+ this.data = data;
+ this.mimeType = mimeType;
+ }
+
+ @Override
+ public String type() {
+ return "image";
+ }
+
+ public List getAudience() {
+ return audience;
+ }
+
+ public Double getPriority() {
+ return priority;
+ }
+
+ public String getData() {
+ return data;
+ }
+
+ public String getMimeType() {
+ return mimeType;
+ }
+ }
+
+ @JsonInclude(JsonInclude.Include.NON_ABSENT)
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class EmbeddedResource implements Content {
+ private final List audience;
+ private final Double priority;
+ private final ResourceContents resource;
+
+ public EmbeddedResource(
+ @JsonProperty("audience") List audience,
+ @JsonProperty("priority") Double priority,
+ @JsonProperty("resource") ResourceContents resource) {
+ this.audience = audience;
+ this.priority = priority;
+ this.resource = resource;
+ }
+
+ @Override
+ public String type() {
+ return "resource";
+ }
+
+ public List getAudience() {
+ return audience;
+ }
+
+ public Double getPriority() {
+ return priority;
+ }
+
+ public ResourceContents getResource() {
+ return resource;
+ }
+ }
+
+ // ---------------------------
+ // Roots
+ // ---------------------------
+
+ @JsonInclude(JsonInclude.Include.NON_ABSENT)
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class Root {
+ private final String uri;
+ private final String name;
+
+ public Root(
+ @JsonProperty("uri") String uri,
+ @JsonProperty("name") String name) {
+ this.uri = uri;
+ this.name = name;
+ }
+
+ public String getUri() {
+ return uri;
+ }
+
+ public String getName() {
+ return name;
+ }
+ }
+
+ @JsonInclude(JsonInclude.Include.NON_ABSENT)
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class ListRootsResult {
+ private final List roots;
+
+ public ListRootsResult(
+ @JsonProperty("roots") List roots) {
+ this.roots = roots;
+ }
+
+ public List getRoots() {
+ return roots;
+ }
+ }
+
+}
diff --git a/labs/arthas-mcp-server/src/main/java/com/taobao/arthas/mcp/server/protocol/spec/McpServerSession.java b/labs/arthas-mcp-server/src/main/java/com/taobao/arthas/mcp/server/protocol/spec/McpServerSession.java
new file mode 100644
index 000000000..234fced1c
--- /dev/null
+++ b/labs/arthas-mcp-server/src/main/java/com/taobao/arthas/mcp/server/protocol/spec/McpServerSession.java
@@ -0,0 +1,433 @@
+package com.taobao.arthas.mcp.server.protocol.spec;
+
+import java.time.Duration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.taobao.arthas.mcp.server.protocol.server.McpNettyServerExchange;
+import io.netty.channel.Channel;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class McpServerSession implements McpSession {
+
+ private static final Logger logger = LoggerFactory.getLogger(McpServerSession.class);
+
+ private final ConcurrentHashMap