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}