Skip to content

Commit

Permalink
Add a way to select which transport is used when multiple exists
Browse files Browse the repository at this point in the history
  • Loading branch information
geoand committed Jan 14, 2025
1 parent 1f36f62 commit 1c51136
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.quarkiverse.mcp.server.deployment;

import java.util.Optional;

import io.quarkus.runtime.annotations.ConfigPhase;
import io.quarkus.runtime.annotations.ConfigRoot;
import io.smallrye.config.ConfigMapping;

@ConfigMapping(prefix = "quarkus.mcp.server")
@ConfigRoot(phase = ConfigPhase.BUILD_TIME)
public interface McpBuildTimeConfig {

/**
* If multiple transports extensions are on the classpath, select which one is used
*/
Optional<String> transport();
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,33 @@

class McpServerProcessor {

@BuildStep
SelectedTransportBuildItem selectTransport(McpBuildTimeConfig buildTimeConfig,
List<TransportCandidateBuildItem> transportCandidates) {
if (transportCandidates.isEmpty()) {
throw new IllegalStateException(
"At least one MCP transport must be used. Consider adding the one of the 'quarkus-mcp-server-stdio', 'quarkus-mcp-server-sse' extensions");
}
if (transportCandidates.size() == 1) {
return new SelectedTransportBuildItem(transportCandidates.get(0).getName());
}
if (buildTimeConfig.transport().isPresent()) {
String selectedTransportName = buildTimeConfig.transport().get();
for (TransportCandidateBuildItem transportCandidate : transportCandidates) {
if (transportCandidate.getName().equals(selectedTransportName)) {
return new SelectedTransportBuildItem(transportCandidate.getName());
}
}
throw new IllegalStateException(
"Selected transport '" + selectedTransportName + "' does not correspond to any of the added extensions");
} else {
throw new IllegalStateException(
"Multiple transport candidates were found, please select one by setting 'quarkus.mcp.server.transport' to one of the following values: "
+ transportCandidates.stream().map(TransportCandidateBuildItem::getName)
.collect(Collectors.joining(", ")));
}
}

@BuildStep
void addBeans(BuildProducer<AdditionalBeanBuildItem> additionalBeans) {
additionalBeans.produce(AdditionalBeanBuildItem.unremovableOf("io.quarkiverse.mcp.server.runtime.ConnectionManager"));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package io.quarkiverse.mcp.server.deployment;

import io.quarkus.builder.item.SimpleBuildItem;

/**
* The selected MCP transport
*/
public final class SelectedTransportBuildItem extends SimpleBuildItem {

private final String name;

public SelectedTransportBuildItem(String name) {
this.name = name;
}

public String getName() {
return name;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package io.quarkiverse.mcp.server.deployment;

import io.quarkus.builder.item.MultiBuildItem;

/**
* Generated by MCP transport implementations to advertise their existence
*/
public final class TransportCandidateBuildItem extends MultiBuildItem {

private final String name;

public TransportCandidateBuildItem(String name) {
this.name = name;
}

public String getName() {
return name;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import static io.quarkus.deployment.annotations.ExecutionTime.RUNTIME_INIT;

import io.quarkiverse.mcp.server.deployment.SelectedTransportBuildItem;
import io.quarkiverse.mcp.server.deployment.TransportCandidateBuildItem;
import io.quarkiverse.mcp.server.sse.runtime.SseMcpServerRecorder;
import io.quarkiverse.mcp.server.sse.runtime.config.McpSseBuildTimeConfig;
import io.quarkus.arc.deployment.SyntheticBeansRuntimeInitBuildItem;
Expand All @@ -21,12 +23,21 @@ FeatureBuildItem feature() {
return new FeatureBuildItem("mcp-server-sse");
}

@BuildStep
TransportCandidateBuildItem transportCandidate() {
return new TransportCandidateBuildItem("sse");
}

@Record(RUNTIME_INIT)
@Consume(SyntheticBeansRuntimeInitBuildItem.class)
@BuildStep
void registerEndpoints(McpSseBuildTimeConfig config, HttpRootPathBuildItem rootPath, SseMcpServerRecorder recorder,
void registerEndpoints(SelectedTransportBuildItem selectedTransportCandidateBuildItem,
McpSseBuildTimeConfig config, HttpRootPathBuildItem rootPath, SseMcpServerRecorder recorder,
BodyHandlerBuildItem bodyHandler,
BuildProducer<RouteBuildItem> routes) {
if (!selectedTransportCandidateBuildItem.getName().equals("sse")) {
return;
}
String mcpPath = rootPath.relativePath(config.rootPath());

routes.produce(RouteBuildItem.newFrameworkRoute(mcpPath + "/" + "sse")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import static io.quarkus.deployment.annotations.ExecutionTime.RUNTIME_INIT;

import io.quarkiverse.mcp.server.deployment.SelectedTransportBuildItem;
import io.quarkiverse.mcp.server.deployment.TransportCandidateBuildItem;
import io.quarkiverse.mcp.server.stdio.runtime.StdioMcpServerRecorder;
import io.quarkus.arc.deployment.SyntheticBeansRuntimeInitBuildItem;
import io.quarkus.deployment.annotations.BuildStep;
Expand All @@ -16,10 +18,18 @@ FeatureBuildItem feature() {
return new FeatureBuildItem("mcp-server-stdio");
}

@BuildStep
TransportCandidateBuildItem transportCandidate() {
return new TransportCandidateBuildItem("stdio");
}

@Record(RUNTIME_INIT)
@Consume(SyntheticBeansRuntimeInitBuildItem.class)
@BuildStep
void initialize(StdioMcpServerRecorder recorder) {
void initialize(SelectedTransportBuildItem selectedTransportCandidateBuildItem, StdioMcpServerRecorder recorder) {
if (!selectedTransportCandidateBuildItem.getName().equals("stdio")) {
return;
}
recorder.initialize();
}

Expand Down

0 comments on commit 1c51136

Please sign in to comment.