001package org.nasdanika.ai.mcp.sse;
002
003import org.nasdanika.ai.mcp.McpAsyncServerProvider;
004import org.nasdanika.cli.ParentCommands;
005import org.nasdanika.common.Description;
006import org.nasdanika.http.AbstractHttpServerCommand;
007
008import com.fasterxml.jackson.databind.ObjectMapper;
009
010import io.opentelemetry.api.OpenTelemetry;
011import picocli.CommandLine.Command;
012import picocli.CommandLine.Option;
013import picocli.CommandLine.ParentCommand;
014import reactor.netty.http.server.HttpServerRoutes;
015
016@Command(
017                description = "MCP SSE Transport",
018                versionProvider = ModuleVersionProvider.class,          
019                mixinStandardHelpOptions = true,
020                name = "sse")
021@ParentCommands(McpAsyncServerProvider.class)
022@Description(icon = "https://docs.nasdanika.org/images/http.svg")
023public class SseTransportCommand extends AbstractHttpServerCommand {
024        
025        private OpenTelemetry openTelemetry;
026
027        public SseTransportCommand(OpenTelemetry openTelemetry) {
028                this.openTelemetry = openTelemetry;
029        }
030        
031        protected String getInstrumentationScopeName() {
032                return getClass().getName();
033        }
034        
035        @ParentCommand
036        private McpAsyncServerProvider asyncServerProvider;
037                
038        @Option(names = "--base-path", defaultValue = "")
039        private String basePath = "";
040        
041        @Option(
042                        names = "--resolve-remote-host-name",
043                        negatable = true,
044                        defaultValue = "true")
045        private boolean resolveRemoteHostName;  
046                
047        @Option(
048                        names = "--message-endpoint", 
049                        description = "Defaults to ${DEFAULT-VALUE}",
050                        defaultValue = "/sse")
051        private String messageEndpoint; 
052        
053        @Option(
054                        names = "--sse-endpoint", 
055                        description = "Defaults to ${DEFAULT-VALUE}",
056                        defaultValue = "/sse")
057        private String sseEndpoint;     
058        
059        protected void buildRoutes(HttpServerRoutes routes) {
060                HttpServerRoutesTransportProvider transportProvider =
061                        HttpServerRoutesTransportProvider.builder()                     
062                                .propagator(openTelemetry.getPropagators().getTextMapPropagator())
063                                .tracer(openTelemetry.getTracer(getInstrumentationScopeName()))
064                                .basePath(basePath)
065                                .resolveRemoteHostName(resolveRemoteHostName)
066                                .sseEndpoint(sseEndpoint)
067                                .messageEndpoint(messageEndpoint)
068                                .objectMapper(new ObjectMapper())
069                                .build(routes);
070                
071                asyncServerProvider.createServer(transportProvider);
072        }
073        
074        @Override
075        public Integer call() throws Exception {
076                startServer(this::buildRoutes);
077                return 0;
078        }
079        
080}