11package io .quarkiverse .mcp .server .sse .deployment ;
22
33import static io .quarkus .deployment .annotations .ExecutionTime .RUNTIME_INIT ;
4+ import static io .quarkus .deployment .annotations .ExecutionTime .STATIC_INIT ;
45
56import java .util .ArrayList ;
67import java .util .HashSet ;
78import java .util .List ;
89import java .util .Map ;
910import java .util .Set ;
1011
11- import jakarta .enterprise .inject .spi .EventContext ;
12-
13- import org .jboss .jandex .DotName ;
12+ import jakarta .inject .Singleton ;
1413
1514import io .quarkiverse .mcp .server .deployment .ServerNameBuildItem ;
15+ import io .quarkiverse .mcp .server .sse .runtime .McpServerEndpoints ;
16+ import io .quarkiverse .mcp .server .sse .runtime .McpServerEndpoints .McpServerEndpoint ;
17+ import io .quarkiverse .mcp .server .sse .runtime .McpServerEndpointsLogger ;
1618import io .quarkiverse .mcp .server .sse .runtime .SseMcpMessageHandler ;
1719import io .quarkiverse .mcp .server .sse .runtime .SseMcpServerRecorder ;
18- import io .quarkiverse .mcp .server .sse .runtime .SseMcpServerRecorder .McpServerEndpoints ;
1920import io .quarkiverse .mcp .server .sse .runtime .StreamableHttpMcpMessageHandler ;
2021import io .quarkiverse .mcp .server .sse .runtime .config .McpSseServerBuildTimeConfig ;
2122import io .quarkiverse .mcp .server .sse .runtime .config .McpSseServersBuildTimeConfig ;
2223import io .quarkus .arc .deployment .AdditionalBeanBuildItem ;
23- import io .quarkus .arc .deployment .ObserverRegistrationPhaseBuildItem ;
24- import io .quarkus .arc .deployment .ObserverRegistrationPhaseBuildItem .ObserverConfiguratorBuildItem ;
24+ import io .quarkus .arc .deployment .SyntheticBeanBuildItem ;
2525import io .quarkus .arc .deployment .SyntheticBeansRuntimeInitBuildItem ;
26- import io .quarkus .arc .processor .ObserverConfigurator ;
2726import io .quarkus .deployment .annotations .BuildProducer ;
2827import io .quarkus .deployment .annotations .BuildStep ;
2928import io .quarkus .deployment .annotations .Consume ;
3029import io .quarkus .deployment .annotations .Record ;
3130import io .quarkus .deployment .builditem .FeatureBuildItem ;
32- import io .quarkus .gizmo .Gizmo ;
33- import io .quarkus .gizmo .MethodCreator ;
34- import io .quarkus .gizmo .MethodDescriptor ;
35- import io .quarkus .gizmo .ResultHandle ;
36- import io .quarkus .vertx .http .HttpServerStart ;
37- import io .quarkus .vertx .http .HttpsServerStart ;
3831import io .quarkus .vertx .http .deployment .BodyHandlerBuildItem ;
3932import io .quarkus .vertx .http .deployment .HttpRootPathBuildItem ;
4033import io .quarkus .vertx .http .deployment .spi .RouteBuildItem ;
41- import io .vertx .core .http .HttpServerOptions ;
4234
4335public class SseMcpServerProcessor {
4436
@@ -50,8 +42,11 @@ FeatureBuildItem feature() {
5042 @ BuildStep
5143 void addBeans (BuildProducer <AdditionalBeanBuildItem > additionalBeans ) {
5244 additionalBeans .produce (
53- AdditionalBeanBuildItem .builder ().setUnremovable ()
54- .addBeanClasses (SseMcpMessageHandler .class , StreamableHttpMcpMessageHandler .class ).build ());
45+ AdditionalBeanBuildItem .builder ()
46+ .setUnremovable ()
47+ .addBeanClasses (SseMcpMessageHandler .class , StreamableHttpMcpMessageHandler .class ,
48+ McpServerEndpointsLogger .class )
49+ .build ());
5550 }
5651
5752 @ BuildStep
@@ -64,79 +59,55 @@ void serverNames(McpSseServersBuildTimeConfig config, BuildProducer<ServerNameBu
6459 @ Record (RUNTIME_INIT )
6560 @ Consume (SyntheticBeansRuntimeInitBuildItem .class )
6661 @ BuildStep
67- void registerEndpoints (McpSseServersBuildTimeConfig config , HttpRootPathBuildItem httpRootPath ,
62+ void registerEndpoints (McpServerEndpointsBuildItem mcpServerEndpoints ,
6863 SseMcpServerRecorder recorder ,
6964 BodyHandlerBuildItem bodyHandler ,
70- BuildProducer <RouteBuildItem > routes ,
71- ObserverRegistrationPhaseBuildItem observerRegistrationPhase ,
72- BuildProducer <ObserverConfiguratorBuildItem > observers ) {
73-
74- Set <String > rootPaths = new HashSet <>();
75- List <McpServerEndpoints > endpoints = new ArrayList <>();
76- for (Map .Entry <String , McpSseServerBuildTimeConfig > e : config .servers ().entrySet ()) {
77- String serverName = e .getKey ();
78- String rootPath = e .getValue ().sse ().rootPath ();
79- if (!rootPaths .add (rootPath )) {
80- throw new IllegalStateException ("Multiple server configurations define the same root path: " + rootPath );
81- }
82-
83- // By default /mcp
84- String mcpPath = httpRootPath .relativePath (rootPath );
85-
65+ BuildProducer <RouteBuildItem > routes ) {
66+ for (McpServerEndpoint endpoint : mcpServerEndpoints .getEndpoints ()) {
8667 // Streamable HTTP transport
87- routes .produce (RouteBuildItem .newFrameworkRoute (mcpPath )
68+ routes .produce (RouteBuildItem .newFrameworkRoute (endpoint . mcpPath )
8869 .withRouteCustomizer (recorder .addBodyHandler (bodyHandler .getHandler ()))
89- .withRequestHandler (recorder .createMcpEndpointHandler (serverName ))
70+ .withRequestHandler (recorder .createMcpEndpointHandler (endpoint . serverName ))
9071 .build ());
91-
9272 // SSE/HTTP transport
93- String ssePath = mcpPath .endsWith ("/" ) ? mcpPath + "sse" : mcpPath + "/sse" ;
94- routes .produce (RouteBuildItem .newFrameworkRoute (ssePath )
95- .withRequestHandler (recorder .createSseEndpointHandler (mcpPath , serverName ))
73+ routes .produce (RouteBuildItem .newFrameworkRoute (endpoint .ssePath )
74+ .withRequestHandler (recorder .createSseEndpointHandler (endpoint .mcpPath , endpoint .serverName ))
9675 .build ());
97- routes .produce (RouteBuildItem .newFrameworkRoute (mcpPath + "/" + "messages/:id" )
76+ routes .produce (RouteBuildItem .newFrameworkRoute (endpoint . mcpPath + "/" + "messages/:id" )
9877 .withRouteCustomizer (recorder .addBodyHandler (bodyHandler .getHandler ()))
99- .withRequestHandler (recorder .createMessagesEndpointHandler (serverName ))
78+ .withRequestHandler (recorder .createMessagesEndpointHandler (endpoint . serverName ))
10079 .build ());
101-
102- endpoints .add (new McpServerEndpoints (serverName , mcpPath , ssePath ));
10380 }
81+ }
10482
105- // Create synthetic observers for HttpServerStart and HttpsServerStart
106- // so that the info is logged after the server is started
107- ObserverConfigurator httpStartConfigurator = observerRegistrationPhase .getContext ()
108- .configure ()
109- .async (true )
110- .beanClass (DotName .createSimple (SseMcpServerRecorder .class ))
111- .observedType (HttpServerStart .class )
112- .notify (mc -> logMcpServerEndpoints (HttpServerStart .class , mc , endpoints ));
113- ObserverConfigurator httpsStartConfigurator = observerRegistrationPhase .getContext ()
114- .configure ()
115- .async (true )
116- .beanClass (DotName .createSimple (SseMcpServerRecorder .class ))
117- .observedType (HttpsServerStart .class )
118- .notify (mc -> logMcpServerEndpoints (HttpsServerStart .class , mc , endpoints ));
119- observers .produce (new ObserverConfiguratorBuildItem (httpStartConfigurator , httpsStartConfigurator ));
83+ @ Record (STATIC_INIT )
84+ @ BuildStep
85+ void registerMcpEndpointsBean (McpServerEndpointsBuildItem mcpServerEndpoints , SseMcpServerRecorder recorder ,
86+ BuildProducer <SyntheticBeanBuildItem > syntheticBeans ) {
87+ syntheticBeans .produce (SyntheticBeanBuildItem .configure (McpServerEndpoints .class )
88+ .scope (Singleton .class )
89+ .createWith (recorder .createMcpServerEndpoints (mcpServerEndpoints .getEndpoints ()))
90+ .done ());
12091 }
12192
122- private void logMcpServerEndpoints (Class <?> eventType , MethodCreator mc , List <McpServerEndpoints > endpoints ) {
123- ResultHandle event = mc .invokeInterfaceMethod (MethodDescriptor .ofMethod (EventContext .class , "getEvent" , Object .class ),
124- mc .getMethodParam (0 ));
125- ResultHandle httpServerOptions = mc .invokeVirtualMethod (
126- MethodDescriptor .ofMethod (eventType , "options" , HttpServerOptions .class ),
127- event );
128- ResultHandle list = Gizmo .newArrayList (mc );
129- for (McpServerEndpoints e : endpoints ) {
130- ResultHandle mcpe = mc .newInstance (MethodDescriptor .ofConstructor (McpServerEndpoints .class ,
131- String .class , String .class , String .class ), mc .load (e .serverName ), mc .load (e .mcpPath ),
132- mc .load (e .ssePath ));
133- Gizmo .listOperations (mc ).on (list ).add (mcpe );
93+ @ BuildStep
94+ McpServerEndpointsBuildItem collectMcpServerEndpoints (McpSseServersBuildTimeConfig config ,
95+ HttpRootPathBuildItem httpRootPath ) {
96+ List <McpServerEndpoint > endpoints = new ArrayList <>();
97+ Set <String > rootPaths = new HashSet <>();
98+ for (Map .Entry <String , McpSseServerBuildTimeConfig > e : config .servers ().entrySet ()) {
99+ String serverName = e .getKey ();
100+ String rootPath = e .getValue ().sse ().rootPath ();
101+ if (!rootPaths .add (rootPath )) {
102+ throw new IllegalStateException ("Multiple server configurations define the same root path: " + rootPath );
103+ }
104+ // Streamable HTTP transport, by default /mcp
105+ String mcpPath = httpRootPath .relativePath (rootPath );
106+ // SSE/HTTP transport, by default /mcp/sse
107+ String ssePath = mcpPath .endsWith ("/" ) ? mcpPath + "sse" : mcpPath + "/sse" ;
108+ endpoints .add (new McpServerEndpoint (serverName , mcpPath , ssePath ));
134109 }
135- mc .invokeStaticMethod (
136- MethodDescriptor .ofMethod (SseMcpServerRecorder .class , "logEndpoints" , void .class , List .class ,
137- HttpServerOptions .class ),
138- list , httpServerOptions );
139- mc .returnVoid ();
110+ return new McpServerEndpointsBuildItem (endpoints );
140111 }
141112
142113}
0 commit comments