Copilot commented on code in PR #15406:
URL: https://github.com/apache/dubbo/pull/15406#discussion_r2264619647


##########
dubbo-plugin/dubbo-mcp/src/main/java/org/apache/dubbo/mcp/core/McpServiceFilter.java:
##########
@@ -0,0 +1,383 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.mcp.core;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.config.Configuration;
+import org.apache.dubbo.common.config.ConfigurationUtils;
+import org.apache.dubbo.common.constants.LoggerCodeConstants;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.utils.StringUtils;
+import org.apache.dubbo.config.annotation.DubboService;
+import org.apache.dubbo.mcp.McpConstant;
+import org.apache.dubbo.mcp.annotations.McpTool;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+import org.apache.dubbo.rpc.model.ProviderModel;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Pattern;
+
+public class McpServiceFilter {
+
+    private static final ErrorTypeAwareLogger logger = 
LoggerFactory.getErrorTypeAwareLogger(McpServiceFilter.class);
+
+    private final Configuration configuration;
+    private final Pattern[] includePatterns;
+    private final Pattern[] excludePatterns;
+    private final boolean defaultEnabled;
+
+    public McpServiceFilter(ApplicationModel applicationModel) {
+        this.configuration = 
ConfigurationUtils.getGlobalConfiguration(applicationModel);
+        this.defaultEnabled = 
configuration.getBoolean(McpConstant.SETTINGS_MCP_DEFAULT_ENABLED, true);
+
+        String includeStr = 
configuration.getString(McpConstant.SETTINGS_MCP_INCLUDE_PATTERNS, "");
+        String excludeStr = 
configuration.getString(McpConstant.SETTINGS_MCP_EXCLUDE_PATTERNS, "");
+
+        this.includePatterns = parsePatterns(includeStr);
+        this.excludePatterns = parsePatterns(excludeStr);
+    }
+
+    /**
+     * Check if service should be exposed as MCP tool.
+     * Priority: URL Parameters > Annotations > Configuration File > Default
+     */
+    public boolean shouldExposeAsMcpTool(ProviderModel providerModel) {
+        String interfaceName = 
providerModel.getServiceModel().getInterfaceName();
+
+        if (isMatchedByPatterns(interfaceName, excludePatterns)) {
+            return false;
+        }
+
+        URL serviceUrl = getServiceUrl(providerModel);
+        if (serviceUrl != null) {
+            String urlValue = 
serviceUrl.getParameter(McpConstant.PARAM_MCP_ENABLED);
+            if (urlValue != null && StringUtils.isNotEmpty(urlValue)) {
+                return Boolean.parseBoolean(urlValue);
+            }
+        }
+
+        Object serviceBean = providerModel.getServiceInstance();
+        if (serviceBean != null) {
+            DubboService dubboService = 
serviceBean.getClass().getAnnotation(DubboService.class);
+            if (dubboService != null && dubboService.mcpEnabled()) {
+                return true;
+            }

Review Comment:
   The code references `dubboService.mcpEnabled()` method on the DubboService 
annotation, but this method does not appear to exist in the standard Dubbo 
annotation interface. This will cause a compilation error.
   ```suggestion
               // Annotation-based MCP enablement is not supported via 
DubboService
   ```



##########
dubbo-plugin/dubbo-mcp/src/main/java/org/apache/dubbo/mcp/core/McpSseServiceImpl.java:
##########
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.mcp.core;
+
+import org.apache.dubbo.common.resource.Disposable;
+import org.apache.dubbo.common.stream.StreamObserver;
+import org.apache.dubbo.mcp.transport.DubboMcpSseTransportProvider;
+import org.apache.dubbo.remoting.http12.message.ServerSentEvent;
+
+public class McpSseServiceImpl implements McpSseService, Disposable {
+
+    private DubboMcpSseTransportProvider transportProvider = 
getTransportProvider();

Review Comment:
   The transportProvider field is initialized during field declaration and then 
conditionally reassigned. This could lead to unnecessary object creation. 
Consider initializing it as null and using lazy initialization consistently.
   ```suggestion
       private DubboMcpSseTransportProvider transportProvider = null;
   ```



##########
dubbo-plugin/dubbo-mcp/src/main/java/org/apache/dubbo/mcp/tool/DubboServiceToolRegistry.java:
##########
@@ -0,0 +1,469 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.mcp.tool;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.constants.LoggerCodeConstants;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.mcp.JsonSchemaType;
+import org.apache.dubbo.mcp.McpConstant;
+import org.apache.dubbo.mcp.annotations.McpToolParam;
+import org.apache.dubbo.mcp.core.McpServiceFilter;
+import org.apache.dubbo.mcp.util.TypeSchemaUtils;
+import org.apache.dubbo.rpc.model.ProviderModel;
+import org.apache.dubbo.rpc.model.ServiceDescriptor;
+import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.MethodMeta;
+import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ParameterMeta;
+import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ServiceMeta;
+import org.apache.dubbo.rpc.protocol.tri.rest.openapi.model.Operation;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.BiFunction;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.modelcontextprotocol.server.McpAsyncServer;
+import io.modelcontextprotocol.server.McpAsyncServerExchange;
+import io.modelcontextprotocol.server.McpServerFeatures;
+import io.modelcontextprotocol.spec.McpSchema;
+import reactor.core.publisher.Mono;
+
+public class DubboServiceToolRegistry {
+
+    private static final ErrorTypeAwareLogger logger =
+            
LoggerFactory.getErrorTypeAwareLogger(DubboServiceToolRegistry.class);
+
+    private final McpAsyncServer mcpServer;
+    private final DubboOpenApiToolConverter toolConverter;
+    private final DubboMcpGenericCaller genericCaller;
+    private final McpServiceFilter mcpServiceFilter;
+    private final Map<String, McpServerFeatures.AsyncToolSpecification> 
registeredTools = new ConcurrentHashMap<>();
+    private final Map<String, Set<String>> serviceToToolsMapping = new 
ConcurrentHashMap<>();
+    private final ObjectMapper objectMapper;
+
+    public DubboServiceToolRegistry(
+            McpAsyncServer mcpServer,
+            DubboOpenApiToolConverter toolConverter,
+            DubboMcpGenericCaller genericCaller,
+            McpServiceFilter mcpServiceFilter) {
+        this.mcpServer = mcpServer;
+        this.toolConverter = toolConverter;
+        this.genericCaller = genericCaller;
+        this.mcpServiceFilter = mcpServiceFilter;
+        this.objectMapper = new ObjectMapper();
+    }
+
+    public int registerService(ProviderModel providerModel) {
+        ServiceDescriptor serviceDescriptor = providerModel.getServiceModel();
+        List<URL> statedURLs = providerModel.getServiceUrls();
+
+        if (statedURLs == null || statedURLs.isEmpty()) {
+            return 0;
+        }
+
+        try {
+            URL url = statedURLs.get(0);
+            int registeredCount = 0;
+            String serviceKey = getServiceKey(providerModel);
+            Set<String> toolNames = new HashSet<>();
+
+            Class<?> serviceInterface = 
serviceDescriptor.getServiceInterfaceClass();
+            if (serviceInterface == null) {
+                return 0;
+            }
+
+            Method[] methods = serviceInterface.getDeclaredMethods();
+            boolean shouldRegisterServiceLevel = 
mcpServiceFilter.shouldExposeAsMcpTool(providerModel);
+
+            for (Method method : methods) {
+                if 
(mcpServiceFilter.shouldExposeMethodAsMcpTool(providerModel, method)) {
+                    McpServiceFilter.McpToolConfig toolConfig =
+                            mcpServiceFilter.getMcpToolConfig(providerModel, 
method);
+
+                    String toolName = registerMethodAsTool(providerModel, 
method, url, toolConfig);
+                    if (toolName != null) {
+                        toolNames.add(toolName);
+                        registeredCount++;
+                    }
+                }
+            }
+
+            if (registeredCount == 0 && shouldRegisterServiceLevel) {
+                Set<String> serviceToolNames = 
registerServiceLevelTools(providerModel, url);
+                toolNames.addAll(serviceToolNames);
+                registeredCount = serviceToolNames.size();
+            }
+
+            if (registeredCount > 0) {
+                serviceToToolsMapping.put(serviceKey, toolNames);
+                logger.info(
+                        "Registered {} MCP tools for service: {}",
+                        registeredCount,
+                        serviceDescriptor.getInterfaceName());
+            }
+
+            return registeredCount;
+
+        } catch (Exception e) {
+            logger.error(
+                    LoggerCodeConstants.COMMON_UNEXPECTED_EXCEPTION,
+                    "",
+                    "",
+                    "Failed to register service as MCP tools: " + 
serviceDescriptor.getInterfaceName(),
+                    e);
+            return 0;
+        }
+    }
+
+    public void unregisterService(ProviderModel providerModel) {
+        String serviceKey = getServiceKey(providerModel);
+        Set<String> toolNames = serviceToToolsMapping.remove(serviceKey);
+
+        if (toolNames == null || toolNames.isEmpty()) {
+            return;
+        }
+
+        int unregisteredCount = 0;
+        for (String toolName : toolNames) {
+            try {
+                McpServerFeatures.AsyncToolSpecification toolSpec = 
registeredTools.remove(toolName);
+                if (toolSpec != null) {
+                    mcpServer.removeTool(toolName).block();
+                    unregisteredCount++;
+                }
+            } catch (Exception e) {
+                logger.error(
+                        LoggerCodeConstants.COMMON_UNEXPECTED_EXCEPTION,
+                        "",
+                        "",
+                        "Failed to unregister MCP tool: " + toolName,
+                        e);
+            }
+        }
+
+        if (unregisteredCount > 0) {
+            logger.info(
+                    "Unregistered {} MCP tools for service: {}",
+                    unregisteredCount,
+                    providerModel.getServiceModel().getInterfaceName());
+        }
+    }
+
+    private String getServiceKey(ProviderModel providerModel) {
+        return providerModel.getServiceKey();
+    }
+
+    private String registerMethodAsTool(
+            ProviderModel providerModel, Method method, URL url, 
McpServiceFilter.McpToolConfig toolConfig) {
+        try {
+            String toolName = toolConfig.getToolName();
+            if (toolName == null || toolName.isEmpty()) {
+                toolName = method.getName();
+            }
+
+            if (registeredTools.containsKey(toolName)) {
+                return null;
+            }
+
+            String description = toolConfig.getDescription();
+            if (description == null || description.isEmpty()) {
+                description = generateDefaultDescription(method, 
providerModel);
+            }
+
+            McpSchema.Tool mcpTool = new McpSchema.Tool(toolName, description, 
generateToolSchema(method));

Review Comment:
   The generateToolSchema method is called synchronously during tool 
registration, which could impact performance when registering many services. 
Consider caching schema generation results or making it asynchronous.
   ```suggestion
               McpSchema.ToolSchema toolSchema = 
toolSchemaCache.computeIfAbsent(method, this::generateToolSchema);
               McpSchema.Tool mcpTool = new McpSchema.Tool(toolName, 
description, toolSchema);
   ```



##########
dubbo-plugin/dubbo-mcp/src/main/java/org/apache/dubbo/mcp/transport/DubboMcpStreamableTransportProvider.java:
##########
@@ -0,0 +1,499 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.mcp.transport;
+
+import org.apache.dubbo.cache.support.expiring.ExpiringMap;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.stream.StreamObserver;
+import org.apache.dubbo.common.utils.CollectionUtils;
+import org.apache.dubbo.common.utils.IOUtils;
+import org.apache.dubbo.common.utils.StringUtils;
+import org.apache.dubbo.remoting.http12.HttpMethods;
+import org.apache.dubbo.remoting.http12.HttpRequest;
+import org.apache.dubbo.remoting.http12.HttpResponse;
+import org.apache.dubbo.remoting.http12.HttpResult;
+import org.apache.dubbo.remoting.http12.HttpStatus;
+import org.apache.dubbo.remoting.http12.HttpUtils;
+import org.apache.dubbo.remoting.http12.message.MediaType;
+import org.apache.dubbo.remoting.http12.message.ServerSentEvent;
+import org.apache.dubbo.rpc.RpcContext;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.modelcontextprotocol.spec.McpError;
+import io.modelcontextprotocol.spec.McpSchema;
+import io.modelcontextprotocol.spec.McpStreamableServerSession;
+import io.modelcontextprotocol.spec.McpStreamableServerSession.Factory;
+import io.modelcontextprotocol.spec.McpStreamableServerTransport;
+import io.modelcontextprotocol.spec.McpStreamableServerTransportProvider;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import static 
org.apache.dubbo.common.constants.LoggerCodeConstants.COMMON_UNEXPECTED_EXCEPTION;
+
+/**
+ * Implementation of {@link McpStreamableServerTransportProvider} for the 
Dubbo MCP transport.
+ * This class provides methods to manage streamable server sessions and notify 
clients.
+ */
+public class DubboMcpStreamableTransportProvider implements 
McpStreamableServerTransportProvider {
+
+    private static final ErrorTypeAwareLogger logger =
+            
LoggerFactory.getErrorTypeAwareLogger(DubboMcpStreamableTransportProvider.class);
+
+    private Factory sessionFactory;
+
+    private final ObjectMapper objectMapper;
+
+    public static final String SESSION_ID_HEADER = "mcp-session-id";
+
+    private final ExpiringMap<String, McpStreamableServerSession> sessions = 
new ExpiringMap<>(30 * 60, 30);
+

Review Comment:
   The hardcoded values for session expiration (30 * 60 seconds and 30 cleanup 
period) should be made configurable through constants or configuration 
parameters for better maintainability.
   ```suggestion
       private static final int SESSION_EXPIRATION_SECONDS = 30 * 60; // 30 
minutes
       private static final int SESSION_CLEANUP_PERIOD_SECONDS = 30;  // 30 
seconds
   
       private final ExpiringMap<String, McpStreamableServerSession> sessions =
               new ExpiringMap<>(SESSION_EXPIRATION_SECONDS, 
SESSION_CLEANUP_PERIOD_SECONDS);
   ```



##########
dubbo-plugin/dubbo-mcp/src/main/java/org/apache/dubbo/mcp/tool/DubboMcpGenericCaller.java:
##########
@@ -0,0 +1,127 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.mcp.tool;
+
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.config.ApplicationConfig;
+import org.apache.dubbo.config.ReferenceConfig;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+import org.apache.dubbo.rpc.service.GenericService;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static 
org.apache.dubbo.common.constants.LoggerCodeConstants.COMMON_UNEXPECTED_EXCEPTION;
+
+public class DubboMcpGenericCaller {
+
+    private static final ErrorTypeAwareLogger logger =
+            LoggerFactory.getErrorTypeAwareLogger(DubboMcpGenericCaller.class);
+
+    private final ApplicationConfig applicationConfig;
+
+    private final Map<String, GenericService> serviceCache = new 
ConcurrentHashMap<>();
+
+    public DubboMcpGenericCaller(ApplicationModel applicationModel) {
+        if (applicationModel == null) {
+            logger.error(
+                    COMMON_UNEXPECTED_EXCEPTION, "", "", "ApplicationModel 
cannot be null for DubboMcpGenericCaller.");
+            throw new IllegalArgumentException("ApplicationModel cannot be 
null.");
+        }
+        this.applicationConfig = applicationModel.getCurrentConfig();
+        if (this.applicationConfig == null) {
+
+            String errMsg = "ApplicationConfig is null in the provided 
ApplicationModel. Application Name: "
+                    + (applicationModel.getApplicationName() != null ? 
applicationModel.getApplicationName() : "N/A");
+            logger.error(COMMON_UNEXPECTED_EXCEPTION, "", "", errMsg);
+            throw new IllegalStateException(errMsg);
+        }
+    }
+
+    public Object execute(
+            String interfaceName,
+            String methodName,
+            List<String> orderedJavaParameterNames,
+            Class<?>[] parameterJavaTypes,
+            Map<String, Object> mcpProvidedParameters,
+            String group,
+            String version) {
+        String cacheKey = interfaceName + ":" + (group == null ? "" : group) + 
":" + (version == null ? "" : version);
+        GenericService genericService = serviceCache.get(cacheKey);
+        if (genericService == null) {
+            ReferenceConfig<GenericService> reference = new 
ReferenceConfig<>();
+            reference.setApplication(this.applicationConfig);
+            reference.setInterface(interfaceName);
+            reference.setGeneric("true"); // Defaults to 'bean' or 'true' for 
POJO generalization.
+            reference.setScope("local");

Review Comment:
   The hardcoded scope value 'local' should be made configurable or documented 
as a constant to explain why local scope is always used for MCP tool calls.
   ```suggestion
               reference.setScope(MCP_TOOL_SCOPE);
   ```



##########
dubbo-plugin/dubbo-mcp/src/main/java/org/apache/dubbo/mcp/util/TypeSchemaUtils.java:
##########
@@ -0,0 +1,378 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.mcp.util;
+
+import org.apache.dubbo.mcp.JsonSchemaType;
+import org.apache.dubbo.mcp.McpConstant;
+
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.lang.reflect.WildcardType;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public final class TypeSchemaUtils {
+
+    public static TypeSchemaInfo resolveTypeSchema(Class<?> javaType, Type 
genericType, String description) {
+        TypeSchemaInfo.Builder builder =
+                
TypeSchemaInfo.builder().description(description).javaType(javaType.getName());
+
+        if (isPrimitiveType(javaType)) {
+            return builder.type(getPrimitiveJsonSchemaType(javaType))
+                    .format(getFormatForType(javaType))
+                    .build();
+        }
+
+        if (javaType.isArray()) {
+            TypeSchemaInfo itemSchema =
+                    resolveTypeSchema(javaType.getComponentType(), 
javaType.getComponentType(), "Array item");
+            return 
builder.type(JsonSchemaType.ARRAY_SCHEMA.getJsonSchemaType())
+                    .items(itemSchema)
+                    .build();
+        }
+
+        if (genericType instanceof ParameterizedType) {
+            return resolveParameterizedType((ParameterizedType) genericType, 
javaType, description, builder);
+        }
+
+        if (java.util.Collection.class.isAssignableFrom(javaType)) {
+            return 
builder.type(JsonSchemaType.ARRAY_SCHEMA.getJsonSchemaType())
+                    .items(TypeSchemaInfo.builder()
+                            
.type(JsonSchemaType.STRING_SCHEMA.getJsonSchemaType())
+                            .description("Collection item")
+                            .build())
+                    .build();
+        }
+
+        if (java.util.Map.class.isAssignableFrom(javaType)) {
+            return 
builder.type(JsonSchemaType.OBJECT_SCHEMA.getJsonSchemaType())
+                    .additionalProperties(TypeSchemaInfo.builder()
+                            
.type(JsonSchemaType.STRING_SCHEMA.getJsonSchemaType())
+                            .description("Map value")
+                            .build())
+                    .build();
+        }
+
+        if (javaType.isEnum()) {
+            Object[] enumConstants = javaType.getEnumConstants();
+            List<String> enumValues = new ArrayList<>();
+            if (enumConstants != null) {
+                for (Object enumConstant : enumConstants) {
+                    enumValues.add(enumConstant.toString());
+                }
+            }
+            return 
builder.type(JsonSchemaType.STRING_SCHEMA.getJsonSchemaType())
+                    .enumValues(enumValues)
+                    .build();
+        }
+
+        if (isDateTimeType(javaType)) {
+            return 
builder.type(JsonSchemaType.STRING_SCHEMA.getJsonSchemaType())
+                    .format(getDateTimeFormat(javaType))
+                    .build();
+        }
+
+        return builder.type(JsonSchemaType.OBJECT_SCHEMA.getJsonSchemaType())
+                .description(
+                        description != null
+                                ? description + " (POJO type: " + 
javaType.getSimpleName() + ")"
+                                : "Complex object of type " + 
javaType.getSimpleName())
+                .build();
+    }
+
+    private static TypeSchemaInfo resolveParameterizedType(
+            ParameterizedType paramType, Class<?> rawType, String description, 
TypeSchemaInfo.Builder builder) {
+
+        Type[] typeArgs = paramType.getActualTypeArguments();
+
+        if (java.util.Collection.class.isAssignableFrom(rawType)) {
+            TypeSchemaInfo itemSchema;
+            if (typeArgs.length > 0) {
+                Class<?> itemType = getClassFromType(typeArgs[0]);
+                itemSchema = resolveTypeSchema(itemType, typeArgs[0], 
"Collection item");
+            } else {
+                itemSchema = TypeSchemaInfo.builder()
+                        .type(JsonSchemaType.STRING_SCHEMA.getJsonSchemaType())
+                        .description("Collection item")
+                        .build();
+            }
+            return 
builder.type(JsonSchemaType.ARRAY_SCHEMA.getJsonSchemaType())
+                    .items(itemSchema)
+                    .build();
+        }
+
+        if (java.util.Map.class.isAssignableFrom(rawType)) {
+            TypeSchemaInfo valueSchema;
+            if (typeArgs.length > 1) {
+                Class<?> valueType = getClassFromType(typeArgs[1]);
+                valueSchema = resolveTypeSchema(valueType, typeArgs[1], "Map 
value");
+            } else {
+                valueSchema = TypeSchemaInfo.builder()
+                        .type(JsonSchemaType.STRING_SCHEMA.getJsonSchemaType())
+                        .description("Map value")
+                        .build();
+            }
+            return 
builder.type(JsonSchemaType.OBJECT_SCHEMA.getJsonSchemaType())
+                    .additionalProperties(valueSchema)
+                    .build();
+        }
+
+        return builder.type(JsonSchemaType.OBJECT_SCHEMA.getJsonSchemaType())
+                .description(
+                        description != null
+                                ? description + " (Generic type: " + 
rawType.getSimpleName() + ")"
+                                : "Complex generic object of type " + 
rawType.getSimpleName())
+                .build();
+    }
+
+    public static Map<String, Object> toSchemaMap(TypeSchemaInfo schemaInfo) {
+        Map<String, Object> schemaMap = new HashMap<>();
+
+        schemaMap.put(McpConstant.SCHEMA_PROPERTY_TYPE, schemaInfo.getType());
+
+        if (schemaInfo.getFormat() != null) {
+            schemaMap.put(McpConstant.SCHEMA_PROPERTY_FORMAT, 
schemaInfo.getFormat());
+        }
+
+        if (schemaInfo.getDescription() != null) {
+            schemaMap.put(McpConstant.SCHEMA_PROPERTY_DESCRIPTION, 
schemaInfo.getDescription());
+        }
+
+        if (schemaInfo.getEnumValues() != null && 
!schemaInfo.getEnumValues().isEmpty()) {
+            schemaMap.put(McpConstant.SCHEMA_PROPERTY_ENUM, 
schemaInfo.getEnumValues());
+        }
+
+        if (schemaInfo.getItems() != null) {
+            schemaMap.put(McpConstant.SCHEMA_PROPERTY_ITEMS, 
toSchemaMap(schemaInfo.getItems()));
+        }
+
+        if (schemaInfo.getAdditionalProperties() != null) {
+            schemaMap.put(
+                    McpConstant.SCHEMA_PROPERTY_ADDITIONAL_PROPERTIES,
+                    toSchemaMap(schemaInfo.getAdditionalProperties()));
+        }
+
+        return schemaMap;
+    }
+
+    public static TypeSchemaInfo resolveNestedType(Type type, String 
description) {
+        if (type instanceof Class) {
+            return resolveTypeSchema((Class<?>) type, type, description);
+        }
+
+        if (type instanceof ParameterizedType paramType) {
+            Class<?> rawType = (Class<?>) paramType.getRawType();
+            return resolveTypeSchema(rawType, type, description);
+        }
+
+        if (type instanceof TypeVariable) {
+            Type[] bounds = ((TypeVariable<?>) type).getBounds();
+            if (bounds.length > 0) {
+                return resolveNestedType(bounds[0], description);
+            }
+        }
+
+        if (type instanceof WildcardType) {
+            Type[] upperBounds = ((WildcardType) type).getUpperBounds();
+            if (upperBounds.length > 0) {
+                return resolveNestedType(upperBounds[0], description);
+            }
+        }
+
+        if (type instanceof GenericArrayType) {
+            Type componentType = ((GenericArrayType) 
type).getGenericComponentType();
+            TypeSchemaInfo itemSchema = resolveNestedType(componentType, 
"Array item");
+            return TypeSchemaInfo.builder()
+                    .type(JsonSchemaType.ARRAY_SCHEMA.getJsonSchemaType())
+                    .items(itemSchema)
+                    .description(description)
+                    .build();
+        }
+
+        // Fallback to object type
+        return TypeSchemaInfo.builder()
+                .type(JsonSchemaType.OBJECT_SCHEMA.getJsonSchemaType())
+                .description(description != null ? description : "Unknown 
type")
+                .build();
+    }
+
+    public static boolean isPrimitiveType(Class<?> type) {
+        return JsonSchemaType.fromJavaType(type) != null;
+    }
+
+    public static String getPrimitiveJsonSchemaType(Class<?> javaType) {
+        JsonSchemaType mapping = JsonSchemaType.fromJavaType(javaType);
+        return mapping != null ? mapping.getJsonSchemaType() : 
JsonSchemaType.STRING_SCHEMA.getJsonSchemaType();
+    }
+
+    public static String getFormatForType(Class<?> javaType) {
+        JsonSchemaType mapping = JsonSchemaType.fromJavaType(javaType);
+        return mapping != null ? mapping.getJsonSchemaFormat() : null;
+    }
+
+    public static boolean isDateTimeType(Class<?> type) {
+        return java.util.Date.class.isAssignableFrom(type)
+                || java.time.temporal.Temporal.class.isAssignableFrom(type)
+                || java.util.Calendar.class.isAssignableFrom(type);
+    }
+
+    public static String getDateTimeFormat(Class<?> type) {
+        if (java.time.LocalDate.class.isAssignableFrom(type)) {
+            return JsonSchemaType.DATE_FORMAT.getJsonSchemaFormat();
+        }
+        if (java.time.LocalTime.class.isAssignableFrom(type) || 
java.time.OffsetTime.class.isAssignableFrom(type)) {
+            return JsonSchemaType.TIME_FORMAT.getJsonSchemaFormat();
+        }
+        return JsonSchemaType.DATE_TIME_FORMAT.getJsonSchemaFormat();
+    }
+
+    public static Class<?> getClassFromType(Type type) {
+        if (type instanceof Class) {
+            return (Class<?>) type;
+        }
+        if (type instanceof ParameterizedType) {
+            return (Class<?>) ((ParameterizedType) type).getRawType();
+        }
+        if (type instanceof GenericArrayType) {
+            Type componentType = ((GenericArrayType) 
type).getGenericComponentType();
+            Class<?> componentClass = getClassFromType(componentType);
+            return java.lang.reflect.Array.newInstance(componentClass, 
0).getClass();
+        }
+        if (type instanceof TypeVariable) {
+            Type[] bounds = ((TypeVariable<?>) type).getBounds();
+            if (bounds.length > 0) {
+                return getClassFromType(bounds[0]);
+            }
+        }
+        if (type instanceof WildcardType) {
+            Type[] upperBounds = ((WildcardType) type).getUpperBounds();
+            if (upperBounds.length > 0) {
+                return getClassFromType(upperBounds[0]);
+            }
+        }
+        return Object.class;
+    }
+
+    public static boolean isPrimitiveOrWrapper(Class<?> type) {
+        return isPrimitiveType(type);

Review Comment:
   [nitpick] The method isPrimitiveOrWrapper delegates to isPrimitiveType which 
checks JsonSchemaType.fromJavaType. This indirection is confusing - consider 
renaming for clarity or implementing the wrapper type check directly.
   ```suggestion
           // Directly check for primitive types and their wrappers
           return type.isPrimitive()
                   || type == Boolean.class
                   || type == Byte.class
                   || type == Character.class
                   || type == Short.class
                   || type == Integer.class
                   || type == Long.class
                   || type == Float.class
                   || type == Double.class
                   || type == Void.class;
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to