1616
1717package org .springframework .ai .autoconfigure .mcp .server ;
1818
19+ import com .fasterxml .jackson .core .type .TypeReference ;
20+ import io .modelcontextprotocol .client .McpSyncClient ;
1921import io .modelcontextprotocol .server .McpAsyncServer ;
22+ import io .modelcontextprotocol .server .McpServerFeatures .AsyncToolRegistration ;
23+ import io .modelcontextprotocol .server .McpServerFeatures .SyncToolRegistration ;
24+ import io .modelcontextprotocol .server .McpServerFeatures .SyncResourceRegistration ;
25+ import io .modelcontextprotocol .server .McpServerFeatures .SyncPromptRegistration ;
2026import io .modelcontextprotocol .server .McpSyncServer ;
2127import io .modelcontextprotocol .server .transport .StdioServerTransport ;
28+ import io .modelcontextprotocol .spec .McpSchema ;
2229import io .modelcontextprotocol .spec .ServerMcpTransport ;
30+ import org .mockito .Mockito ;
2331import org .junit .jupiter .api .Test ;
24-
32+ import org .springframework .ai .mcp .SyncMcpToolCallback ;
33+ import org .springframework .ai .tool .ToolCallback ;
2534import org .springframework .boot .autoconfigure .AutoConfigurations ;
2635import org .springframework .boot .test .context .runner .ApplicationContextRunner ;
36+ import org .springframework .context .annotation .Bean ;
37+ import org .springframework .context .annotation .Configuration ;
38+ import reactor .core .publisher .Mono ;
39+
40+ import java .util .List ;
41+ import java .util .function .Consumer ;
42+ import java .util .function .Function ;
2743
2844import static org .assertj .core .api .Assertions .assertThat ;
2945
@@ -65,6 +81,36 @@ void asyncConfiguration() {
6581 });
6682 }
6783
84+ @ Test
85+ void transportConfiguration () {
86+ this .contextRunner .withUserConfiguration (CustomTransportConfiguration .class ).run (context -> {
87+ assertThat (context ).hasSingleBean (ServerMcpTransport .class );
88+ assertThat (context .getBean (ServerMcpTransport .class )).isInstanceOf (CustomServerTransport .class );
89+ });
90+ }
91+
92+ @ Test
93+ void serverNotificationConfiguration () {
94+ this .contextRunner
95+ .withPropertyValues ("spring.ai.mcp.server.tool-change-notification=false" ,
96+ "spring.ai.mcp.server.resource-change-notification=false" )
97+ .run (context -> {
98+ McpServerProperties properties = context .getBean (McpServerProperties .class );
99+ assertThat (properties .isToolChangeNotification ()).isFalse ();
100+ assertThat (properties .isResourceChangeNotification ()).isFalse ();
101+ });
102+ }
103+
104+ // @Test
105+ void invalidConfigurationThrowsException () {
106+ this .contextRunner .withPropertyValues ("spring.ai.mcp.server.version=invalid-version" ).run (context -> {
107+ assertThat (context ).hasFailed ();
108+ assertThat (context ).getFailure ()
109+ .hasRootCauseInstanceOf (IllegalArgumentException .class )
110+ .hasMessageContaining ("Invalid version format" );
111+ });
112+ }
113+
68114 @ Test
69115 void disabledConfiguration () {
70116 this .contextRunner .withPropertyValues ("spring.ai.mcp.server.enabled=false" ).run (context -> {
@@ -88,4 +134,178 @@ void notificationConfiguration() {
88134 });
89135 }
90136
137+ @ Test
138+ void stdioConfiguration () {
139+ this .contextRunner .withPropertyValues ("spring.ai.mcp.server.stdio=true" ).run (context -> {
140+ McpServerProperties properties = context .getBean (McpServerProperties .class );
141+ assertThat (properties .isStdio ()).isTrue ();
142+ });
143+ }
144+
145+ @ Test
146+ void serverCapabilitiesConfiguration () {
147+ this .contextRunner .run (context -> {
148+ assertThat (context ).hasSingleBean (McpSchema .ServerCapabilities .Builder .class );
149+ McpSchema .ServerCapabilities .Builder builder = context .getBean (McpSchema .ServerCapabilities .Builder .class );
150+ assertThat (builder ).isNotNull ();
151+ });
152+ }
153+
154+ @ Test
155+ void toolRegistrationConfiguration () {
156+ this .contextRunner .withUserConfiguration (TestToolConfiguration .class ).run (context -> {
157+ List <SyncToolRegistration > tools = context .getBean ("syncTools" , List .class );
158+ assertThat (tools ).hasSize (1 );
159+ });
160+ }
161+
162+ @ Test
163+ void resourceRegistrationConfiguration () {
164+ this .contextRunner .withUserConfiguration (TestResourceConfiguration .class ).run (context -> {
165+ McpSyncServer server = context .getBean (McpSyncServer .class );
166+ assertThat (server ).isNotNull ();
167+ });
168+ }
169+
170+ @ Test
171+ void promptRegistrationConfiguration () {
172+ this .contextRunner .withUserConfiguration (TestPromptConfiguration .class ).run (context -> {
173+ McpSyncServer server = context .getBean (McpSyncServer .class );
174+ assertThat (server ).isNotNull ();
175+ });
176+ }
177+
178+ @ Test
179+ void asyncToolRegistrationConfiguration () {
180+ this .contextRunner .withPropertyValues ("spring.ai.mcp.server.type=ASYNC" )
181+ .withUserConfiguration (TestToolConfiguration .class )
182+ .run (context -> {
183+ List <AsyncToolRegistration > tools = context .getBean ("asyncTools" , List .class );
184+ assertThat (tools ).hasSize (1 );
185+ });
186+ }
187+
188+ @ Test
189+ void customCapabilitiesBuilder () {
190+ this .contextRunner .withUserConfiguration (CustomCapabilitiesConfiguration .class ).run (context -> {
191+ assertThat (context ).hasSingleBean (McpSchema .ServerCapabilities .Builder .class );
192+ assertThat (context .getBean (McpSchema .ServerCapabilities .Builder .class ))
193+ .isInstanceOf (CustomCapabilitiesBuilder .class );
194+ });
195+ }
196+
197+ @ Test
198+ void rootsChangeConsumerConfiguration () {
199+ this .contextRunner .withUserConfiguration (TestRootsChangeConfiguration .class ).run (context -> {
200+ McpSyncServer server = context .getBean (McpSyncServer .class );
201+ assertThat (server ).isNotNull ();
202+ });
203+ }
204+
205+ @ Configuration
206+ static class TestResourceConfiguration {
207+
208+ @ Bean
209+ List <SyncResourceRegistration > testResources () {
210+ return List .of ();
211+ }
212+
213+ }
214+
215+ @ Configuration
216+ static class TestPromptConfiguration {
217+
218+ @ Bean
219+ List <SyncPromptRegistration > testPrompts () {
220+ return List .of ();
221+ }
222+
223+ }
224+
225+ @ Configuration
226+ static class CustomCapabilitiesConfiguration {
227+
228+ @ Bean
229+ McpSchema .ServerCapabilities .Builder customCapabilitiesBuilder () {
230+ return new CustomCapabilitiesBuilder ();
231+ }
232+
233+ }
234+
235+ static class CustomCapabilitiesBuilder extends McpSchema .ServerCapabilities .Builder {
236+
237+ // Custom implementation for testing
238+
239+ }
240+
241+ @ Configuration
242+ static class TestToolConfiguration {
243+
244+ @ Bean
245+ List <ToolCallback > testTool () {
246+ McpSyncClient mockClient = Mockito .mock (McpSyncClient .class );
247+ McpSchema .Tool mockTool = Mockito .mock (McpSchema .Tool .class );
248+ McpSchema .CallToolResult mockResult = Mockito .mock (McpSchema .CallToolResult .class );
249+
250+ Mockito .when (mockTool .name ()).thenReturn ("test-tool" );
251+ Mockito .when (mockTool .description ()).thenReturn ("Test Tool" );
252+ Mockito .when (mockClient .callTool (Mockito .any (McpSchema .CallToolRequest .class ))).thenReturn (mockResult );
253+
254+ return List .of (new SyncMcpToolCallback (mockClient , mockTool ));
255+ }
256+
257+ }
258+
259+ @ Configuration
260+ static class TestRootsChangeConfiguration {
261+
262+ @ Bean
263+ Consumer <List <McpSchema .Root >> rootsChangeConsumer () {
264+ return roots -> {
265+ // Test implementation
266+ };
267+ }
268+
269+ }
270+
271+ static class CustomServerTransport implements ServerMcpTransport {
272+
273+ @ Override
274+ public Mono <Void > connect (
275+ Function <Mono <McpSchema .JSONRPCMessage >, Mono <McpSchema .JSONRPCMessage >> messageHandler ) {
276+ return Mono .empty (); // Test implementation
277+ }
278+
279+ @ Override
280+ public Mono <Void > sendMessage (McpSchema .JSONRPCMessage message ) {
281+ return Mono .empty (); // Test implementation
282+ }
283+
284+ @ Override
285+ public <T > T unmarshalFrom (Object value , TypeReference <T > type ) {
286+ return null ; // Test implementation
287+ }
288+
289+ @ Override
290+ public void close () {
291+ // Test implementation
292+ }
293+
294+ @ Override
295+ public Mono <Void > closeGracefully () {
296+ return Mono .empty (); // Test implementation
297+ }
298+
299+ }
300+
301+ @ Configuration
302+ static class CustomTransportConfiguration {
303+
304+ @ Bean
305+ ServerMcpTransport customTransport () {
306+ return new CustomServerTransport ();
307+ }
308+
309+ }
310+
91311}
0 commit comments