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 = "/messages") 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}