diff --git a/.licenserc.yaml b/.licenserc.yaml index f298fffad..d880a9c78 100644 --- a/.licenserc.yaml +++ b/.licenserc.yaml @@ -41,6 +41,7 @@ header: - 'pnpm-lock.yaml' - '**/*.txt' - '**/*.md' + - '**/*.prompt' comment: on-failure @@ -109,6 +110,20 @@ dependency: license: EDL-1.0 - name: org.jline:jline license: BSD-3-Clause + - name: com.github.victools:jsonschema-module-jackson + license: Apache-2.0 + - name: com.fasterxml.jackson.module:jackson-module-jsonSchema + license: Apache-2.0 + - name: io.swagger.core.v3:swagger-annotations + license: Apache-2.0 + - name: com.github.victools:jsonschema-module-swagger-2 + license: Apache-2.0 + - name: org.antlr:antlr-runtime + license: BSD-3-Clause + - name: org.antlr:antlr4-runtime + license: BSD-3-Clause + - name: org.antlr:ST4 + license: BSD-3-Clause # Weak compatible licenses - name: javax.servlet.jsp:jsp-api license: CDDL-1.1 diff --git a/bigtop-manager-bom/pom.xml b/bigtop-manager-bom/pom.xml index 3bc38e605..4b078b9f0 100644 --- a/bigtop-manager-bom/pom.xml +++ b/bigtop-manager-bom/pom.xml @@ -31,6 +31,7 @@ Bigtop Manager Bom + 1.0.0-RC1 3.1.1 2.2.0 2.3.32 @@ -55,6 +56,7 @@ 1.0.1-beta6 3.0.3 2.1.0 + 4.29.0 @@ -276,11 +278,25 @@ langchain4j-community-qianfan ${langchain4j.version} + + + org.springframework.ai + spring-ai-starter-mcp-server-webmvc + ${spring-ai.version} + + + + com.github.victools + jsonschema-module-jackson + ${victools.version} + + dev.langchain4j langchain4j-community-dashscope ${langchain4j.version} + dev.langchain4j langchain4j-reactor diff --git a/bigtop-manager-common/src/main/java/org/apache/bigtop/manager/common/utils/ProjectPathUtils.java b/bigtop-manager-common/src/main/java/org/apache/bigtop/manager/common/utils/ProjectPathUtils.java index f8f63c08e..1cd620f3a 100644 --- a/bigtop-manager-common/src/main/java/org/apache/bigtop/manager/common/utils/ProjectPathUtils.java +++ b/bigtop-manager-common/src/main/java/org/apache/bigtop/manager/common/utils/ProjectPathUtils.java @@ -51,6 +51,10 @@ public static String getAgentCachePath() { return getProjectStoreDir() + File.separator + "agent-caches"; } + public static String getPromptsPath() { + return getProjectResourcesDir() + File.separator + "prompts"; + } + private static String getProjectResourcesDir() { if (Environments.isDevMode()) { return Objects.requireNonNull(ProjectPathUtils.class.getResource("/")) diff --git a/bigtop-manager-server/pom.xml b/bigtop-manager-server/pom.xml index 539592057..7d4545749 100644 --- a/bigtop-manager-server/pom.xml +++ b/bigtop-manager-server/pom.xml @@ -63,7 +63,10 @@ org.apache.bigtop bigtop-manager-ai - + + com.github.victools + jsonschema-module-jackson + org.springframework.boot spring-boot-starter-web @@ -74,6 +77,11 @@ spring-boot-starter-webflux + + org.springframework.ai + spring-ai-starter-mcp-server-webmvc + + org.springframework.boot spring-boot-starter-validation diff --git a/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/config/McpConfig.java b/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/config/McpConfig.java new file mode 100644 index 000000000..700bd9430 --- /dev/null +++ b/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/config/McpConfig.java @@ -0,0 +1,39 @@ +/* + * 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 + * + * https://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.bigtop.manager.server.config; + +import org.apache.bigtop.manager.server.holder.SpringContextHolder; +import org.apache.bigtop.manager.server.mcp.tool.McpTool; + +import org.springframework.ai.tool.ToolCallbackProvider; +import org.springframework.ai.tool.method.MethodToolCallbackProvider; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class McpConfig { + + @Bean + public ToolCallbackProvider mcpTools() { + return MethodToolCallbackProvider.builder() + .toolObjects( + (Object[]) SpringContextHolder.getMcpTools().values().toArray(new McpTool[0])) + .build(); + } +} diff --git a/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/config/WebConfig.java b/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/config/WebConfig.java index 15ff1ce6d..efb46e690 100644 --- a/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/config/WebConfig.java +++ b/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/config/WebConfig.java @@ -19,6 +19,7 @@ package org.apache.bigtop.manager.server.config; import org.apache.bigtop.manager.server.interceptor.AuthInterceptor; +import org.apache.bigtop.manager.server.interceptor.MCPInterceptor; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; @@ -35,6 +36,9 @@ public class WebConfig implements WebMvcConfigurer { @Resource private AuthInterceptor authInterceptor; + @Resource + private MCPInterceptor mcpInterceptor; + private static final String API_PREFIX = "/api"; private static final String PREFIXED_PACKAGE = "org.apache.bigtop.manager.server.controller"; @@ -45,10 +49,14 @@ public void addInterceptors(InterceptorRegistry registry) { .addPathPatterns("/**") // Server APIs .excludePathPatterns("/api/salt", "/api/nonce", "/api/login") + // MCP APIs + .excludePathPatterns("/mcp/**") // Frontend pages .excludePathPatterns("/", "/ui/**", "/favicon.ico", "/error") // Swagger pages .excludePathPatterns("/swagger-ui/**", "/v3/**", "/swagger-ui.html"); + + registry.addInterceptor(mcpInterceptor).addPathPatterns("/mcp/**"); } @Override diff --git a/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/holder/SpringContextHolder.java b/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/holder/SpringContextHolder.java index 381ba6832..7f38c1763 100644 --- a/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/holder/SpringContextHolder.java +++ b/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/holder/SpringContextHolder.java @@ -20,6 +20,7 @@ import org.apache.bigtop.manager.server.command.factory.JobFactory; import org.apache.bigtop.manager.server.command.validator.CommandValidator; +import org.apache.bigtop.manager.server.mcp.tool.McpTool; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; @@ -52,4 +53,8 @@ public static Map getCommandValidators() { public static Map getJobFactories() { return applicationContext.getBeansOfType(JobFactory.class); } + + public static Map getMcpTools() { + return applicationContext.getBeansOfType(McpTool.class); + } } diff --git a/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/interceptor/MCPInterceptor.java b/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/interceptor/MCPInterceptor.java new file mode 100644 index 000000000..f02bcab4c --- /dev/null +++ b/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/interceptor/MCPInterceptor.java @@ -0,0 +1,68 @@ +/* + * 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 + * + * https://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.bigtop.manager.server.interceptor; + +import org.apache.bigtop.manager.common.utils.JsonUtils; +import org.apache.bigtop.manager.server.utils.ResponseEntity; + +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.ModelAndView; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +@Component +public class MCPInterceptor implements HandlerInterceptor { + + private ResponseEntity responseEntity; + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) + throws Exception { + if (checkAuthenticated(request)) { + return HandlerInterceptor.super.preHandle(request, response, handler); + } else { + response.setHeader("Content-Type", "application/json; charset=UTF-8"); + response.getWriter().write(JsonUtils.writeAsString(responseEntity)); + return false; + } + } + + @Override + public void postHandle( + HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) + throws Exception { + HandlerInterceptor.super.postHandle(request, response, handler, modelAndView); + } + + @Override + public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) + throws Exception { + HandlerInterceptor.super.afterCompletion(request, response, handler, ex); + } + + private Boolean checkAuthenticated(HttpServletRequest request) { + return true; + } + + private Boolean checkPermission() { + return true; + } +} diff --git a/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/mcp/converter/JsonToolCallResultConverter.java b/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/mcp/converter/JsonToolCallResultConverter.java new file mode 100644 index 000000000..3632001e5 --- /dev/null +++ b/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/mcp/converter/JsonToolCallResultConverter.java @@ -0,0 +1,66 @@ +/* + * 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 + * + * https://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.bigtop.manager.server.mcp.converter; + +import org.apache.bigtop.manager.common.utils.JsonUtils; + +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ai.tool.execution.ToolCallResultConverter; +import org.springframework.lang.Nullable; + +import javax.imageio.ImageIO; +import java.awt.image.RenderedImage; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.lang.reflect.Type; +import java.util.Base64; +import java.util.Map; +import java.util.Objects; + +/** + * Custom converter only replace JsonParser to JsonUtils. + * See original source code from {@link org.springframework.ai.tool.execution.DefaultToolCallResultConverter} + */ +public class JsonToolCallResultConverter implements ToolCallResultConverter { + + private static final Logger logger = LoggerFactory.getLogger(JsonToolCallResultConverter.class); + + @NotNull @Override + public String convert(@Nullable Object result, @Nullable Type returnType) { + if (returnType == Void.TYPE) { + logger.debug("The tool has no return type. Converting to conventional response."); + return JsonUtils.writeAsString("Done"); + } + if (result instanceof RenderedImage) { + final var buf = new ByteArrayOutputStream(1024 * 4); + try { + ImageIO.write((RenderedImage) result, "PNG", buf); + } catch (IOException e) { + return "Failed to convert tool result to a base64 image: " + e.getMessage(); + } + final var imgB64 = Base64.getEncoder().encodeToString(buf.toByteArray()); + return JsonUtils.writeAsString(Map.of("mimeType", "image/png", "data", imgB64)); + } else { + logger.debug("Converting tool result to JSON."); + return Objects.requireNonNull(JsonUtils.writeAsString(result)); + } + } +} diff --git a/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/mcp/prompt/Prompts.java b/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/mcp/prompt/Prompts.java new file mode 100644 index 000000000..9ee0677ef --- /dev/null +++ b/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/mcp/prompt/Prompts.java @@ -0,0 +1,44 @@ +/* + * 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 + * + * https://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.bigtop.manager.server.mcp.prompt; + +import org.apache.bigtop.manager.common.utils.FileUtils; +import org.apache.bigtop.manager.common.utils.ProjectPathUtils; +import org.apache.bigtop.manager.server.exception.ServerException; + +import java.io.File; + +/** + * Static prompts for tools. + */ +public class Prompts { + + public static final String SAMPLE = getText("sample.prompt"); + + private static String getText(String filename) { + String promptPath = ProjectPathUtils.getPromptsPath(); + String filePath = promptPath + File.separator + filename; + + try { + return FileUtils.readFile2Str(new File(filePath)); + } catch (Exception e) { + throw new ServerException("Error reading prompt file: " + filePath, e); + } + } +} diff --git a/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/mcp/tool/McpTool.java b/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/mcp/tool/McpTool.java new file mode 100644 index 000000000..0968c5410 --- /dev/null +++ b/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/mcp/tool/McpTool.java @@ -0,0 +1,21 @@ +/* + * 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 + * + * https://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.bigtop.manager.server.mcp.tool; + +public interface McpTool {} diff --git a/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/mcp/tool/StackMcpTool.java b/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/mcp/tool/StackMcpTool.java new file mode 100644 index 000000000..229d6b78d --- /dev/null +++ b/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/mcp/tool/StackMcpTool.java @@ -0,0 +1,60 @@ +/* + * 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 + * + * https://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.bigtop.manager.server.mcp.tool; + +import org.apache.bigtop.manager.server.mcp.converter.JsonToolCallResultConverter; +import org.apache.bigtop.manager.server.model.converter.ServiceConverter; +import org.apache.bigtop.manager.server.model.converter.StackConverter; +import org.apache.bigtop.manager.server.model.dto.ServiceDTO; +import org.apache.bigtop.manager.server.model.dto.StackDTO; +import org.apache.bigtop.manager.server.model.vo.StackVO; +import org.apache.bigtop.manager.server.utils.StackUtils; + +import org.springframework.ai.tool.annotation.Tool; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +@Component +public class StackMcpTool implements McpTool { + + @Tool( + name = "ListStacks", + description = "List supported stacks", + resultConverter = JsonToolCallResultConverter.class) + public List listStacks() { + List stackVOList = new ArrayList<>(); + + for (Map.Entry> entry : StackUtils.STACK_SERVICE_MAP.entrySet()) { + StackDTO stackDTO = entry.getKey(); + List serviceDTOList = entry.getValue(); + for (ServiceDTO serviceDTO : serviceDTOList) { + serviceDTO.setConfigs(StackUtils.SERVICE_CONFIG_MAP.get(serviceDTO.getName())); + } + + StackVO stackVO = StackConverter.INSTANCE.fromDTO2VO(stackDTO); + stackVO.setServices(ServiceConverter.INSTANCE.fromDTO2VO(serviceDTOList)); + stackVOList.add(stackVO); + } + + return stackVOList; + } +} diff --git a/bigtop-manager-server/src/main/resources/application.yml b/bigtop-manager-server/src/main/resources/application.yml index 87b5dbd0f..860573b97 100644 --- a/bigtop-manager-server/src/main/resources/application.yml +++ b/bigtop-manager-server/src/main/resources/application.yml @@ -18,6 +18,13 @@ # spring: + ai: + mcp: + server: + name: bigtop-manager-mcp-server + type: ASYNC + sse-endpoint: /mcp/sse + sse-message-endpoint: /mcp/messages banner: charset: utf-8 application: diff --git a/bigtop-manager-server/src/main/resources/prompts/sample.prompt b/bigtop-manager-server/src/main/resources/prompts/sample.prompt new file mode 100644 index 000000000..a5daf3b9e --- /dev/null +++ b/bigtop-manager-server/src/main/resources/prompts/sample.prompt @@ -0,0 +1 @@ +sample prompt file \ No newline at end of file