Please do a quick search on GitHub issues first, the feature you are about to request might have already been requested.
#79
#80
#425
#432
Expected Behavior
The user can customize the endpoint routing functions.
We hope to wrap HTTP-APIs to MCP-Server-Tools.
- Uses Spring WebFlux's RouterFunction for endpoint handling (GET, POST, DELETE)
We hope to support the follow MCP-Servers in one application process:
/mcp/mcp/mcp-server-app-name-A -> some MCP-Tools/mcp/mcp-server-app-name-B -> some MCP-Tools/mcp/mcp-server-app-name-C -> some MCP-Tools
Current Behavior
The RouterFunction is private initialization in WebFluxStreamableServerTransportProvider, and its constructor is private.
publicclassWebFluxStreamableServerTransportProviderimplementsMcpStreamableServerTransportProvider {
privatefinalStringmcpEndpoint;
privatefinalRouterFunction<?> routerFunction;
privateWebFluxStreamableServerTransportProvider(ObjectMapperobjectMapper, StringmcpEndpoint,
McpTransportContextExtractor<ServerRequest> contextExtractor, booleandisallowDelete,
DurationkeepAliveInterval) {
this.mcpEndpoint = mcpEndpoint;
this.routerFunction = RouterFunctions.route()
.GET(this.mcpEndpoint, this::handleGet)
.POST(this.mcpEndpoint, this::handlePost)
.DELETE(this.mcpEndpoint, this::handleDelete)
.build();
}
publicRouterFunction<?> getRouterFunction() {
returnthis.routerFunction;
}
}publicabstractclassRouterFunctions {
publicstaticBuilderroute() {
returnnewRouterFunctionBuilder();
}
}classRouterFunctionBuilderimplementsRouterFunctions.Builder {
privatefinalList<RouterFunction<ServerResponse>> routerFunctions = newArrayList<>();
@OverridepublicRouterFunctions.Builderadd(RouterFunction<ServerResponse> routerFunction) {
Assert.notNull(routerFunction, "RouterFunction must not be null");
this.routerFunctions.add(routerFunction);
returnthis;
}
@OverridepublicRouterFunction<ServerResponse> build() {
if (this.routerFunctions.isEmpty()) {
thrownewIllegalStateException("No routes registered. Register a route with GET(), POST(), etc.");
}
RouterFunction<ServerResponse> result = newBuiltRouterFunction(this.routerFunctions);
if (this.filterFunctions.isEmpty() && this.errorHandlers.isEmpty()) {
returnresult;
}
else {
HandlerFilterFunction<ServerResponse, ServerResponse> filter =
Stream.concat(this.filterFunctions.stream(), this.errorHandlers.stream())
.reduce(HandlerFilterFunction::andThen)
.orElseThrow(IllegalStateException::new);
returnresult.filter(filter);
}
}
/** * Router function returned by {@link #build()} that simply iterates over the registered routes. */privatestaticclassBuiltRouterFunctionextendsRouterFunctions.AbstractRouterFunction<ServerResponse> {
privatefinalList<RouterFunction<ServerResponse>> routerFunctions;
publicBuiltRouterFunction(List<RouterFunction<ServerResponse>> routerFunctions) {
Assert.notEmpty(routerFunctions, "RouterFunctions must not be empty");
this.routerFunctions = newArrayList<>(routerFunctions);
}
@OverridepublicMono<HandlerFunction<ServerResponse>> route(ServerRequestrequest) {
returnFlux.fromIterable(this.routerFunctions)
.concatMap(routerFunction -> routerFunction.route(request))
.next();
}
@Overridepublicvoidaccept(RouterFunctions.Visitorvisitor) {
this.routerFunctions.forEach(routerFunction -> routerFunction.accept(visitor));
}
}
}Context
API is MCP, allowing AI to connect to the real world with lower cost, speed, and security. The existing APIs can be instantly converted into a Remote MCP Server, laying out the shortest connection path between AI and the real world.
We need to start multiple WebFluxStreamableServerTransportProvider, McpAsyncServer instances in one application process. Please to see the follow code in McpServerConfiguration, that is reference to McpServerStreamableHttpWebFluxAutoConfiguration.
It can support the follow MCP-Servers:
/mcp/mcp/mcp-server-app-name-A -> some MCP-Tools/mcp/mcp-server-app-name-B -> some MCP-Tools
But the RouterFunction can not dynamic update when the database update for some new app-name MCP-Server.
/mcp/mcp-server-app-name-C
@Slf4j@EnableConfigurationProperties({ McpServerStreamableHttpProperties.class })
@Configuration(proxyBeanMethods = false)
publicclassMcpServerConfiguration {
publicMcpServerConfiguration() {
log.info("create McpServerConfiguration");
}
@BeanpublicMap<String, List<McpTool>> mcpToolListMap() {
List<String> yamlFiles = List.of(
"mcp-server-user-apis.yml",
"mcp-server-travel-apis.yml"
);
returnyamlFiles.stream()
.map(YamlUtil::load)
.collect(Collectors.toMap(
mcpServerRule -> mcpServerRule.getServer().getName(),
McpServerRule::getTools
));
}
@Bean@ConditionalOnProperty(prefix = McpServerProperties.CONFIG_PREFIX, name = "type", havingValue = "ASYNC")
@Conditional({ McpServerAutoConfiguration.EnabledStreamableServerCondition.class })
publicMap<String, WebFluxStreamableServerTransportProvider> transportProviderMap(
Map<String, List<McpTool>> mcpToolListMap) {
log.info("init transportProviderMap");
Map<String, WebFluxStreamableServerTransportProvider> transportProviderMap =
newConcurrentHashMap<>(mcpToolListMap.size());
transportProviderMap.putAll(McpServerTransportManager.transportProviderMap(mcpToolListMap.keySet()));
returntransportProviderMap;
}
/** * @see McpServerAutoConfiguration#capabilitiesBuilder() */@BeanpublicMcpSchema.ServerCapabilities.BuildercapabilitiesBuilder() {
log.info("init capabilitiesBuilder");
returnMcpSchema.ServerCapabilities.builder()
.tools(true);
}
@Bean@ConditionalOnProperty(prefix = McpServerProperties.CONFIG_PREFIX, name = "type", havingValue = "ASYNC")
publicMap<String, McpAsyncServer> mcpAsyncServerMap(
Map<String, List<McpTool>> mcpToolListMap,
Map<String, WebFluxStreamableServerTransportProvider> transportProviderMap,
McpSchema.ServerCapabilities.BuildercapabilitiesBuilder) {
log.info("init mcpAsyncServerMap");
returnMcpServerManager.mcpAsyncServerMap(mcpToolListMap, transportProviderMap, capabilitiesBuilder);
}
/** * @see McpServerStreamableHttpWebFluxAutoConfiguration#webFluxStreamableServerTransportProvider */@Bean@ConditionalOnProperty(prefix = McpServerProperties.CONFIG_PREFIX, name = "type", havingValue = "ASYNC")
@Conditional({ McpServerAutoConfiguration.EnabledStreamableServerCondition.class })
publicWebFluxStreamableServerTransportProviderwebFluxStreamableServerTransportProvider(
ObjectProvider<ObjectMapper> objectMapperProvider, McpServerStreamableHttpPropertiesserverProperties) {
log.info("init webFluxStreamableServerTransportProvider");
ObjectMapperobjectMapper = objectMapperProvider.getIfAvailable(ObjectMapper::new);
returnWebFluxStreamableServerTransportProvider.builder()
.objectMapper(objectMapper)
.messageEndpoint(serverProperties.getMcpEndpoint())
.keepAliveInterval(serverProperties.getKeepAliveInterval())
.disallowDelete(serverProperties.isDisallowDelete())
.build();
}
/** * @see McpServerStreamableHttpWebFluxAutoConfiguration#webFluxStreamableServerRouterFunction */// Router function for streamable http transport used by Spring WebFlux to start an// HTTP server.@Bean@ConditionalOnProperty(prefix = McpServerProperties.CONFIG_PREFIX, name = "type", havingValue = "ASYNC")
@Conditional({ McpServerAutoConfiguration.EnabledStreamableServerCondition.class })
publicRouterFunction<?> webFluxStreamableServerRouterFunction(
WebFluxStreamableServerTransportProviderwebFluxProvider,
Map<String, WebFluxStreamableServerTransportProvider> transportProviderMap) {
log.info("init webFluxStreamableServerRouterFunction");
RouterFunctions.BuilderrouterFunctionBuilder = RouterFunctions.route();
routerFunctionBuilder.add((RouterFunction<ServerResponse>) webFluxProvider.getRouterFunction());
for (WebFluxStreamableServerTransportProvidertransportProvider : transportProviderMap.values()) {
routerFunctionBuilder.add((RouterFunction<ServerResponse>) transportProvider.getRouterFunction());
}
returnrouterFunctionBuilder.build();
}
}
Please do a quick search on GitHub issues first, the feature you are about to request might have already been requested.
#79
#80
#425
#432
Expected Behavior
The user can customize the endpoint routing functions.
We hope to wrap HTTP-APIs to MCP-Server-Tools.
We hope to support the follow MCP-Servers in one application process:
/mcp/mcp/mcp-server-app-name-A-> some MCP-Tools/mcp/mcp-server-app-name-B-> some MCP-Tools/mcp/mcp-server-app-name-C-> some MCP-ToolsCurrent Behavior
The
RouterFunctionis private initialization inWebFluxStreamableServerTransportProvider, and its constructor is private.Context
API is MCP, allowing AI to connect to the real world with lower cost, speed, and security. The existing APIs can be instantly converted into a Remote MCP Server, laying out the shortest connection path between AI and the real world.
We need to start multiple
WebFluxStreamableServerTransportProvider,McpAsyncServerinstances in one application process. Please to see the follow code inMcpServerConfiguration, that is reference toMcpServerStreamableHttpWebFluxAutoConfiguration.It can support the follow MCP-Servers:
/mcp/mcp/mcp-server-app-name-A-> some MCP-Tools/mcp/mcp-server-app-name-B-> some MCP-ToolsBut the
RouterFunctioncan not dynamic update when the database update for some new app-name MCP-Server./mcp/mcp-server-app-name-C