001package org.nasdanika.ai.mcp;
002
003import java.util.ArrayList;
004import java.util.Collection;
005
006import org.nasdanika.cli.ParentCommands;
007import org.nasdanika.cli.RootCommand;
008import org.nasdanika.common.Description;
009
010import io.modelcontextprotocol.server.McpServerFeatures.AsyncPromptSpecification;
011import io.modelcontextprotocol.server.McpServerFeatures.AsyncResourceSpecification;
012import io.modelcontextprotocol.server.McpServerFeatures.AsyncToolSpecification;
013import io.modelcontextprotocol.server.McpServerFeatures.SyncPromptSpecification;
014import io.modelcontextprotocol.server.McpServerFeatures.SyncResourceSpecification;
015import io.modelcontextprotocol.server.McpServerFeatures.SyncToolSpecification;
016import io.modelcontextprotocol.server.McpSyncServerExchange;
017import io.opentelemetry.api.OpenTelemetry;
018import picocli.CommandLine.Command;
019import reactor.core.publisher.Mono;
020import reactor.core.scheduler.Schedulers;
021
022@Command(
023                description = "MCP server",
024                versionProvider = ModuleVersionProvider.class,          
025                mixinStandardHelpOptions = true,
026                name = "mcp-server")
027@ParentCommands(RootCommand.class)
028@Description(icon = "https://docs.nasdanika.org/images/mcp.png")
029public class McpServerCommand extends McpServerCommandBase {
030        
031        private Collection<AsyncResourceSpecification> resourceSpecifications = new ArrayList<>();
032        private Collection<AsyncToolSpecification> toolSpecifications = new ArrayList<>();
033        private Collection<AsyncPromptSpecification> promptSpecifications = new ArrayList<>();
034
035        public McpServerCommand(
036                        OpenTelemetry openTelemetry,                    
037                        Collection<SyncPromptSpecification> syncPromptSpecifications,
038                        Collection<AsyncPromptSpecification> asyncPromptSpecifications,                 
039                        Collection<SyncResourceSpecification> syncResourceSpecifications,
040                        Collection<AsyncResourceSpecification> asyncResourceSpecifications,
041                        Collection<SyncToolSpecification> syncToolSpecifications,
042                        Collection<AsyncToolSpecification> asyncToolSpecifications) {
043                super(openTelemetry);
044                
045                if (syncPromptSpecifications != null) {
046                        for (SyncPromptSpecification syncPromptSpecification: syncPromptSpecifications) {
047                                this.promptSpecifications.add(new AsyncPromptSpecification(
048                                        syncPromptSpecification.prompt(),
049                                        (exchange, req) -> Mono
050                                                .fromCallable(() -> syncPromptSpecification.promptHandler().apply(new McpSyncServerExchange(exchange), req))
051                                                .subscribeOn(Schedulers.boundedElastic())));
052                        }
053                }
054                if (asyncPromptSpecifications != null) {
055                        promptSpecifications.addAll(asyncPromptSpecifications);
056                }
057                
058                if (syncResourceSpecifications != null) {
059                        for (SyncResourceSpecification syncResourceSpecification: syncResourceSpecifications) {
060                                this.resourceSpecifications.add(new AsyncResourceSpecification(
061                                        syncResourceSpecification.resource(),
062                                        (exchange, req) -> Mono
063                                                .fromCallable(() -> syncResourceSpecification.readHandler().apply(new McpSyncServerExchange(exchange), req))
064                                                .subscribeOn(Schedulers.boundedElastic())));
065                        }
066                }
067                if (asyncResourceSpecifications != null) {
068                        resourceSpecifications.addAll(asyncResourceSpecifications);
069                }
070                
071                if (syncToolSpecifications != null) {
072                        for (SyncToolSpecification syncToolSpecification: syncToolSpecifications) {
073                                this.toolSpecifications.add(new AsyncToolSpecification(
074                                        syncToolSpecification.tool(),
075                                        (exchange, req) -> Mono
076                                                .fromCallable(() -> syncToolSpecification.call().apply(new McpSyncServerExchange(exchange), req))
077                                                .subscribeOn(Schedulers.boundedElastic())));
078                        }
079                }
080                if (asyncToolSpecifications != null) {
081                        toolSpecifications.addAll(asyncToolSpecifications);
082                }
083        }
084        
085        public boolean isEmpty() {
086                return resourceSpecifications.isEmpty() && toolSpecifications.isEmpty() && promptSpecifications.isEmpty();
087        }
088
089        @Override
090        public Collection<AsyncResourceSpecification> getResourceSpecifications() {
091                return resourceSpecifications;
092        }
093
094        @Override
095        public Collection<AsyncToolSpecification> getToolSpecifications() {
096                return toolSpecifications;
097        }
098
099        @Override
100        public Collection<AsyncPromptSpecification> getPromptSpecifications() {
101                return promptSpecifications;
102        }
103
104}