You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@dubbo.apache.org by "Stellar1999 (via GitHub)" <gi...@apache.org> on 2025/05/20 12:16:08 UTC
[PR] Dubbo MCP Integration [dubbo]
Stellar1999 opened a new pull request, #15406:
URL: https://github.com/apache/dubbo/pull/15406
## What is the purpose of the change?
Ref: #15332
## Checklist
- [x] Make sure there is a [GitHub_issue](https://github.com/apache/dubbo/issues) field for the change.
- [x] Write a pull request description that is detailed enough to understand what the pull request does, how, and why.
- [x] Write necessary unit-test to verify your logic correction. If the new feature or significant change is committed, please remember to add sample in [dubbo samples](https://github.com/apache/dubbo-samples) project.
- [x] Make sure gitHub actions can pass. [Why the workflow is failing and how to fix it?](../CONTRIBUTING.md)
--
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: notifications-unsubscribe@dubbo.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@dubbo.apache.org
For additional commands, e-mail: notifications-help@dubbo.apache.org
Re: [PR] Dubbo MCP SSE Integration [dubbo]
Posted by "songxiaosheng (via GitHub)" <gi...@apache.org>.
songxiaosheng commented on code in PR #15406:
URL: https://github.com/apache/dubbo/pull/15406#discussion_r2239414572
##########
dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ServiceConfig.java:
##########
@@ -196,6 +201,15 @@ public boolean isUnexported() {
return unexported;
}
+ @Parameter(attribute = false, key = "mcp.enabled")
+ public boolean getMcpEnabled() {
Review Comment:
Can this method be rewritten as isMcpEnabled?
--
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: notifications-unsubscribe@dubbo.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@dubbo.apache.org
For additional commands, e-mail: notifications-help@dubbo.apache.org
Re: [PR] Dubbo MCP Integration [dubbo]
Posted by "Stellar1999 (via GitHub)" <gi...@apache.org>.
Stellar1999 commented on PR #15406:
URL: https://github.com/apache/dubbo/pull/15406#issuecomment-2910950928
> > > There are many Chinese comments in the change commit.
> >
> >
> > Done.Translation completed.
>
> no, there are still chinese comments. i.e. McpConstant.java
You're right. done.
--
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: notifications-unsubscribe@dubbo.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@dubbo.apache.org
For additional commands, e-mail: notifications-help@dubbo.apache.org
Re: [PR] Dubbo MCP Integration [dubbo]
Posted by "AlbumenJ (via GitHub)" <gi...@apache.org>.
AlbumenJ commented on code in PR #15406:
URL: https://github.com/apache/dubbo/pull/15406#discussion_r2108081336
##########
dubbo-plugin/dubbo-mcp/pom.xml:
##########
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.dubbo</groupId>
+ <artifactId>dubbo-parent</artifactId>
+ <version>${revision}</version>
+ <relativePath>../../pom.xml</relativePath>
+ </parent>
+
+ <artifactId>dubbo-mcp</artifactId>
+ <properties>
+ <mcp.version>0.9.0</mcp.version>
+ <skip_maven_deploy>false</skip_maven_deploy>
+ <guava.version>33.2.1-jre</guava.version>
Review Comment:
Why import guava here
##########
dubbo-plugin/dubbo-mcp/src/main/java/org/apache/dubbo/mcp/core/McpApplicationDeployListener.java:
##########
@@ -0,0 +1,197 @@
+/*
+ * 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.config.Configuration;
+import org.apache.dubbo.common.config.ConfigurationUtils;
+import org.apache.dubbo.common.constants.CommonConstants;
+import org.apache.dubbo.common.constants.LoggerCodeConstants;
+import org.apache.dubbo.common.deploy.ApplicationDeployListener;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.threadpool.manager.FrameworkExecutorRepository;
+import org.apache.dubbo.common.utils.CollectionUtils;
+import org.apache.dubbo.common.utils.NetUtils;
+import org.apache.dubbo.config.ProtocolConfig;
+import org.apache.dubbo.config.ServiceConfig;
+import org.apache.dubbo.config.bootstrap.builders.InternalServiceConfigBuilder;
+import org.apache.dubbo.mcp.McpConstant;
+import org.apache.dubbo.mcp.tool.DubboMcpGenericCaller;
+import org.apache.dubbo.mcp.tool.DubboOpenApiToolConverter;
+import org.apache.dubbo.mcp.tool.DubboServiceToolRegistry;
+import org.apache.dubbo.mcp.transport.DubboMcpSseTransportProvider;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+import org.apache.dubbo.rpc.model.FrameworkModel;
+import org.apache.dubbo.rpc.model.ProviderModel;
+import org.apache.dubbo.rpc.protocol.tri.rest.openapi.DefaultOpenAPIService;
+
+import java.util.Collection;
+import java.util.concurrent.ExecutorService;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.modelcontextprotocol.server.McpAsyncServer;
+import io.modelcontextprotocol.server.McpServer;
+import io.modelcontextprotocol.spec.McpSchema;
+
+import static org.apache.dubbo.metadata.util.MetadataServiceVersionUtils.V1;
+
+public class McpApplicationDeployListener implements ApplicationDeployListener {
+
+ private static final ErrorTypeAwareLogger logger =
+ LoggerFactory.getErrorTypeAwareLogger(McpApplicationDeployListener.class);
+ private DubboServiceToolRegistry toolRegistry;
+ private McpServiceFilter mcpServiceFilter;
+ private boolean mcpEnable = true;
+
+ private volatile ServiceConfig<McpSseService> serviceConfig;
+
+ private static DubboMcpSseTransportProvider dubboMcpSseTransportProvider;
+
+ @Override
+ public void onInitialize(ApplicationModel scopeModel) {}
+
+ @Override
+ public void onStarting(ApplicationModel applicationModel) {}
+
+ public static DubboMcpSseTransportProvider getDubboMcpSseTransportProvider() {
+ return dubboMcpSseTransportProvider;
+ }
+
+ @Override
+ public void onStarted(ApplicationModel applicationModel) {
+ Configuration globalConf = ConfigurationUtils.getGlobalConfiguration(ApplicationModel.defaultModel());
Review Comment:
Prevent using `ApplicationModel.defaultModel()`
##########
dubbo-plugin/dubbo-mcp/src/main/java/org/apache/dubbo/mcp/core/McpServiceFilter.java:
##########
@@ -0,0 +1,191 @@
+/*
+ * 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.config.Configuration;
+import org.apache.dubbo.common.config.ConfigurationUtils;
+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.rpc.model.ApplicationModel;
+import org.apache.dubbo.rpc.model.ProviderModel;
+
+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);
+
+ // Parse include and exclude patterns
+ 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);
+
+ logger.debug(
+ "MCP service filter initialized: defaultEnabled={}, includePatterns={}, excludePatterns={}",
+ defaultEnabled,
+ includeStr,
+ excludeStr);
+ }
+
+ /**
+ * Check if service should be exposed as MCP tool
+ */
+ public boolean shouldExposeAsMcpTool(ProviderModel providerModel) {
+ String interfaceName = providerModel.getServiceModel().getInterfaceName();
+
+ // 1. Check exclude patterns (the highest priority)
+ if (isMatchedByPatterns(interfaceName, excludePatterns)) {
+ return false;
+ }
+
+ // 2. Check annotation configuration
+ Object serviceBean = providerModel.getServiceInstance();
+ if (serviceBean != null) {
+ DubboService dubboService = serviceBean.getClass().getAnnotation(DubboService.class);
+ if (dubboService != null && dubboService.mcpEnabled()) {
+ return true;
+ }
+ }
Review Comment:
Should get properties from configs or URL.
##########
dubbo-plugin/dubbo-mcp/src/main/java/org/apache/dubbo/mcp/core/McpApplicationDeployListener.java:
##########
@@ -0,0 +1,197 @@
+/*
+ * 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.config.Configuration;
+import org.apache.dubbo.common.config.ConfigurationUtils;
+import org.apache.dubbo.common.constants.CommonConstants;
+import org.apache.dubbo.common.constants.LoggerCodeConstants;
+import org.apache.dubbo.common.deploy.ApplicationDeployListener;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.threadpool.manager.FrameworkExecutorRepository;
+import org.apache.dubbo.common.utils.CollectionUtils;
+import org.apache.dubbo.common.utils.NetUtils;
+import org.apache.dubbo.config.ProtocolConfig;
+import org.apache.dubbo.config.ServiceConfig;
+import org.apache.dubbo.config.bootstrap.builders.InternalServiceConfigBuilder;
+import org.apache.dubbo.mcp.McpConstant;
+import org.apache.dubbo.mcp.tool.DubboMcpGenericCaller;
+import org.apache.dubbo.mcp.tool.DubboOpenApiToolConverter;
+import org.apache.dubbo.mcp.tool.DubboServiceToolRegistry;
+import org.apache.dubbo.mcp.transport.DubboMcpSseTransportProvider;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+import org.apache.dubbo.rpc.model.FrameworkModel;
+import org.apache.dubbo.rpc.model.ProviderModel;
+import org.apache.dubbo.rpc.protocol.tri.rest.openapi.DefaultOpenAPIService;
+
+import java.util.Collection;
+import java.util.concurrent.ExecutorService;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.modelcontextprotocol.server.McpAsyncServer;
+import io.modelcontextprotocol.server.McpServer;
+import io.modelcontextprotocol.spec.McpSchema;
+
+import static org.apache.dubbo.metadata.util.MetadataServiceVersionUtils.V1;
+
+public class McpApplicationDeployListener implements ApplicationDeployListener {
+
+ private static final ErrorTypeAwareLogger logger =
+ LoggerFactory.getErrorTypeAwareLogger(McpApplicationDeployListener.class);
+ private DubboServiceToolRegistry toolRegistry;
+ private McpServiceFilter mcpServiceFilter;
+ private boolean mcpEnable = true;
+
+ private volatile ServiceConfig<McpSseService> serviceConfig;
+
+ private static DubboMcpSseTransportProvider dubboMcpSseTransportProvider;
+
+ @Override
+ public void onInitialize(ApplicationModel scopeModel) {}
+
+ @Override
+ public void onStarting(ApplicationModel applicationModel) {}
+
+ public static DubboMcpSseTransportProvider getDubboMcpSseTransportProvider() {
+ return dubboMcpSseTransportProvider;
+ }
+
+ @Override
+ public void onStarted(ApplicationModel applicationModel) {
+ Configuration globalConf = ConfigurationUtils.getGlobalConfiguration(ApplicationModel.defaultModel());
+ mcpEnable = globalConf.getBoolean(McpConstant.SETTINGS_MCP_ENABLE, true);
+ if (!mcpEnable) {
+ logger.info("MCP service is disabled, skipping initialization");
+ return;
+ }
+ try {
+ logger.info("Initializing MCP server and tools");
+
+ // Initialize service filter
+ mcpServiceFilter = new McpServiceFilter(applicationModel);
+
+ dubboMcpSseTransportProvider = new DubboMcpSseTransportProvider(new ObjectMapper());
+ McpSchema.ServerCapabilities.ToolCapabilities toolCapabilities =
+ new McpSchema.ServerCapabilities.ToolCapabilities(true);
+ McpSchema.ServerCapabilities serverCapabilities =
+ new McpSchema.ServerCapabilities(null, null, null, null, toolCapabilities);
+
+ McpAsyncServer mcpAsyncServer = McpServer.async(dubboMcpSseTransportProvider)
+ .capabilities(serverCapabilities)
+ .build();
+
+ FrameworkModel frameworkModel = applicationModel.getFrameworkModel();
+ DefaultOpenAPIService defaultOpenAPIService = new DefaultOpenAPIService(frameworkModel);
+
+ DubboOpenApiToolConverter toolConverter = new DubboOpenApiToolConverter(defaultOpenAPIService);
+
+ DubboMcpGenericCaller genericCaller = new DubboMcpGenericCaller(applicationModel);
+
+ toolRegistry = new DubboServiceToolRegistry(mcpAsyncServer, toolConverter, genericCaller);
+
+ Collection<ProviderModel> providerModels =
+ applicationModel.getApplicationServiceRepository().allProviderModels();
+
+ int registeredCount = 0;
+ for (ProviderModel pm : providerModels) {
+ // Check if service should be exposed as MCP tool
+ if (mcpServiceFilter.shouldExposeAsMcpTool(pm)) {
+ // Get MCP tool configuration
+ McpServiceFilter.McpToolConfig toolConfig = mcpServiceFilter.getMcpToolConfig(pm);
+ toolRegistry.registerService(pm, toolConfig);
+ registeredCount++;
+ }
+ }
Review Comment:
How to manage dynamic exported services
##########
dubbo-plugin/dubbo-mcp/src/main/java/org/apache/dubbo/mcp/tool/DubboMcpGenericCaller.java:
##########
@@ -0,0 +1,133 @@
+/*
+ * 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.
+
+ // If no registries are configured, assume local JVM call is intended or possible.
+ if (this.applicationConfig.getRegistries() == null
+ || this.applicationConfig.getRegistries().isEmpty()) {
+ reference.setScope("local");
+ }
Review Comment:
Should usimg injvm invoke in MCP
--
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: notifications-unsubscribe@dubbo.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@dubbo.apache.org
For additional commands, e-mail: notifications-help@dubbo.apache.org
Re: [PR] Dubbo MCP Integration [dubbo]
Posted by "SpursJiang (via GitHub)" <gi...@apache.org>.
SpursJiang commented on PR #15406:
URL: https://github.com/apache/dubbo/pull/15406#issuecomment-2910034977
Dear Dubbo team:
Once a Dubbo service interface is registered as an MCP (Mesh Control Plane) tool, is its metadata stored exclusively in local memory? Furthermore, are there any plans to register MCP tools with the MCP register in Nacos version 3?
Is the Streamable HTTP protocol supported?
--
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: notifications-unsubscribe@dubbo.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@dubbo.apache.org
For additional commands, e-mail: notifications-help@dubbo.apache.org
Re: [PR] Dubbo MCP Integration [dubbo]
Posted by "RainYuY (via GitHub)" <gi...@apache.org>.
RainYuY merged PR #15406:
URL: https://github.com/apache/dubbo/pull/15406
--
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: notifications-unsubscribe@dubbo.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@dubbo.apache.org
For additional commands, e-mail: notifications-help@dubbo.apache.org
Re: [PR] Dubbo MCP SSE Integration [dubbo]
Posted by "zrlw (via GitHub)" <gi...@apache.org>.
zrlw commented on PR #15406:
URL: https://github.com/apache/dubbo/pull/15406#issuecomment-3157225210
@wcy666103 spotless:apply to fix code format issue.
--
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: notifications-unsubscribe@dubbo.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@dubbo.apache.org
For additional commands, e-mail: notifications-help@dubbo.apache.org
Re: [PR] Dubbo MCP SSE Integration [dubbo]
Posted by "RainYuY (via GitHub)" <gi...@apache.org>.
RainYuY commented on code in PR #15406:
URL: https://github.com/apache/dubbo/pull/15406#discussion_r2233630380
##########
dubbo-demo/dubbo-demo-spring-boot/pom.xml:
##########
@@ -31,6 +31,7 @@
<module>dubbo-demo-spring-boot-provider</module>
<module>dubbo-demo-spring-boot-interface</module>
<module>dubbo-demo-spring-boot-servlet</module>
+ <module>dubbo-demo-mcp-server</module>
Review Comment:
I've moved the demo to a new location, but I don't think it's necessary to add a README for it at this stage since it just needs to be run directly. After this PR is merged, I'll add more detailed documentation about dubbo-mcp in the official docs.
--
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: notifications-unsubscribe@dubbo.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@dubbo.apache.org
For additional commands, e-mail: notifications-help@dubbo.apache.org
Re: [PR] Dubbo MCP Integration [dubbo]
Posted by "Copilot (via GitHub)" <gi...@apache.org>.
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: notifications-unsubscribe@dubbo.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@dubbo.apache.org
For additional commands, e-mail: notifications-help@dubbo.apache.org
Re: [PR] Dubbo MCP Integration [dubbo]
Posted by "Stellar1999 (via GitHub)" <gi...@apache.org>.
Stellar1999 commented on PR #15406:
URL: https://github.com/apache/dubbo/pull/15406#issuecomment-2910953774
> Dear Dubbo team: Once a Dubbo service interface is registered as an MCP (Mesh Control Plane) tool, is its metadata stored exclusively in local memory? Furthermore, are there any plans to register MCP tools with the MCP register in Nacos version 3? Is the Streamable HTTP protocol supported?
1. Is its metadata stored exclusively in local memory? Yes.
2. Furthermore, are there any plans to register MCP tools with the MCP registry in Nacos version 3? Under consideration.
3. Is the Streamable HTTP protocol supported? No, but I believe we will support it in the future.Now just sse.
--
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: notifications-unsubscribe@dubbo.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@dubbo.apache.org
For additional commands, e-mail: notifications-help@dubbo.apache.org
Re: [PR] Dubbo MCP SSE Integration [dubbo]
Posted by "conghuhu (via GitHub)" <gi...@apache.org>.
conghuhu commented on code in PR #15406:
URL: https://github.com/apache/dubbo/pull/15406#discussion_r2217531603
##########
dubbo-demo/dubbo-demo-spring-boot/pom.xml:
##########
@@ -31,6 +31,7 @@
<module>dubbo-demo-spring-boot-provider</module>
<module>dubbo-demo-spring-boot-interface</module>
<module>dubbo-demo-spring-boot-servlet</module>
+ <module>dubbo-demo-mcp-server</module>
Review Comment:
<img width="872" height="614" alt="image" src="https://github.com/user-attachments/assets/867e1ed0-1336-4034-a222-d59983adff80" />
It seems that you shouldn't place `dubbo-demo-mcp-server` under `/dubbo-demo-spring-boot`. The directory structure is not very reasonable
--
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: notifications-unsubscribe@dubbo.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@dubbo.apache.org
For additional commands, e-mail: notifications-help@dubbo.apache.org
Re: [PR] Dubbo MCP Integration [dubbo]
Posted by "RainYuY (via GitHub)" <gi...@apache.org>.
RainYuY commented on code in PR #15406:
URL: https://github.com/apache/dubbo/pull/15406#discussion_r2268633213
##########
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:
I believe asynchronous loading is not a suitable solution, as it may prevent the application from detecting the MCP server after startup.
--
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: notifications-unsubscribe@dubbo.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@dubbo.apache.org
For additional commands, e-mail: notifications-help@dubbo.apache.org
Re: [PR] Dubbo MCP SSE Integration [dubbo]
Posted by "Copilot (via GitHub)" <gi...@apache.org>.
Copilot commented on code in PR #15406:
URL: https://github.com/apache/dubbo/pull/15406#discussion_r2249640592
##########
dubbo-plugin/dubbo-mcp/src/main/java/org/apache/dubbo/mcp/transport/DubboMcpSseTransportProvider.java:
##########
@@ -0,0 +1,202 @@
+/*
+ * 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.IOUtils;
+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.message.ServerSentEvent;
+import org.apache.dubbo.rpc.RpcContext;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+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.McpServerSession;
+import io.modelcontextprotocol.spec.McpServerTransport;
+import io.modelcontextprotocol.spec.McpServerTransportProvider;
+import io.netty.util.internal.StringUtil;
Review Comment:
Avoid using internal Netty classes like `io.netty.util.internal.StringUtil`. Use standard Java utilities like `org.apache.dubbo.common.utils.StringUtils` or `java.util.Objects` for null/empty checks instead.
```suggestion
```
##########
dubbo-plugin/dubbo-mcp/src/main/java/org/apache/dubbo/mcp/transport/DubboMcpSseTransportProvider.java:
##########
@@ -0,0 +1,202 @@
+/*
+ * 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.IOUtils;
+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.message.ServerSentEvent;
+import org.apache.dubbo.rpc.RpcContext;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+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.McpServerSession;
+import io.modelcontextprotocol.spec.McpServerTransport;
+import io.modelcontextprotocol.spec.McpServerTransportProvider;
+import io.netty.util.internal.StringUtil;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.COMMON_UNEXPECTED_EXCEPTION;
+
+public class DubboMcpSseTransportProvider implements McpServerTransportProvider {
+
+ private static final ErrorTypeAwareLogger logger =
+ LoggerFactory.getErrorTypeAwareLogger(DubboMcpSseTransportProvider.class);
+
+ /**
+ * Event type for JSON-RPC messages sent through the SSE connection.
+ */
+ public static final String MESSAGE_EVENT_TYPE = "message";
+
+ /**
+ * Event type for sending the message endpoint URI to clients.
+ */
+ public static final String ENDPOINT_EVENT_TYPE = "endpoint";
+
+ private McpServerSession.Factory sessionFactory;
+
+ private final ObjectMapper objectMapper;
+
+ private final ExpiringMap<String, McpServerSession> sessions = new ExpiringMap<>(30 * 60, 30);
+
+ public DubboMcpSseTransportProvider(ObjectMapper objectMapper) {
+ this.objectMapper = objectMapper;
+ sessions.getExpireThread().startExpiryIfNotStarted();
+ }
+
+ @Override
+ public void setSessionFactory(McpServerSession.Factory sessionFactory) {
+ this.sessionFactory = sessionFactory;
+ }
+
+ @Override
+ public Mono<Void> notifyClients(String method, Object params) {
+ if (sessions.isEmpty()) {
+ return Mono.empty();
+ }
+ return Flux.fromIterable(sessions.values())
+ .flatMap(session -> session.sendNotification(method, params)
+ .doOnError(e -> logger.error(
+ COMMON_UNEXPECTED_EXCEPTION,
+ "",
+ "",
+ String.format(
+ "Failed to send message to session %s: %s", session.getId(), e.getMessage()),
+ e))
+ .onErrorComplete())
+ .then();
+ }
+
+ @Override
+ public Mono<Void> closeGracefully() {
+ return Flux.fromIterable(sessions.values())
+ .flatMap(McpServerSession::closeGracefully)
+ .then();
+ }
+
+ public void handleRequest(StreamObserver<ServerSentEvent<String>> responseObserver) {
+ // Handle the request and return the response
+ HttpRequest request = RpcContext.getServiceContext().getRequest(HttpRequest.class);
+ if (HttpMethods.isGet(request.method())) {
+ handleSseConnection(responseObserver);
+ } else if (HttpMethods.isPost(request.method())) {
+ handleMessage();
+ }
+ return;
+ }
+
+ public void handleMessage() {
+ HttpRequest request = RpcContext.getServiceContext().getRequest(HttpRequest.class);
+ String sessionId = request.parameter("sessionId");
+ HttpResponse response = RpcContext.getServiceContext().getResponse(HttpResponse.class);
+ if (StringUtil.isNullOrEmpty(sessionId)) {
Review Comment:
Replace `StringUtil.isNullOrEmpty(sessionId)` with `StringUtils.isEmpty(sessionId)` from Dubbo's common utilities to avoid using internal Netty classes.
```suggestion
if (StringUtils.isEmpty(sessionId)) {
```
##########
dubbo-plugin/dubbo-mcp/src/main/java/org/apache/dubbo/mcp/transport/DubboMcpStreamableTransportProvider.java:
##########
@@ -0,0 +1,135 @@
+/*
+ * 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.stream.StreamObserver;
+import org.apache.dubbo.common.utils.CollectionUtils;
+import org.apache.dubbo.common.utils.JsonUtils;
+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.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.util.List;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.modelcontextprotocol.spec.McpError;
+import io.modelcontextprotocol.spec.McpStreamableServerSession;
+import io.modelcontextprotocol.spec.McpStreamableServerSession.Factory;
+import io.modelcontextprotocol.spec.McpStreamableServerTransportProvider;
+import reactor.core.publisher.Mono;
+
+/**
+ * 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 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);
+
+ public DubboMcpStreamableTransportProvider(ObjectMapper objectMapper) {
+ this.objectMapper = objectMapper;
+ }
+
+ @Override
+ public void setSessionFactory(Factory sessionFactory) {
+ this.sessionFactory = sessionFactory;
+ }
+
+ @Override
+ public Mono<Void> notifyClients(String method, Object params) {
+ return null;
+ }
+
+ @Override
+ public void close() {}
+
+ @Override
+ public Mono<Void> closeGracefully() {
+ return null;
+ }
+
+ public void handleRequest(StreamObserver<ServerSentEvent<String>> responseObserver) {
+ // Handle the request and return the response
+ HttpRequest request = RpcContext.getServiceContext().getRequest(HttpRequest.class);
+ if (HttpMethods.isGet(request.method())) {
+ handleGet(responseObserver);
+
+ } else if (HttpMethods.isPost(request.method())) {
+ handlePost(responseObserver);
+ }
+ return;
+ }
+
+ private void handleGet(StreamObserver<ServerSentEvent<String>> responseObserver) {
+ HttpRequest request = RpcContext.getServiceContext().getRequest(HttpRequest.class);
+ // check header
+ List<String> accepts = HttpUtils.parseAccept(request.accept());
+ if (CollectionUtils.isEmpty(accepts)
+ || !accepts.contains(MediaType.TEXT_EVENT_STREAM.getName())
+ || !accepts.contains(MediaType.APPLICATION_JSON.getName())) {
+ // 如果没有包含必须类型,则返回异常
Review Comment:
[nitpick] Comments should be in English for consistency with the rest of the codebase. Consider translating Chinese comments to English.
```suggestion
// If the required types are not included, return an error
```
##########
dubbo-plugin/dubbo-mcp/src/main/java/org/apache/dubbo/mcp/core/McpApplicationDeployListener.java:
##########
@@ -0,0 +1,205 @@
+/*
+ * 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.config.Configuration;
+import org.apache.dubbo.common.config.ConfigurationUtils;
+import org.apache.dubbo.common.constants.CommonConstants;
+import org.apache.dubbo.common.constants.LoggerCodeConstants;
+import org.apache.dubbo.common.deploy.ApplicationDeployListener;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.threadpool.manager.FrameworkExecutorRepository;
+import org.apache.dubbo.common.utils.CollectionUtils;
+import org.apache.dubbo.common.utils.NetUtils;
+import org.apache.dubbo.config.ProtocolConfig;
+import org.apache.dubbo.config.ServiceConfig;
+import org.apache.dubbo.config.bootstrap.builders.InternalServiceConfigBuilder;
+import org.apache.dubbo.mcp.McpConstant;
+import org.apache.dubbo.mcp.tool.DubboMcpGenericCaller;
+import org.apache.dubbo.mcp.tool.DubboOpenApiToolConverter;
+import org.apache.dubbo.mcp.tool.DubboServiceToolRegistry;
+import org.apache.dubbo.mcp.transport.DubboMcpSseTransportProvider;
+import org.apache.dubbo.mcp.transport.DubboMcpStreamableTransportProvider;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+import org.apache.dubbo.rpc.model.FrameworkModel;
+import org.apache.dubbo.rpc.model.ProviderModel;
+import org.apache.dubbo.rpc.protocol.tri.rest.openapi.DefaultOpenAPIService;
+
+import java.util.Collection;
+import java.util.concurrent.ExecutorService;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.modelcontextprotocol.server.McpAsyncServer;
+import io.modelcontextprotocol.server.McpServer;
+import io.modelcontextprotocol.spec.McpSchema;
+
+import static org.apache.dubbo.metadata.util.MetadataServiceVersionUtils.V1;
+
+public class McpApplicationDeployListener implements ApplicationDeployListener {
+
+ private static final ErrorTypeAwareLogger logger =
+ LoggerFactory.getErrorTypeAwareLogger(McpApplicationDeployListener.class);
+ private DubboServiceToolRegistry toolRegistry;
+ private McpServiceFilter mcpServiceFilter;
+ private boolean mcpEnable = true;
+
+ private volatile ServiceConfig<McpSseService> serviceConfig;
+
+ private static DubboMcpSseTransportProvider dubboMcpSseTransportProvider;
+
+ private static DubboMcpStreamableTransportProvider dubboMcpStreamableTransportProvider;
+
+ private McpAsyncServer mcpAsyncServer;
+
+ @Override
+ public void onInitialize(ApplicationModel scopeModel) {}
+
+ @Override
+ public void onStarting(ApplicationModel applicationModel) {}
+
+ public static DubboMcpSseTransportProvider getDubboMcpSseTransportProvider() {
+ return dubboMcpSseTransportProvider;
+ }
+
+ public static DubboMcpStreamableTransportProvider getDubboMcpStreamableTransportProvider() {
+ return dubboMcpStreamableTransportProvider;
+ }
+
+ @Override
+ public void onStarted(ApplicationModel applicationModel) {
+ Configuration globalConf = ConfigurationUtils.getGlobalConfiguration(applicationModel);
+ mcpEnable = globalConf.getBoolean(McpConstant.SETTINGS_MCP_ENABLE, true);
+ if (!mcpEnable) {
+ logger.info("MCP service is disabled, skipping initialization");
+ return;
+ }
+ try {
+ logger.info("Initializing MCP server and dynamic service registration");
+
+ // Initialize service filter
+ mcpServiceFilter = new McpServiceFilter(applicationModel);
+
+ dubboMcpSseTransportProvider = new DubboMcpSseTransportProvider(new ObjectMapper());
+ McpSchema.ServerCapabilities.ToolCapabilities toolCapabilities =
+ new McpSchema.ServerCapabilities.ToolCapabilities(true);
+ McpSchema.ServerCapabilities serverCapabilities =
+ new McpSchema.ServerCapabilities(null, null, null, null, null, toolCapabilities);
+
+ mcpAsyncServer = McpServer.async(dubboMcpSseTransportProvider)
+ .capabilities(serverCapabilities)
+ .build();
+
+ FrameworkModel frameworkModel = applicationModel.getFrameworkModel();
+ DefaultOpenAPIService defaultOpenAPIService = new DefaultOpenAPIService(frameworkModel);
+
+ DubboOpenApiToolConverter toolConverter = new DubboOpenApiToolConverter(defaultOpenAPIService);
+
+ DubboMcpGenericCaller genericCaller = new DubboMcpGenericCaller(applicationModel);
+
+ toolRegistry = new DubboServiceToolRegistry(mcpAsyncServer, toolConverter, genericCaller, mcpServiceFilter);
+
+ applicationModel.getBeanFactory().registerBean(toolRegistry);
+
+ Collection<ProviderModel> providerModels =
+ applicationModel.getApplicationServiceRepository().allProviderModels();
+
+ int registeredCount = 0;
+ for (ProviderModel pm : providerModels) {
+ int serviceRegisteredCount = toolRegistry.registerService(pm);
+ registeredCount += serviceRegisteredCount;
+ }
+
+ exportMcpService(applicationModel);
+ logger.info(
+ "MCP server initialized successfully, {} existing tools registered, dynamic registration enabled",
+ registeredCount);
+ } catch (Exception e) {
+ logger.error(
+ LoggerCodeConstants.COMMON_UNEXPECTED_EXCEPTION,
+ "",
+ "",
+ "MCP service initialization failed: " + e.getMessage(),
+ e);
+ }
+ }
+
+ @Override
+ public void onStopping(ApplicationModel applicationModel) {
+ if (toolRegistry != null) {
+ logger.info("MCP server stopping, clearing tool registry");
+ toolRegistry.clearRegistry();
+ }
+ }
+
+ @Override
+ public void onStopped(ApplicationModel applicationModel) {
+ if (mcpEnable && mcpAsyncServer != null) {
+ mcpAsyncServer.close();
+ }
+ }
+
+ @Override
+ public void onFailure(ApplicationModel applicationModel, Throwable cause) {}
+
+ private void exportMcpService(ApplicationModel applicationModel) {
+ McpSseServiceImpl mcpSseServiceImpl =
+ applicationModel.getBeanFactory().getOrRegisterBean(McpSseServiceImpl.class);
+
+ ExecutorService internalServiceExecutor = applicationModel
+ .getFrameworkModel()
+ .getBeanFactory()
+ .getBean(FrameworkExecutorRepository.class)
+ .getInternalServiceExecutor();
+
+ this.serviceConfig = InternalServiceConfigBuilder.<McpSseService>newBuilder(applicationModel)
+ .interfaceClass(McpSseService.class)
+ .protocol(CommonConstants.TRIPLE, McpConstant.MCP_SERVICE_PROTOCOL)
+ .port(getRegisterPort(), String.valueOf(McpConstant.MCP_SERVICE_PORT))
+ .registryId("internal-mcp-registry")
+ .executor(internalServiceExecutor)
+ .ref(mcpSseServiceImpl)
+ .version(V1)
+ .build();
+ serviceConfig.export();
+ logger.info("MCP service exported on: {}", serviceConfig.getExportedUrls());
+ }
+
+ /**
+ * Get the Mcp service register port.
+ * First, try to get config from user configuration, if not found, get from protocol config.
+ * Second, try to get config from protocol config, if not found, get a random available port.
+ */
+ private int getRegisterPort() {
+ Configuration globalConf = ConfigurationUtils.getGlobalConfiguration(ApplicationModel.defaultModel());
+ int mcpPort = globalConf.getInt(McpConstant.SETTINGS_MCP_PORT, -1);
+ if (mcpPort != -1) {
+ return mcpPort;
+ }
Review Comment:
[nitpick] The method `getRegisterPort()` has multiple responsibilities (getting port from config, from protocol, or generating random port). Consider extracting this logic into separate methods for better readability and maintainability.
```suggestion
int port = getPortFromConfig();
if (port != -1) {
return port;
}
port = getPortFromProtocol();
if (port != -1) {
return port;
}
return getRandomAvailablePort();
}
private int getPortFromConfig() {
Configuration globalConf = ConfigurationUtils.getGlobalConfiguration(ApplicationModel.defaultModel());
return globalConf.getInt(McpConstant.SETTINGS_MCP_PORT, -1);
}
private int getPortFromProtocol() {
```
##########
dubbo-plugin/dubbo-mcp/src/main/java/org/apache/dubbo/mcp/transport/DubboMcpStreamableTransportProvider.java:
##########
@@ -0,0 +1,135 @@
+/*
+ * 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.stream.StreamObserver;
+import org.apache.dubbo.common.utils.CollectionUtils;
+import org.apache.dubbo.common.utils.JsonUtils;
+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.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.util.List;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.modelcontextprotocol.spec.McpError;
+import io.modelcontextprotocol.spec.McpStreamableServerSession;
+import io.modelcontextprotocol.spec.McpStreamableServerSession.Factory;
+import io.modelcontextprotocol.spec.McpStreamableServerTransportProvider;
+import reactor.core.publisher.Mono;
+
+/**
+ * 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 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);
+
+ public DubboMcpStreamableTransportProvider(ObjectMapper objectMapper) {
+ this.objectMapper = objectMapper;
+ }
+
+ @Override
+ public void setSessionFactory(Factory sessionFactory) {
+ this.sessionFactory = sessionFactory;
+ }
+
+ @Override
+ public Mono<Void> notifyClients(String method, Object params) {
+ return null;
+ }
+
+ @Override
+ public void close() {}
+
+ @Override
+ public Mono<Void> closeGracefully() {
+ return null;
+ }
+
+ public void handleRequest(StreamObserver<ServerSentEvent<String>> responseObserver) {
+ // Handle the request and return the response
+ HttpRequest request = RpcContext.getServiceContext().getRequest(HttpRequest.class);
+ if (HttpMethods.isGet(request.method())) {
+ handleGet(responseObserver);
+
+ } else if (HttpMethods.isPost(request.method())) {
+ handlePost(responseObserver);
+ }
+ return;
+ }
+
+ private void handleGet(StreamObserver<ServerSentEvent<String>> responseObserver) {
+ HttpRequest request = RpcContext.getServiceContext().getRequest(HttpRequest.class);
+ // check header
+ List<String> accepts = HttpUtils.parseAccept(request.accept());
+ if (CollectionUtils.isEmpty(accepts)
+ || !accepts.contains(MediaType.TEXT_EVENT_STREAM.getName())
+ || !accepts.contains(MediaType.APPLICATION_JSON.getName())) {
+ // 如果没有包含必须类型,则返回异常
+ responseObserver.onError(HttpResult.builder()
+ .header("Content-Type", MediaType.APPLICATION_JSON.getName())
+ .status(HttpStatus.NOT_ACCEPTABLE.getCode())
+ .body(JsonUtils.toJson(new McpError("Unsupported accept type").getJsonRpcError()))
+ .build()
+ .toPayload());
+ responseObserver.onCompleted();
+ return;
+ }
+
+ String sessionId =
+ RpcContext.getServiceContext().getRequest(HttpRequest.class).header(SESSION_ID_HEADER);
+ if (StringUtils.isBlank(sessionId)) {
+ // 如果没有sessionId,则返回异常
Review Comment:
[nitpick] Comments should be in English for consistency with the rest of the codebase. Consider translating Chinese comments to English.
```suggestion
// If there is no sessionId, return an error
```
##########
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 method `mcpEnabled()` is being called on `DubboService` annotation, but this method doesn't exist in the standard Dubbo annotation. This will cause a compilation error.
```suggestion
// The standard DubboService annotation does not have mcpEnabled(). Remove this check.
// If annotation-based configuration is needed, implement a custom annotation.
```
##########
dubbo-demo/dubbo-demo-mcp-server/src/main/java/org/apache/dubbo/mcp/server/demo/demo/HelloServiceImpl.java:
##########
@@ -0,0 +1,46 @@
+/*
+ * 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.server.demo.demo;
+
+import org.apache.dubbo.config.annotation.DubboService;
+
+@DubboService(mcpEnabled = true)
Review Comment:
The `mcpEnabled` attribute doesn't exist in the `@DubboService` annotation. This will cause a compilation error. Consider removing this attribute or implementing it as a custom extension.
```suggestion
@DubboService
```
##########
dubbo-plugin/dubbo-mcp/src/main/java/org/apache/dubbo/mcp/transport/DubboMcpStreamableTransportProvider.java:
##########
@@ -0,0 +1,135 @@
+/*
+ * 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.stream.StreamObserver;
+import org.apache.dubbo.common.utils.CollectionUtils;
+import org.apache.dubbo.common.utils.JsonUtils;
+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.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.util.List;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.modelcontextprotocol.spec.McpError;
+import io.modelcontextprotocol.spec.McpStreamableServerSession;
+import io.modelcontextprotocol.spec.McpStreamableServerSession.Factory;
+import io.modelcontextprotocol.spec.McpStreamableServerTransportProvider;
+import reactor.core.publisher.Mono;
+
+/**
+ * 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 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);
+
+ public DubboMcpStreamableTransportProvider(ObjectMapper objectMapper) {
+ this.objectMapper = objectMapper;
+ }
+
+ @Override
+ public void setSessionFactory(Factory sessionFactory) {
+ this.sessionFactory = sessionFactory;
+ }
+
+ @Override
+ public Mono<Void> notifyClients(String method, Object params) {
+ return null;
+ }
+
+ @Override
+ public void close() {}
+
+ @Override
+ public Mono<Void> closeGracefully() {
+ return null;
+ }
+
+ public void handleRequest(StreamObserver<ServerSentEvent<String>> responseObserver) {
+ // Handle the request and return the response
+ HttpRequest request = RpcContext.getServiceContext().getRequest(HttpRequest.class);
+ if (HttpMethods.isGet(request.method())) {
+ handleGet(responseObserver);
+
+ } else if (HttpMethods.isPost(request.method())) {
+ handlePost(responseObserver);
+ }
+ return;
+ }
+
+ private void handleGet(StreamObserver<ServerSentEvent<String>> responseObserver) {
+ HttpRequest request = RpcContext.getServiceContext().getRequest(HttpRequest.class);
+ // check header
+ List<String> accepts = HttpUtils.parseAccept(request.accept());
+ if (CollectionUtils.isEmpty(accepts)
+ || !accepts.contains(MediaType.TEXT_EVENT_STREAM.getName())
+ || !accepts.contains(MediaType.APPLICATION_JSON.getName())) {
Review Comment:
The condition checks if accepts contains both TEXT_EVENT_STREAM AND APPLICATION_JSON, but uses OR logic. This will reject requests that have one but not the other. Consider using AND logic or separate the checks based on the actual requirements.
```suggestion
|| (!accepts.contains(MediaType.TEXT_EVENT_STREAM.getName())
&& !accepts.contains(MediaType.APPLICATION_JSON.getName()))) {
```
--
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: notifications-unsubscribe@dubbo.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@dubbo.apache.org
For additional commands, e-mail: notifications-help@dubbo.apache.org
Re: [PR] Dubbo MCP Integration [dubbo]
Posted by "zrlw (via GitHub)" <gi...@apache.org>.
zrlw commented on PR #15406:
URL: https://github.com/apache/dubbo/pull/15406#issuecomment-2910881928
> > There are many Chinese comments in the change commit.
>
> Done.Translation completed.
no, there are still chinese comments. i.e. McpConstant.java
--
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: notifications-unsubscribe@dubbo.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@dubbo.apache.org
For additional commands, e-mail: notifications-help@dubbo.apache.org
Re: [PR] Dubbo MCP Integration [dubbo]
Posted by "Stellar1999 (via GitHub)" <gi...@apache.org>.
Stellar1999 commented on code in PR #15406:
URL: https://github.com/apache/dubbo/pull/15406#discussion_r2108445945
##########
dubbo-plugin/dubbo-mcp/src/main/java/org/apache/dubbo/mcp/core/McpApplicationDeployListener.java:
##########
@@ -0,0 +1,197 @@
+/*
+ * 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.config.Configuration;
+import org.apache.dubbo.common.config.ConfigurationUtils;
+import org.apache.dubbo.common.constants.CommonConstants;
+import org.apache.dubbo.common.constants.LoggerCodeConstants;
+import org.apache.dubbo.common.deploy.ApplicationDeployListener;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.threadpool.manager.FrameworkExecutorRepository;
+import org.apache.dubbo.common.utils.CollectionUtils;
+import org.apache.dubbo.common.utils.NetUtils;
+import org.apache.dubbo.config.ProtocolConfig;
+import org.apache.dubbo.config.ServiceConfig;
+import org.apache.dubbo.config.bootstrap.builders.InternalServiceConfigBuilder;
+import org.apache.dubbo.mcp.McpConstant;
+import org.apache.dubbo.mcp.tool.DubboMcpGenericCaller;
+import org.apache.dubbo.mcp.tool.DubboOpenApiToolConverter;
+import org.apache.dubbo.mcp.tool.DubboServiceToolRegistry;
+import org.apache.dubbo.mcp.transport.DubboMcpSseTransportProvider;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+import org.apache.dubbo.rpc.model.FrameworkModel;
+import org.apache.dubbo.rpc.model.ProviderModel;
+import org.apache.dubbo.rpc.protocol.tri.rest.openapi.DefaultOpenAPIService;
+
+import java.util.Collection;
+import java.util.concurrent.ExecutorService;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.modelcontextprotocol.server.McpAsyncServer;
+import io.modelcontextprotocol.server.McpServer;
+import io.modelcontextprotocol.spec.McpSchema;
+
+import static org.apache.dubbo.metadata.util.MetadataServiceVersionUtils.V1;
+
+public class McpApplicationDeployListener implements ApplicationDeployListener {
+
+ private static final ErrorTypeAwareLogger logger =
+ LoggerFactory.getErrorTypeAwareLogger(McpApplicationDeployListener.class);
+ private DubboServiceToolRegistry toolRegistry;
+ private McpServiceFilter mcpServiceFilter;
+ private boolean mcpEnable = true;
+
+ private volatile ServiceConfig<McpSseService> serviceConfig;
+
+ private static DubboMcpSseTransportProvider dubboMcpSseTransportProvider;
+
+ @Override
+ public void onInitialize(ApplicationModel scopeModel) {}
+
+ @Override
+ public void onStarting(ApplicationModel applicationModel) {}
+
+ public static DubboMcpSseTransportProvider getDubboMcpSseTransportProvider() {
+ return dubboMcpSseTransportProvider;
+ }
+
+ @Override
+ public void onStarted(ApplicationModel applicationModel) {
+ Configuration globalConf = ConfigurationUtils.getGlobalConfiguration(ApplicationModel.defaultModel());
+ mcpEnable = globalConf.getBoolean(McpConstant.SETTINGS_MCP_ENABLE, true);
+ if (!mcpEnable) {
+ logger.info("MCP service is disabled, skipping initialization");
+ return;
+ }
+ try {
+ logger.info("Initializing MCP server and tools");
+
+ // Initialize service filter
+ mcpServiceFilter = new McpServiceFilter(applicationModel);
+
+ dubboMcpSseTransportProvider = new DubboMcpSseTransportProvider(new ObjectMapper());
+ McpSchema.ServerCapabilities.ToolCapabilities toolCapabilities =
+ new McpSchema.ServerCapabilities.ToolCapabilities(true);
+ McpSchema.ServerCapabilities serverCapabilities =
+ new McpSchema.ServerCapabilities(null, null, null, null, toolCapabilities);
+
+ McpAsyncServer mcpAsyncServer = McpServer.async(dubboMcpSseTransportProvider)
+ .capabilities(serverCapabilities)
+ .build();
+
+ FrameworkModel frameworkModel = applicationModel.getFrameworkModel();
+ DefaultOpenAPIService defaultOpenAPIService = new DefaultOpenAPIService(frameworkModel);
+
+ DubboOpenApiToolConverter toolConverter = new DubboOpenApiToolConverter(defaultOpenAPIService);
+
+ DubboMcpGenericCaller genericCaller = new DubboMcpGenericCaller(applicationModel);
+
+ toolRegistry = new DubboServiceToolRegistry(mcpAsyncServer, toolConverter, genericCaller);
+
+ Collection<ProviderModel> providerModels =
+ applicationModel.getApplicationServiceRepository().allProviderModels();
+
+ int registeredCount = 0;
+ for (ProviderModel pm : providerModels) {
+ // Check if service should be exposed as MCP tool
+ if (mcpServiceFilter.shouldExposeAsMcpTool(pm)) {
+ // Get MCP tool configuration
+ McpServiceFilter.McpToolConfig toolConfig = mcpServiceFilter.getMcpToolConfig(pm);
+ toolRegistry.registerService(pm, toolConfig);
+ registeredCount++;
+ }
+ }
Review Comment:
@heliang666s PTAL
--
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: notifications-unsubscribe@dubbo.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@dubbo.apache.org
For additional commands, e-mail: notifications-help@dubbo.apache.org
Re: [PR] Dubbo MCP Integration [dubbo]
Posted by "RainYuY (via GitHub)" <gi...@apache.org>.
RainYuY commented on code in PR #15406:
URL: https://github.com/apache/dubbo/pull/15406#discussion_r2268605976
##########
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;
+ }
+ }
+
+ String serviceSpecificKey = McpConstant.SETTINGS_MCP_SERVICE_PREFIX + "." + interfaceName + ".enabled";
+ if (configuration.containsKey(serviceSpecificKey)) {
+ return configuration.getBoolean(serviceSpecificKey, false);
+ }
+
+ if (configuration.containsKey(McpConstant.PARAM_MCP_ENABLED)) {
+ return configuration.getBoolean(McpConstant.PARAM_MCP_ENABLED, false);
+ }
+
+ if (includePatterns.length > 0) {
+ return isMatchedByPatterns(interfaceName, includePatterns);
+ }
+
+ return defaultEnabled;
+ }
+
+ /**
+ * Check if specific method should be exposed as MCP tool.
+ * Priority: @McpTool(enabled=false) > URL Parameters > @McpTool(enabled=true) > Configuration > Service-level
+ */
+ public boolean shouldExposeMethodAsMcpTool(ProviderModel providerModel, Method method) {
+ String interfaceName = providerModel.getServiceModel().getInterfaceName();
+ String methodName = method.getName();
+
+ if (isMatchedByPatterns(interfaceName, excludePatterns)) {
+ return false;
+ }
+
+ McpTool methodMcpTool = getMethodMcpTool(providerModel, method);
+
+ if (methodMcpTool != null && !methodMcpTool.enabled()) {
+ return false;
+ }
+
+ URL serviceUrl = getServiceUrl(providerModel);
+ if (serviceUrl != null) {
+ String methodUrlValue = serviceUrl.getMethodParameter(methodName, McpConstant.PARAM_MCP_ENABLED);
+
+ if (methodUrlValue != null && StringUtils.isNotEmpty(methodUrlValue)) {
+ return Boolean.parseBoolean(methodUrlValue);
+ }
+
+ String serviceLevelValue = serviceUrl.getParameter(McpConstant.PARAM_MCP_ENABLED);
+
+ if (serviceLevelValue != null && StringUtils.isNotEmpty(serviceLevelValue)) {
+ return Boolean.parseBoolean(serviceLevelValue);
+ }
+ }
+
+ if (methodMcpTool != null && methodMcpTool.enabled()) {
+ return true;
+ }
+
+ String methodConfigKey =
+ McpConstant.SETTINGS_MCP_SERVICE_PREFIX + "." + interfaceName + ".methods." + methodName + ".enabled";
+ if (configuration.containsKey(methodConfigKey)) {
+ return configuration.getBoolean(methodConfigKey, false);
+ }
+
+ if (shouldExposeAsMcpTool(providerModel)) {
+ return Modifier.isPublic(method.getModifiers());
+ }
+
+ return false;
+ }
+
+ /**
+ * Get @McpTool annotation from method, checking both interface and implementation class.
+ */
+ private McpTool getMethodMcpTool(ProviderModel providerModel, Method method) {
+ String methodName = method.getName();
+ Class<?>[] paramTypes = method.getParameterTypes();
+
+ Object serviceBean = providerModel.getServiceInstance();
+ if (serviceBean != null) {
+ try {
+ Method implMethod = serviceBean.getClass().getMethod(methodName, paramTypes);
+ McpTool implMcpTool = implMethod.getAnnotation(McpTool.class);
+ if (implMcpTool != null) {
+ return implMcpTool;
+ }
+ } catch (NoSuchMethodException e) {
+ // Method not found in implementation class
Review Comment:
Done
--
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: notifications-unsubscribe@dubbo.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@dubbo.apache.org
For additional commands, e-mail: notifications-help@dubbo.apache.org
Re: [PR] Dubbo MCP Integration [dubbo]
Posted by "codecov-commenter (via GitHub)" <gi...@apache.org>.
codecov-commenter commented on PR #15406:
URL: https://github.com/apache/dubbo/pull/15406#issuecomment-2894561316
## [Codecov](https://app.codecov.io/gh/apache/dubbo/pull/15406?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) Report
Attention: Patch coverage is `0%` with `458 lines` in your changes missing coverage. Please review.
> Project coverage is 29.06%. Comparing base [(`379fa90`)](https://app.codecov.io/gh/apache/dubbo/commit/379fa907b787987586e19d18db8dc1ee9b75c48e?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) to head [(`4739e28`)](https://app.codecov.io/gh/apache/dubbo/commit/4739e288148dafd355c33bd79eaffebe33765ff0?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache).
| [Files with missing lines](https://app.codecov.io/gh/apache/dubbo/pull/15406?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) | Patch % | Lines |
|---|---|---|
| [...server/sdk/registry/DubboOpenApiToolConverter.java](https://app.codecov.io/gh/apache/dubbo/pull/15406?src=pr&el=tree&filepath=dubbo-plugin%2Fdubbo-mcp%2Fsrc%2Fmain%2Fjava%2Forg%2Fapache%2Fdubbo%2Fmcp%2Fserver%2Fsdk%2Fregistry%2FDubboOpenApiToolConverter.java&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache#diff-ZHViYm8tcGx1Z2luL2R1YmJvLW1jcC9zcmMvbWFpbi9qYXZhL29yZy9hcGFjaGUvZHViYm8vbWNwL3NlcnZlci9zZGsvcmVnaXN0cnkvRHViYm9PcGVuQXBpVG9vbENvbnZlcnRlci5qYXZh) | 0.00% | [170 Missing :warning: ](https://app.codecov.io/gh/apache/dubbo/pull/15406?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) |
| [.../server/sdk/registry/DubboServiceToolRegistry.java](https://app.codecov.io/gh/apache/dubbo/pull/15406?src=pr&el=tree&filepath=dubbo-plugin%2Fdubbo-mcp%2Fsrc%2Fmain%2Fjava%2Forg%2Fapache%2Fdubbo%2Fmcp%2Fserver%2Fsdk%2Fregistry%2FDubboServiceToolRegistry.java&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache#diff-ZHViYm8tcGx1Z2luL2R1YmJvLW1jcC9zcmMvbWFpbi9qYXZhL29yZy9hcGFjaGUvZHViYm8vbWNwL3NlcnZlci9zZGsvcmVnaXN0cnkvRHViYm9TZXJ2aWNlVG9vbFJlZ2lzdHJ5LmphdmE=) | 0.00% | [77 Missing :warning: ](https://app.codecov.io/gh/apache/dubbo/pull/15406?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) |
| [...er/sdk/transport/DubboMcpSseTransportProvider.java](https://app.codecov.io/gh/apache/dubbo/pull/15406?src=pr&el=tree&filepath=dubbo-plugin%2Fdubbo-mcp%2Fsrc%2Fmain%2Fjava%2Forg%2Fapache%2Fdubbo%2Fmcp%2Fserver%2Fsdk%2Ftransport%2FDubboMcpSseTransportProvider.java&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache#diff-ZHViYm8tcGx1Z2luL2R1YmJvLW1jcC9zcmMvbWFpbi9qYXZhL29yZy9hcGFjaGUvZHViYm8vbWNwL3NlcnZlci9zZGsvdHJhbnNwb3J0L0R1YmJvTWNwU3NlVHJhbnNwb3J0UHJvdmlkZXIuamF2YQ==) | 0.00% | [73 Missing :warning: ](https://app.codecov.io/gh/apache/dubbo/pull/15406?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) |
| [...ver/sdk/registry/McpApplicationDeployListener.java](https://app.codecov.io/gh/apache/dubbo/pull/15406?src=pr&el=tree&filepath=dubbo-plugin%2Fdubbo-mcp%2Fsrc%2Fmain%2Fjava%2Forg%2Fapache%2Fdubbo%2Fmcp%2Fserver%2Fsdk%2Fregistry%2FMcpApplicationDeployListener.java&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache#diff-ZHViYm8tcGx1Z2luL2R1YmJvLW1jcC9zcmMvbWFpbi9qYXZhL29yZy9hcGFjaGUvZHViYm8vbWNwL3NlcnZlci9zZGsvcmVnaXN0cnkvTWNwQXBwbGljYXRpb25EZXBsb3lMaXN0ZW5lci5qYXZh) | 0.00% | [62 Missing :warning: ](https://app.codecov.io/gh/apache/dubbo/pull/15406?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) |
| [.../mcp/server/sdk/generic/DubboMcpGenericCaller.java](https://app.codecov.io/gh/apache/dubbo/pull/15406?src=pr&el=tree&filepath=dubbo-plugin%2Fdubbo-mcp%2Fsrc%2Fmain%2Fjava%2Forg%2Fapache%2Fdubbo%2Fmcp%2Fserver%2Fsdk%2Fgeneric%2FDubboMcpGenericCaller.java&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache#diff-ZHViYm8tcGx1Z2luL2R1YmJvLW1jcC9zcmMvbWFpbi9qYXZhL29yZy9hcGFjaGUvZHViYm8vbWNwL3NlcnZlci9zZGsvZ2VuZXJpYy9EdWJib01jcEdlbmVyaWNDYWxsZXIuamF2YQ==) | 0.00% | [60 Missing :warning: ](https://app.codecov.io/gh/apache/dubbo/pull/15406?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) |
| [...org/apache/dubbo/mcp/server/sdk/MCPSseService.java](https://app.codecov.io/gh/apache/dubbo/pull/15406?src=pr&el=tree&filepath=dubbo-plugin%2Fdubbo-mcp%2Fsrc%2Fmain%2Fjava%2Forg%2Fapache%2Fdubbo%2Fmcp%2Fserver%2Fsdk%2FMCPSseService.java&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache#diff-ZHViYm8tcGx1Z2luL2R1YmJvLW1jcC9zcmMvbWFpbi9qYXZhL29yZy9hcGFjaGUvZHViYm8vbWNwL3NlcnZlci9zZGsvTUNQU3NlU2VydmljZS5qYXZh) | 0.00% | [12 Missing :warning: ](https://app.codecov.io/gh/apache/dubbo/pull/15406?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) |
| [...ava/org/apache/dubbo/mcp/server/sdk/McpConfig.java](https://app.codecov.io/gh/apache/dubbo/pull/15406?src=pr&el=tree&filepath=dubbo-plugin%2Fdubbo-mcp%2Fsrc%2Fmain%2Fjava%2Forg%2Fapache%2Fdubbo%2Fmcp%2Fserver%2Fsdk%2FMcpConfig.java&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache#diff-ZHViYm8tcGx1Z2luL2R1YmJvLW1jcC9zcmMvbWFpbi9qYXZhL29yZy9hcGFjaGUvZHViYm8vbWNwL3NlcnZlci9zZGsvTWNwQ29uZmlnLmphdmE=) | 0.00% | [4 Missing :warning: ](https://app.codecov.io/gh/apache/dubbo/pull/15406?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) |
> :exclamation: There is a different number of reports uploaded between BASE (379fa90) and HEAD (4739e28). Click for more details.
>
> <details><summary>HEAD has 1 upload less than BASE</summary>
>
>| Flag | BASE (379fa90) | HEAD (4739e28) |
>|------|------|------|
>|unit-tests|1|0|
></details>
<details><summary>Additional details and impacted files</summary>
```diff
@@ Coverage Diff @@
## 3.3 #15406 +/- ##
=============================================
- Coverage 58.86% 29.06% -29.81%
- Complexity 12 8907 +8895
=============================================
Files 1887 1894 +7
Lines 86219 86677 +458
Branches 12923 13002 +79
=============================================
- Hits 50753 25189 -25564
- Misses 29923 57499 +27576
+ Partials 5543 3989 -1554
```
| [Flag](https://app.codecov.io/gh/apache/dubbo/pull/15406/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) | Coverage Δ | |
|---|---|---|
| [samples-tests](https://app.codecov.io/gh/apache/dubbo/pull/15406/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) | `29.06% <0.00%> (?)` | |
| [unit-tests](https://app.codecov.io/gh/apache/dubbo/pull/15406/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) | `?` | |
Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache#carryforward-flags-in-the-pull-request-comment) to find out more.
</details>
[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/apache/dubbo/pull/15406?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache).
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache).
<details><summary> :rocket: New features to boost your workflow: </summary>
- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
--
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: notifications-unsubscribe@dubbo.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@dubbo.apache.org
For additional commands, e-mail: notifications-help@dubbo.apache.org
Re: [PR] Dubbo MCP Integration [dubbo]
Posted by "Stellar1999 (via GitHub)" <gi...@apache.org>.
Stellar1999 commented on code in PR #15406:
URL: https://github.com/apache/dubbo/pull/15406#discussion_r2108105610
##########
dubbo-plugin/dubbo-mcp/pom.xml:
##########
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.dubbo</groupId>
+ <artifactId>dubbo-parent</artifactId>
+ <version>${revision}</version>
+ <relativePath>../../pom.xml</relativePath>
+ </parent>
+
+ <artifactId>dubbo-mcp</artifactId>
+ <properties>
+ <mcp.version>0.9.0</mcp.version>
+ <skip_maven_deploy>false</skip_maven_deploy>
+ <guava.version>33.2.1-jre</guava.version>
Review Comment:
This was previously used for local caching. Since it is no longer needed, it has been removed.
--
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: notifications-unsubscribe@dubbo.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@dubbo.apache.org
For additional commands, e-mail: notifications-help@dubbo.apache.org
Re: [PR] Dubbo MCP Integration [dubbo]
Posted by "Stellar1999 (via GitHub)" <gi...@apache.org>.
Stellar1999 commented on PR #15406:
URL: https://github.com/apache/dubbo/pull/15406#issuecomment-2909554740
> There are many Chinese comments in the change commit.
Done.Translation completed.
--
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: notifications-unsubscribe@dubbo.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@dubbo.apache.org
For additional commands, e-mail: notifications-help@dubbo.apache.org
Re: [PR] Dubbo MCP Integration [dubbo]
Posted by "Stellar1999 (via GitHub)" <gi...@apache.org>.
Stellar1999 commented on code in PR #15406:
URL: https://github.com/apache/dubbo/pull/15406#discussion_r2108436239
##########
dubbo-plugin/dubbo-mcp/src/main/java/org/apache/dubbo/mcp/core/McpApplicationDeployListener.java:
##########
@@ -0,0 +1,197 @@
+/*
+ * 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.config.Configuration;
+import org.apache.dubbo.common.config.ConfigurationUtils;
+import org.apache.dubbo.common.constants.CommonConstants;
+import org.apache.dubbo.common.constants.LoggerCodeConstants;
+import org.apache.dubbo.common.deploy.ApplicationDeployListener;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.threadpool.manager.FrameworkExecutorRepository;
+import org.apache.dubbo.common.utils.CollectionUtils;
+import org.apache.dubbo.common.utils.NetUtils;
+import org.apache.dubbo.config.ProtocolConfig;
+import org.apache.dubbo.config.ServiceConfig;
+import org.apache.dubbo.config.bootstrap.builders.InternalServiceConfigBuilder;
+import org.apache.dubbo.mcp.McpConstant;
+import org.apache.dubbo.mcp.tool.DubboMcpGenericCaller;
+import org.apache.dubbo.mcp.tool.DubboOpenApiToolConverter;
+import org.apache.dubbo.mcp.tool.DubboServiceToolRegistry;
+import org.apache.dubbo.mcp.transport.DubboMcpSseTransportProvider;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+import org.apache.dubbo.rpc.model.FrameworkModel;
+import org.apache.dubbo.rpc.model.ProviderModel;
+import org.apache.dubbo.rpc.protocol.tri.rest.openapi.DefaultOpenAPIService;
+
+import java.util.Collection;
+import java.util.concurrent.ExecutorService;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.modelcontextprotocol.server.McpAsyncServer;
+import io.modelcontextprotocol.server.McpServer;
+import io.modelcontextprotocol.spec.McpSchema;
+
+import static org.apache.dubbo.metadata.util.MetadataServiceVersionUtils.V1;
+
+public class McpApplicationDeployListener implements ApplicationDeployListener {
+
+ private static final ErrorTypeAwareLogger logger =
+ LoggerFactory.getErrorTypeAwareLogger(McpApplicationDeployListener.class);
+ private DubboServiceToolRegistry toolRegistry;
+ private McpServiceFilter mcpServiceFilter;
+ private boolean mcpEnable = true;
+
+ private volatile ServiceConfig<McpSseService> serviceConfig;
+
+ private static DubboMcpSseTransportProvider dubboMcpSseTransportProvider;
+
+ @Override
+ public void onInitialize(ApplicationModel scopeModel) {}
+
+ @Override
+ public void onStarting(ApplicationModel applicationModel) {}
+
+ public static DubboMcpSseTransportProvider getDubboMcpSseTransportProvider() {
+ return dubboMcpSseTransportProvider;
+ }
+
+ @Override
+ public void onStarted(ApplicationModel applicationModel) {
+ Configuration globalConf = ConfigurationUtils.getGlobalConfiguration(ApplicationModel.defaultModel());
+ mcpEnable = globalConf.getBoolean(McpConstant.SETTINGS_MCP_ENABLE, true);
+ if (!mcpEnable) {
+ logger.info("MCP service is disabled, skipping initialization");
+ return;
+ }
+ try {
+ logger.info("Initializing MCP server and tools");
+
+ // Initialize service filter
+ mcpServiceFilter = new McpServiceFilter(applicationModel);
+
+ dubboMcpSseTransportProvider = new DubboMcpSseTransportProvider(new ObjectMapper());
+ McpSchema.ServerCapabilities.ToolCapabilities toolCapabilities =
+ new McpSchema.ServerCapabilities.ToolCapabilities(true);
+ McpSchema.ServerCapabilities serverCapabilities =
+ new McpSchema.ServerCapabilities(null, null, null, null, toolCapabilities);
+
+ McpAsyncServer mcpAsyncServer = McpServer.async(dubboMcpSseTransportProvider)
+ .capabilities(serverCapabilities)
+ .build();
+
+ FrameworkModel frameworkModel = applicationModel.getFrameworkModel();
+ DefaultOpenAPIService defaultOpenAPIService = new DefaultOpenAPIService(frameworkModel);
+
+ DubboOpenApiToolConverter toolConverter = new DubboOpenApiToolConverter(defaultOpenAPIService);
+
+ DubboMcpGenericCaller genericCaller = new DubboMcpGenericCaller(applicationModel);
+
+ toolRegistry = new DubboServiceToolRegistry(mcpAsyncServer, toolConverter, genericCaller);
+
+ Collection<ProviderModel> providerModels =
+ applicationModel.getApplicationServiceRepository().allProviderModels();
+
+ int registeredCount = 0;
+ for (ProviderModel pm : providerModels) {
+ // Check if service should be exposed as MCP tool
+ if (mcpServiceFilter.shouldExposeAsMcpTool(pm)) {
+ // Get MCP tool configuration
+ McpServiceFilter.McpToolConfig toolConfig = mcpServiceFilter.getMcpToolConfig(pm);
+ toolRegistry.registerService(pm, toolConfig);
+ registeredCount++;
+ }
+ }
Review Comment:
Do you mean that if the service goes offline, the tool also needs to be taken down?
Or rather, we need to make sure the service and the tool stay consistent with each other?
--
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: notifications-unsubscribe@dubbo.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@dubbo.apache.org
For additional commands, e-mail: notifications-help@dubbo.apache.org
Re: [PR] Dubbo MCP SSE Integration [dubbo]
Posted by "conghuhu (via GitHub)" <gi...@apache.org>.
conghuhu commented on code in PR #15406:
URL: https://github.com/apache/dubbo/pull/15406#discussion_r2217532808
##########
dubbo-demo/dubbo-demo-spring-boot/pom.xml:
##########
@@ -31,6 +31,7 @@
<module>dubbo-demo-spring-boot-provider</module>
<module>dubbo-demo-spring-boot-interface</module>
<module>dubbo-demo-spring-boot-servlet</module>
+ <module>dubbo-demo-mcp-server</module>
Review Comment:
And, plz to add readme
--
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: notifications-unsubscribe@dubbo.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@dubbo.apache.org
For additional commands, e-mail: notifications-help@dubbo.apache.org
Re: [PR] Dubbo MCP Integration [dubbo]
Posted by "RainYuY (via GitHub)" <gi...@apache.org>.
RainYuY commented on code in PR #15406:
URL: https://github.com/apache/dubbo/pull/15406#discussion_r2268637234
##########
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:
Emm... just local is fine, I think, because in Dubbo it has a special meaning.
--
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: notifications-unsubscribe@dubbo.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@dubbo.apache.org
For additional commands, e-mail: notifications-help@dubbo.apache.org
Re: [PR] Dubbo MCP Integration [dubbo]
Posted by "RainYuY (via GitHub)" <gi...@apache.org>.
RainYuY commented on PR #15406:
URL: https://github.com/apache/dubbo/pull/15406#issuecomment-3417533387
> A small suggestion: Disable MCP load by default
>
> Others LGTM
Done
--
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: notifications-unsubscribe@dubbo.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@dubbo.apache.org
For additional commands, e-mail: notifications-help@dubbo.apache.org
Re: [PR] Dubbo MCP Integration [dubbo]
Posted by "RainYuY (via GitHub)" <gi...@apache.org>.
RainYuY commented on PR #15406:
URL: https://github.com/apache/dubbo/pull/15406#issuecomment-3395925191
@zrlw @AlbumenJ @CrazyHZM @songxiaosheng @heliang666s @EarthChen PTAL
--
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: notifications-unsubscribe@dubbo.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@dubbo.apache.org
For additional commands, e-mail: notifications-help@dubbo.apache.org
Re: [PR] Dubbo MCP Integration [dubbo]
Posted by "Stellar1999 (via GitHub)" <gi...@apache.org>.
Stellar1999 commented on PR #15406:
URL: https://github.com/apache/dubbo/pull/15406#issuecomment-2906423852
PTAL @AlbumenJ @CrazyHZM
--
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: notifications-unsubscribe@dubbo.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@dubbo.apache.org
For additional commands, e-mail: notifications-help@dubbo.apache.org
Re: [PR] Dubbo MCP Integration [dubbo]
Posted by "SpursJiang (via GitHub)" <gi...@apache.org>.
SpursJiang commented on PR #15406:
URL: https://github.com/apache/dubbo/pull/15406#issuecomment-2911646059
> > Dear Dubbo team: Once a Dubbo service interface is registered as an MCP (Mesh Control Plane) tool, is its metadata stored exclusively in local memory? Furthermore, are there any plans to register MCP tools with the MCP register in Nacos version 3? Is the Streamable HTTP protocol supported?
>
> 1. Is its metadata stored exclusively in local memory? Yes.
> 2. Furthermore, are there any plans to register MCP tools with the MCP registry in Nacos version 3? Under consideration.
> 3. Is the Streamable HTTP protocol supported? No, but I believe we will support it in the future.Now just sse.
OK👍
--
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: notifications-unsubscribe@dubbo.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@dubbo.apache.org
For additional commands, e-mail: notifications-help@dubbo.apache.org
Re: [PR] Dubbo MCP SSE Integration [dubbo]
Posted by "conghuhu (via GitHub)" <gi...@apache.org>.
conghuhu commented on PR #15406:
URL: https://github.com/apache/dubbo/pull/15406#issuecomment-3095040476
Currently, this PR lacks a solution for multiple instances of MCP Server, which involves session sharing issues in cluster mode. However, the multi-instance solution is an enterprise-level solution, which can be implemented after this PR is merged
--
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: notifications-unsubscribe@dubbo.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@dubbo.apache.org
For additional commands, e-mail: notifications-help@dubbo.apache.org
Re: [PR] Dubbo MCP SSE Integration [dubbo]
Posted by "zrlw (via GitHub)" <gi...@apache.org>.
zrlw commented on PR #15406:
URL: https://github.com/apache/dubbo/pull/15406#issuecomment-3111743530
check the reviews. @RainYuY
--
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: notifications-unsubscribe@dubbo.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@dubbo.apache.org
For additional commands, e-mail: notifications-help@dubbo.apache.org
Re: [PR] Dubbo MCP SSE Integration [dubbo]
Posted by "songxiaosheng (via GitHub)" <gi...@apache.org>.
songxiaosheng commented on code in PR #15406:
URL: https://github.com/apache/dubbo/pull/15406#discussion_r2239414572
##########
dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ServiceConfig.java:
##########
@@ -196,6 +201,15 @@ public boolean isUnexported() {
return unexported;
}
+ @Parameter(attribute = false, key = "mcp.enabled")
+ public boolean getMcpEnabled() {
Review Comment:
i think this method can use isMcpEnabled replace
--
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: notifications-unsubscribe@dubbo.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@dubbo.apache.org
For additional commands, e-mail: notifications-help@dubbo.apache.org
Re: [PR] Dubbo MCP Integration [dubbo]
Posted by "RainYuY (via GitHub)" <gi...@apache.org>.
RainYuY commented on code in PR #15406:
URL: https://github.com/apache/dubbo/pull/15406#discussion_r2264632621
##########
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:
I don't know what this comment means… It exists.
--
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: notifications-unsubscribe@dubbo.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@dubbo.apache.org
For additional commands, e-mail: notifications-help@dubbo.apache.org
Re: [PR] Dubbo MCP Integration [dubbo]
Posted by "songxiaosheng (via GitHub)" <gi...@apache.org>.
songxiaosheng commented on code in PR #15406:
URL: https://github.com/apache/dubbo/pull/15406#discussion_r2266551875
##########
dubbo-plugin/dubbo-mcp/src/main/java/org/apache/dubbo/mcp/core/McpServiceExportListener.java:
##########
@@ -0,0 +1,139 @@
+/*
+ * 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.constants.LoggerCodeConstants;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.config.ServiceConfig;
+import org.apache.dubbo.config.ServiceListener;
+import org.apache.dubbo.mcp.tool.DubboServiceToolRegistry;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+import org.apache.dubbo.rpc.model.ProviderModel;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class McpServiceExportListener implements ServiceListener {
+
+ private static final ErrorTypeAwareLogger logger =
+ LoggerFactory.getErrorTypeAwareLogger(McpServiceExportListener.class);
+
+ private final Map<String, RegisteredServiceInfo> registeredServiceTools = new ConcurrentHashMap<>();
+
+ private static class RegisteredServiceInfo {
+ final int toolCount;
+ final String interfaceName;
+ final ProviderModel providerModel;
+
+ RegisteredServiceInfo(int toolCount, String interfaceName, ProviderModel providerModel) {
+ this.toolCount = toolCount;
+ this.interfaceName = interfaceName;
+ this.providerModel = providerModel;
+ }
+ }
+
+ @Override
+ public void exported(ServiceConfig sc) {
+ try {
+ if (sc.getRef() == null) {
+ return;
+ }
+
+ String serviceKey = sc.getUniqueServiceName();
+ ProviderModel providerModel =
+ sc.getScopeModel().getServiceRepository().lookupExportedService(serviceKey);
+
+ if (providerModel == null) {
+ logger.warn(
+ LoggerCodeConstants.COMMON_UNEXPECTED_EXCEPTION,
+ "",
+ "",
+ "ProviderModel not found for service: " + sc.getInterface() + " with key: " + serviceKey);
+ return;
+ }
+
+ DubboServiceToolRegistry toolRegistry = getToolRegistry(sc);
+ if (toolRegistry == null) {
+ return;
+ }
+
+ int registeredCount = toolRegistry.registerService(providerModel);
+
+ if (registeredCount > 0) {
+ registeredServiceTools.put(
+ serviceKey,
+ new RegisteredServiceInfo(
+ registeredCount, providerModel.getServiceModel().getInterfaceName(), providerModel));
+ logger.info(
+ "Dynamically registered {} MCP tools for exported service: {}",
+ registeredCount,
+ providerModel.getServiceModel().getInterfaceName());
+ }
+ } catch (Exception e) {
+ logger.error(
+ LoggerCodeConstants.COMMON_UNEXPECTED_EXCEPTION,
+ "",
+ "",
+ "Failed to register MCP tools for exported service: " + sc.getInterface(),
+ e);
+ }
+ }
+
+ @Override
+ public void unexported(ServiceConfig sc) {
+ try {
+ if (sc.getRef() == null) {
+ return;
+ }
+
+ String serviceKey = sc.getUniqueServiceName();
+ RegisteredServiceInfo serviceInfo = registeredServiceTools.remove(serviceKey);
+
+ if (serviceInfo != null && serviceInfo.toolCount > 0) {
+ DubboServiceToolRegistry toolRegistry = getToolRegistry(sc);
+ if (toolRegistry == null) {
+ return;
+ }
+
+ toolRegistry.unregisterService(serviceInfo.providerModel);
+ logger.info(
+ "Dynamically unregistered {} MCP tools for unexported service: {}",
+ serviceInfo.toolCount,
+ serviceInfo.interfaceName);
+ }
+
+ } catch (Exception e) {
+ logger.error(
+ LoggerCodeConstants.COMMON_UNEXPECTED_EXCEPTION,
+ "",
+ "",
+ "Failed to unregister MCP tools for unexported service: " + sc.getInterface(),
+ e);
+ }
+ }
+
+ private DubboServiceToolRegistry getToolRegistry(ServiceConfig sc) {
+ try {
+ ApplicationModel applicationModel = sc.getScopeModel().getApplicationModel();
+ return applicationModel.getBeanFactory().getBean(DubboServiceToolRegistry.class);
+ } catch (Exception e) {
+ logger.debug("Failed to get DubboServiceToolRegistry from application context: {}", e.getMessage());
Review Comment:
You can use a warn log here to remind users to pay attention to the exception, as debug logs are often ignored.
##########
dubbo-common/src/main/java/org/apache/dubbo/config/nested/McpConfig.java:
##########
@@ -0,0 +1,114 @@
+/*
+ * 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.config.nested;
+
+import org.apache.dubbo.config.support.Nested;
+
+import java.io.Serializable;
+
+public class McpConfig implements Serializable {
+
+ private static final long serialVersionUID = 6943417856234001947L;
+
+ /**
+ * Whether to enable MCP Server support
+ * <p>The default value is 'true'.
+ */
+ private Boolean enabled;
+
+ /**
+ * The port of MCP Server
+ * <p>The default value is '0'.
+ */
+ private Integer port;
+
+ /**
+ * the path of mcp
+ */
+ @Nested
+ private MCPPath path;
+
+ /**
+ * streamable or sse
+ */
+ private String protocol;
+
+ public Boolean getEnabled() {
Review Comment:
It is recommended to use `isEnabled`, which is more appropriate.
##########
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;
+ }
+ }
+
+ String serviceSpecificKey = McpConstant.SETTINGS_MCP_SERVICE_PREFIX + "." + interfaceName + ".enabled";
+ if (configuration.containsKey(serviceSpecificKey)) {
+ return configuration.getBoolean(serviceSpecificKey, false);
+ }
+
+ if (configuration.containsKey(McpConstant.PARAM_MCP_ENABLED)) {
+ return configuration.getBoolean(McpConstant.PARAM_MCP_ENABLED, false);
+ }
+
+ if (includePatterns.length > 0) {
+ return isMatchedByPatterns(interfaceName, includePatterns);
+ }
+
+ return defaultEnabled;
+ }
+
+ /**
+ * Check if specific method should be exposed as MCP tool.
+ * Priority: @McpTool(enabled=false) > URL Parameters > @McpTool(enabled=true) > Configuration > Service-level
+ */
+ public boolean shouldExposeMethodAsMcpTool(ProviderModel providerModel, Method method) {
+ String interfaceName = providerModel.getServiceModel().getInterfaceName();
+ String methodName = method.getName();
+
+ if (isMatchedByPatterns(interfaceName, excludePatterns)) {
+ return false;
+ }
+
+ McpTool methodMcpTool = getMethodMcpTool(providerModel, method);
+
+ if (methodMcpTool != null && !methodMcpTool.enabled()) {
+ return false;
+ }
+
+ URL serviceUrl = getServiceUrl(providerModel);
+ if (serviceUrl != null) {
+ String methodUrlValue = serviceUrl.getMethodParameter(methodName, McpConstant.PARAM_MCP_ENABLED);
+
+ if (methodUrlValue != null && StringUtils.isNotEmpty(methodUrlValue)) {
+ return Boolean.parseBoolean(methodUrlValue);
+ }
+
+ String serviceLevelValue = serviceUrl.getParameter(McpConstant.PARAM_MCP_ENABLED);
+
+ if (serviceLevelValue != null && StringUtils.isNotEmpty(serviceLevelValue)) {
+ return Boolean.parseBoolean(serviceLevelValue);
+ }
+ }
+
+ if (methodMcpTool != null && methodMcpTool.enabled()) {
+ return true;
+ }
+
+ String methodConfigKey =
+ McpConstant.SETTINGS_MCP_SERVICE_PREFIX + "." + interfaceName + ".methods." + methodName + ".enabled";
+ if (configuration.containsKey(methodConfigKey)) {
+ return configuration.getBoolean(methodConfigKey, false);
+ }
+
+ if (shouldExposeAsMcpTool(providerModel)) {
+ return Modifier.isPublic(method.getModifiers());
+ }
+
+ return false;
+ }
+
+ /**
+ * Get @McpTool annotation from method, checking both interface and implementation class.
+ */
+ private McpTool getMethodMcpTool(ProviderModel providerModel, Method method) {
+ String methodName = method.getName();
+ Class<?>[] paramTypes = method.getParameterTypes();
+
+ Object serviceBean = providerModel.getServiceInstance();
+ if (serviceBean != null) {
+ try {
+ Method implMethod = serviceBean.getClass().getMethod(methodName, paramTypes);
+ McpTool implMcpTool = implMethod.getAnnotation(McpTool.class);
+ if (implMcpTool != null) {
+ return implMcpTool;
+ }
+ } catch (NoSuchMethodException e) {
+ // Method not found in implementation class
Review Comment:
don't ignore exception
--
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: notifications-unsubscribe@dubbo.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@dubbo.apache.org
For additional commands, e-mail: notifications-help@dubbo.apache.org
Re: [PR] Dubbo MCP SSE Integration [dubbo]
Posted by "Stellar1999 (via GitHub)" <gi...@apache.org>.
Stellar1999 commented on PR #15406:
URL: https://github.com/apache/dubbo/pull/15406#issuecomment-3038039949
Since the mcp-java-sdk has not yet implemented server-side streamable support, I suggest we begin reviewing this Pull Request and proceed with using SSE as the first RC version. Once the streamable specification is implemented, we can release a second version. @AlbumenJ @zrlw @CrazyHZM @oxsean @heliang666s
--
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: notifications-unsubscribe@dubbo.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@dubbo.apache.org
For additional commands, e-mail: notifications-help@dubbo.apache.org