Restructure

Signed-off-by: Christian Tzolov <christian.tzolov@broadcom.com>
This commit is contained in:
Christian Tzolov
2025-02-09 22:05:35 +01:00
parent ab571f059a
commit a29ea2b743
110 changed files with 2013 additions and 768 deletions

58
mcp.servlet-server.log Normal file
View File

@@ -0,0 +1,58 @@
2025-02-09T21:26:38.153+01:00 INFO 95924 --- [main] .s.a.m.s.s.s.McpServletServerApplication : Starting McpServletServerApplication using Java 17.0.12 with PID 95924 (/Users/christiantzolov/Dev/projects/spring-ai-examples/model-context-protocol/book-library/manual-servlet-server/target/classes started by christiantzolov in /Users/christiantzolov/Dev/projects/spring-ai-examples)
2025-02-09T21:26:38.154+01:00 INFO 95924 --- [main] .s.a.m.s.s.s.McpServletServerApplication : No active profile set, falling back to 1 default profile: "default"
2025-02-09T21:26:38.468+01:00 INFO 95924 --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port 8080 (http)
2025-02-09T21:26:38.473+01:00 INFO 95924 --- [main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2025-02-09T21:26:38.473+01:00 INFO 95924 --- [main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.33]
2025-02-09T21:26:38.493+01:00 INFO 95924 --- [main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2025-02-09T21:26:38.493+01:00 INFO 95924 --- [main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 319 ms
2025-02-09T21:26:38.711+01:00 INFO 95924 --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8080 (http) with context path '/'
2025-02-09T21:26:38.715+01:00 INFO 95924 --- [main] .s.a.m.s.s.s.McpServletServerApplication : Started McpServletServerApplication in 0.731 seconds (process running for 0.85)
2025-02-09T21:26:43.707+01:00 INFO 95924 --- [http-nio-8080-exec-2] i.m.spec.DefaultMcpSession : Received request: JSONRPCRequest[jsonrpc=2.0, method=initialize, id=6c0682de-0, params={protocolVersion=2024-11-05, capabilities={}, clientInfo={name=Java SDK MCP Client, version=1.0.0}}]
2025-02-09T21:26:43.711+01:00 INFO 95924 --- [http-nio-8080-exec-2] i.m.server.McpAsyncServer : Client initialize request - Protocol: 2024-11-05, Capabilities: ClientCapabilities[experimental=null, roots=null, sampling=null], Info: Implementation[name=Java SDK MCP Client, version=1.0.0]
2025-02-09T21:26:43.756+01:00 INFO 95924 --- [http-nio-8080-exec-3] i.m.spec.DefaultMcpSession : Received notification: JSONRPCNotification[jsonrpc=2.0, method=notifications/initialized, params=null]
2025-02-09T21:26:43.758+01:00 INFO 95924 --- [http-nio-8080-exec-4] i.m.spec.DefaultMcpSession : Received request: JSONRPCRequest[jsonrpc=2.0, method=ping, id=6c0682de-1, params=null]
2025-02-09T21:26:43.760+01:00 INFO 95924 --- [http-nio-8080-exec-5] i.m.spec.DefaultMcpSession : Received request: JSONRPCRequest[jsonrpc=2.0, method=tools/list, id=6c0682de-2, params={}]
2025-02-09T21:26:43.770+01:00 INFO 95924 --- [http-nio-8080-exec-6] i.m.spec.DefaultMcpSession : Received request: JSONRPCRequest[jsonrpc=2.0, method=tools/call, id=6c0682de-3, params={name=toUpperCase, arguments={input=accountName}}]
2025-02-09T21:26:43.794+01:00 INFO 95924 --- [http-nio-8080-exec-7] i.m.spec.DefaultMcpSession : Received request: JSONRPCRequest[jsonrpc=2.0, method=tools/call, id=6c0682de-4, params={name=getBooks, arguments={title=Spring Framework}}]
2025-02-09T21:26:44.908+01:00 INFO 95924 --- [http-nio-8080-exec-8] i.m.spec.DefaultMcpSession : Received request: JSONRPCRequest[jsonrpc=2.0, method=resources/list, id=6c0682de-5, params={}]
2025-02-09T21:26:44.917+01:00 INFO 95924 --- [http-nio-8080-exec-9] i.m.spec.DefaultMcpSession : Received request: JSONRPCRequest[jsonrpc=2.0, method=resources/read, id=6c0682de-6, params={uri=system://info}]
2025-02-09T21:26:44.927+01:00 INFO 95924 --- [http-nio-8080-exec-10] i.m.spec.DefaultMcpSession : Received request: JSONRPCRequest[jsonrpc=2.0, method=prompts/list, id=6c0682de-7, params={}]
2025-02-09T21:26:44.934+01:00 INFO 95924 --- [http-nio-8080-exec-1] i.m.spec.DefaultMcpSession : Received request: JSONRPCRequest[jsonrpc=2.0, method=prompts/get, id=6c0682de-8, params={name=greeting, arguments={name=Spring}}]
2025-02-09T21:26:46.642+01:00 ERROR 95924 --- [SpringApplicationShutdownHook] reactor.core.publisher.Operators : Operator called default onErrorDropped
reactor.core.Exceptions$ErrorCallbackNotImplemented: java.lang.IllegalStateException: The request associated with the AsyncContext has already completed processing.
Caused by: java.lang.IllegalStateException: The request associated with the AsyncContext has already completed processing.
at org.apache.catalina.core.AsyncContextImpl.check(AsyncContextImpl.java:529) ~[tomcat-embed-core-10.1.33.jar:10.1.33]
at org.apache.catalina.core.AsyncContextImpl.complete(AsyncContextImpl.java:92) ~[tomcat-embed-core-10.1.33.jar:10.1.33]
at io.modelcontextprotocol.server.transport.HttpServletSseServerTransport.removeSession(HttpServletSseServerTransport.java:379) ~[mcp-0.7.0-SNAPSHOT.jar:0.7.0-SNAPSHOT]
at java.base/java.util.concurrent.ConcurrentHashMap$ValuesView.forEach(ConcurrentHashMap.java:4780) ~[na:na]
at io.modelcontextprotocol.server.transport.HttpServletSseServerTransport.lambda$closeGracefully$4(HttpServletSseServerTransport.java:351) ~[mcp-0.7.0-SNAPSHOT.jar:0.7.0-SNAPSHOT]
at reactor.core.publisher.MonoCreate.subscribe(MonoCreate.java:61) ~[reactor-core-3.6.12.jar:3.6.12]
at reactor.core.publisher.Mono.subscribe(Mono.java:4576) ~[reactor-core-3.6.12.jar:3.6.12]
at reactor.core.publisher.Mono.subscribeWith(Mono.java:4642) ~[reactor-core-3.6.12.jar:3.6.12]
at reactor.core.publisher.Mono.subscribe(Mono.java:4403) ~[reactor-core-3.6.12.jar:3.6.12]
at io.modelcontextprotocol.spec.McpTransport.close(McpTransport.java:61) ~[mcp-0.7.0-SNAPSHOT.jar:0.7.0-SNAPSHOT]
at io.modelcontextprotocol.server.transport.HttpServletSseServerTransport.close(HttpServletSseServerTransport.java:323) ~[mcp-0.7.0-SNAPSHOT.jar:0.7.0-SNAPSHOT]
at io.modelcontextprotocol.spec.DefaultMcpSession.close(DefaultMcpSession.java:283) ~[mcp-0.7.0-SNAPSHOT.jar:0.7.0-SNAPSHOT]
at io.modelcontextprotocol.server.McpAsyncServer.close(McpAsyncServer.java:255) ~[mcp-0.7.0-SNAPSHOT.jar:0.7.0-SNAPSHOT]
at io.modelcontextprotocol.server.McpSyncServer.close(McpSyncServer.java:205) ~[mcp-0.7.0-SNAPSHOT.jar:0.7.0-SNAPSHOT]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
at org.springframework.beans.factory.support.DisposableBeanAdapter.invokeCustomDestroyMethod(DisposableBeanAdapter.java:316) ~[spring-beans-6.1.15.jar:6.1.15]
at org.springframework.beans.factory.support.DisposableBeanAdapter.destroy(DisposableBeanAdapter.java:249) ~[spring-beans-6.1.15.jar:6.1.15]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.destroyBean(DefaultSingletonBeanRegistry.java:587) ~[spring-beans-6.1.15.jar:6.1.15]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.destroySingleton(DefaultSingletonBeanRegistry.java:559) ~[spring-beans-6.1.15.jar:6.1.15]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.destroySingleton(DefaultListableBeanFactory.java:1202) ~[spring-beans-6.1.15.jar:6.1.15]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.destroySingletons(DefaultSingletonBeanRegistry.java:520) ~[spring-beans-6.1.15.jar:6.1.15]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.destroySingletons(DefaultListableBeanFactory.java:1195) ~[spring-beans-6.1.15.jar:6.1.15]
at org.springframework.context.support.AbstractApplicationContext.destroyBeans(AbstractApplicationContext.java:1195) ~[spring-context-6.1.15.jar:6.1.15]
at org.springframework.context.support.AbstractApplicationContext.doClose(AbstractApplicationContext.java:1156) ~[spring-context-6.1.15.jar:6.1.15]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.doClose(ServletWebServerApplicationContext.java:174) ~[spring-boot-3.3.6.jar:3.3.6]
at org.springframework.context.support.AbstractApplicationContext.close(AbstractApplicationContext.java:1102) ~[spring-context-6.1.15.jar:6.1.15]
at org.springframework.boot.SpringApplicationShutdownHook.closeAndWait(SpringApplicationShutdownHook.java:145) ~[spring-boot-3.3.6.jar:3.3.6]
at java.base/java.lang.Iterable.forEach(Iterable.java:75) ~[na:na]
at org.springframework.boot.SpringApplicationShutdownHook.run(SpringApplicationShutdownHook.java:114) ~[spring-boot-3.3.6.jar:3.3.6]
at java.base/java.lang.Thread.run(Thread.java:842) ~[na:na]

50
mcp.webflux.log Normal file
View File

@@ -0,0 +1,50 @@
2025-02-09T21:25:36.624+01:00 INFO 95283 --- [main] o.s.a.m.s.server.McpServerApplication : Starting McpServerApplication using Java 17.0.12 with PID 95283 (/Users/christiantzolov/Dev/projects/spring-ai-examples/model-context-protocol/book-library/manual-webflux-server/target/classes started by christiantzolov in /Users/christiantzolov/Dev/projects/spring-ai-examples)
2025-02-09T21:25:36.625+01:00 INFO 95283 --- [main] o.s.a.m.s.server.McpServerApplication : No active profile set, falling back to 1 default profile: "default"
2025-02-09T21:25:38.595+01:00 WARN 95283 --- [main] o.s.c.support.DefaultLifecycleProcessor : Failed to stop bean 'reactorResourceFactory'
reactor.core.Exceptions$ReactiveException: java.lang.InterruptedException
at reactor.core.Exceptions.propagate(Exceptions.java:410) ~[reactor-core-3.6.12.jar:3.6.12]
at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:96) ~[reactor-core-3.6.12.jar:3.6.12]
at reactor.core.publisher.Mono.block(Mono.java:1779) ~[reactor-core-3.6.12.jar:3.6.12]
at org.springframework.http.client.ReactorResourceFactory.stop(ReactorResourceFactory.java:298) ~[spring-web-6.1.15.jar:6.1.15]
at org.springframework.context.SmartLifecycle.stop(SmartLifecycle.java:117) ~[spring-context-6.1.15.jar:6.1.15]
at org.springframework.context.support.DefaultLifecycleProcessor.doStop(DefaultLifecycleProcessor.java:346) ~[spring-context-6.1.15.jar:6.1.15]
at org.springframework.context.support.DefaultLifecycleProcessor$LifecycleGroup.stop(DefaultLifecycleProcessor.java:488) ~[spring-context-6.1.15.jar:6.1.15]
at java.base/java.lang.Iterable.forEach(Iterable.java:75) ~[na:na]
at org.springframework.context.support.DefaultLifecycleProcessor.stopBeans(DefaultLifecycleProcessor.java:315) ~[spring-context-6.1.15.jar:6.1.15]
at org.springframework.context.support.DefaultLifecycleProcessor.onRefresh(DefaultLifecycleProcessor.java:207) ~[spring-context-6.1.15.jar:6.1.15]
at org.springframework.context.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:990) ~[spring-context-6.1.15.jar:6.1.15]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:628) ~[spring-context-6.1.15.jar:6.1.15]
at org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext.refresh(ReactiveWebServerApplicationContext.java:66) ~[spring-boot-3.3.6.jar:3.3.6]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754) ~[spring-boot-3.3.6.jar:3.3.6]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:456) ~[spring-boot-3.3.6.jar:3.3.6]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:335) ~[spring-boot-3.3.6.jar:3.3.6]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1363) ~[spring-boot-3.3.6.jar:3.3.6]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1352) ~[spring-boot-3.3.6.jar:3.3.6]
at org.springframework.ai.mcp.sample.server.McpServerApplication.main(McpServerApplication.java:10) ~[classes/:na]
Caused by: java.lang.InterruptedException: null
at java.base/java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1048) ~[na:na]
at java.base/java.util.concurrent.CountDownLatch.await(CountDownLatch.java:230) ~[na:na]
at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:91) ~[reactor-core-3.6.12.jar:3.6.12]
... 17 common frames omitted
2025-02-09T21:25:38.599+01:00 WARN 95283 --- [main] onfigReactiveWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.context.ApplicationContextException: Failed to start bean 'webServerStartStop'
2025-02-09T21:25:38.609+01:00 INFO 95283 --- [main] o.s.a.m.s.t.WebFluxSseServerTransport : Graceful shutdown completed
2025-02-09T21:25:38.610+01:00 INFO 95283 --- [main] o.s.a.m.s.t.WebFluxSseServerTransport : Graceful shutdown completed
2025-02-09T21:25:44.586+01:00 INFO 95411 --- [main] o.s.a.m.s.server.McpServerApplication : Starting McpServerApplication using Java 17.0.12 with PID 95411 (/Users/christiantzolov/Dev/projects/spring-ai-examples/model-context-protocol/book-library/manual-webflux-server/target/classes started by christiantzolov in /Users/christiantzolov/Dev/projects/spring-ai-examples)
2025-02-09T21:25:44.587+01:00 INFO 95411 --- [main] o.s.a.m.s.server.McpServerApplication : No active profile set, falling back to 1 default profile: "default"
2025-02-09T21:25:45.244+01:00 INFO 95411 --- [main] o.s.b.web.embedded.netty.NettyWebServer : Netty started on port 8080 (http)
2025-02-09T21:25:45.252+01:00 INFO 95411 --- [main] o.s.a.m.s.server.McpServerApplication : Started McpServerApplication in 0.833 seconds (process running for 0.968)
2025-02-09T21:25:49.434+01:00 INFO 95411 --- [reactor-http-nio-3] o.s.ai.mcp.spec.DefaultMcpSession : Received request: JSONRPCRequest[jsonrpc=2.0, method=initialize, id=12e95de7-0, params={protocolVersion=2024-11-05, capabilities={}, clientInfo={name=Spring AI MCP Client, version=1.0.0}}]
2025-02-09T21:25:49.438+01:00 INFO 95411 --- [reactor-http-nio-3] o.s.ai.mcp.server.McpAsyncServer : Client initialize request - Protocol: 2024-11-05, Capabilities: ClientCapabilities[experimental=null, roots=null, sampling=null], Info: Implementation[name=Spring AI MCP Client, version=1.0.0]
2025-02-09T21:25:49.488+01:00 INFO 95411 --- [reactor-http-nio-4] o.s.ai.mcp.spec.DefaultMcpSession : Received notification: JSONRPCNotification[jsonrpc=2.0, method=notifications/initialized, params=null]
2025-02-09T21:25:49.492+01:00 INFO 95411 --- [reactor-http-nio-3] o.s.ai.mcp.spec.DefaultMcpSession : Received request: JSONRPCRequest[jsonrpc=2.0, method=ping, id=12e95de7-1, params=null]
2025-02-09T21:25:49.496+01:00 INFO 95411 --- [reactor-http-nio-4] o.s.ai.mcp.spec.DefaultMcpSession : Received request: JSONRPCRequest[jsonrpc=2.0, method=tools/list, id=12e95de7-2, params={}]
2025-02-09T21:25:49.508+01:00 INFO 95411 --- [reactor-http-nio-3] o.s.ai.mcp.spec.DefaultMcpSession : Received request: JSONRPCRequest[jsonrpc=2.0, method=tools/call, id=12e95de7-3, params={name=toUpperCase, arguments={input=accountName}}]
2025-02-09T21:25:49.527+01:00 INFO 95411 --- [reactor-http-nio-4] o.s.ai.mcp.spec.DefaultMcpSession : Received request: JSONRPCRequest[jsonrpc=2.0, method=tools/call, id=12e95de7-4, params={name=getBooks, arguments={title=Spring Framework}}]
2025-02-09T21:25:50.384+01:00 INFO 95411 --- [reactor-http-nio-3] o.s.ai.mcp.spec.DefaultMcpSession : Received request: JSONRPCRequest[jsonrpc=2.0, method=resources/list, id=12e95de7-5, params={}]
2025-02-09T21:25:50.396+01:00 INFO 95411 --- [reactor-http-nio-4] o.s.ai.mcp.spec.DefaultMcpSession : Received request: JSONRPCRequest[jsonrpc=2.0, method=resources/read, id=12e95de7-6, params={uri=system://info}]
2025-02-09T21:25:50.408+01:00 INFO 95411 --- [reactor-http-nio-3] o.s.ai.mcp.spec.DefaultMcpSession : Received request: JSONRPCRequest[jsonrpc=2.0, method=prompts/list, id=12e95de7-7, params={}]
2025-02-09T21:25:50.417+01:00 INFO 95411 --- [reactor-http-nio-4] o.s.ai.mcp.spec.DefaultMcpSession : Received request: JSONRPCRequest[jsonrpc=2.0, method=prompts/get, id=12e95de7-8, params={name=greeting, arguments={name=Spring}}]
2025-02-09T21:25:54.383+01:00 INFO 95411 --- [SpringApplicationShutdownHook] o.s.a.m.s.t.WebFluxSseServerTransport : Graceful shutdown completed
2025-02-09T21:25:54.384+01:00 INFO 95411 --- [SpringApplicationShutdownHook] o.s.a.m.s.t.WebFluxSseServerTransport : Graceful shutdown completed

50
mcp.webmvc.log Normal file
View File

@@ -0,0 +1,50 @@
2025-02-09T21:21:11.760+01:00 INFO 93310 --- [main] o.s.a.m.s.w.s.McpMvcServerApplication : Starting McpMvcServerApplication using Java 17.0.12 with PID 93310 (/Users/christiantzolov/Dev/projects/spring-ai-examples/model-context-protocol/book-library/manual-webmvc-server/target/classes started by christiantzolov in /Users/christiantzolov/Dev/projects/spring-ai-examples)
2025-02-09T21:21:11.761+01:00 INFO 93310 --- [main] o.s.a.m.s.w.s.McpMvcServerApplication : No active profile set, falling back to 1 default profile: "default"
2025-02-09T21:21:12.089+01:00 INFO 93310 --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port 8080 (http)
2025-02-09T21:21:12.095+01:00 INFO 93310 --- [main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2025-02-09T21:21:12.095+01:00 INFO 93310 --- [main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.33]
2025-02-09T21:21:12.125+01:00 INFO 93310 --- [main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2025-02-09T21:21:12.125+01:00 INFO 93310 --- [main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 344 ms
2025-02-09T21:21:12.481+01:00 INFO 93310 --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8080 (http) with context path '/'
2025-02-09T21:21:12.487+01:00 INFO 93310 --- [main] o.s.a.m.s.w.s.McpMvcServerApplication : Started McpMvcServerApplication in 0.895 seconds (process running for 1.022)
2025-02-09T21:21:36.802+01:00 INFO 93310 --- [SpringApplicationShutdownHook] o.s.a.m.s.t.WebMvcSseServerTransport : Graceful shutdown completed
2025-02-09T21:21:36.803+01:00 INFO 93310 --- [SpringApplicationShutdownHook] o.s.a.m.s.t.WebMvcSseServerTransport : Graceful shutdown completed
2025-02-09T21:22:52.834+01:00 INFO 93938 --- [main] o.s.a.m.s.w.s.McpMvcServerApplication : Starting McpMvcServerApplication using Java 17.0.12 with PID 93938 (/Users/christiantzolov/Dev/projects/spring-ai-examples/model-context-protocol/book-library/manual-webmvc-server/target/classes started by christiantzolov in /Users/christiantzolov/Dev/projects/spring-ai-examples)
2025-02-09T21:22:52.835+01:00 INFO 93938 --- [main] o.s.a.m.s.w.s.McpMvcServerApplication : No active profile set, falling back to 1 default profile: "default"
2025-02-09T21:22:53.140+01:00 INFO 93938 --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port 8080 (http)
2025-02-09T21:22:53.145+01:00 INFO 93938 --- [main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2025-02-09T21:22:53.145+01:00 INFO 93938 --- [main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.33]
2025-02-09T21:22:53.167+01:00 INFO 93938 --- [main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2025-02-09T21:22:53.167+01:00 INFO 93938 --- [main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 313 ms
2025-02-09T21:22:53.484+01:00 INFO 93938 --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8080 (http) with context path '/'
2025-02-09T21:22:53.490+01:00 INFO 93938 --- [main] o.s.a.m.s.w.s.McpMvcServerApplication : Started McpMvcServerApplication in 0.82 seconds (process running for 0.943)
2025-02-09T21:23:04.979+01:00 INFO 93938 --- [SpringApplicationShutdownHook] o.s.a.m.s.t.WebMvcSseServerTransport : Graceful shutdown completed
2025-02-09T21:23:04.979+01:00 INFO 93938 --- [SpringApplicationShutdownHook] o.s.a.m.s.t.WebMvcSseServerTransport : Graceful shutdown completed
2025-02-09T21:24:24.073+01:00 INFO 94676 --- [main] o.s.a.m.s.w.s.McpMvcServerApplication : Starting McpMvcServerApplication using Java 17.0.12 with PID 94676 (/Users/christiantzolov/Dev/projects/spring-ai-examples/model-context-protocol/book-library/manual-webmvc-server/target/classes started by christiantzolov in /Users/christiantzolov/Dev/projects/spring-ai-examples)
2025-02-09T21:24:24.074+01:00 INFO 94676 --- [main] o.s.a.m.s.w.s.McpMvcServerApplication : No active profile set, falling back to 1 default profile: "default"
2025-02-09T21:24:24.425+01:00 INFO 94676 --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port 8080 (http)
2025-02-09T21:24:24.430+01:00 INFO 94676 --- [main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2025-02-09T21:24:24.431+01:00 INFO 94676 --- [main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.33]
2025-02-09T21:24:24.455+01:00 INFO 94676 --- [main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2025-02-09T21:24:24.456+01:00 INFO 94676 --- [main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 362 ms
2025-02-09T21:24:24.789+01:00 INFO 94676 --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8080 (http) with context path '/'
2025-02-09T21:24:24.795+01:00 INFO 94676 --- [main] o.s.a.m.s.w.s.McpMvcServerApplication : Started McpMvcServerApplication in 0.896 seconds (process running for 1.022)
2025-02-09T21:24:27.528+01:00 INFO 94676 --- [http-nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2025-02-09T21:24:27.529+01:00 INFO 94676 --- [http-nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2025-02-09T21:24:27.529+01:00 INFO 94676 --- [http-nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 0 ms
2025-02-09T21:24:27.578+01:00 INFO 94676 --- [http-nio-8080-exec-2] o.s.ai.mcp.spec.DefaultMcpSession : Received request: JSONRPCRequest[jsonrpc=2.0, method=initialize, id=1f83bbd8-0, params={protocolVersion=2024-11-05, capabilities={}, clientInfo={name=Spring AI MCP Client, version=1.0.0}}]
2025-02-09T21:24:27.582+01:00 INFO 94676 --- [http-nio-8080-exec-2] o.s.ai.mcp.server.McpAsyncServer : Client initialize request - Protocol: 2024-11-05, Capabilities: ClientCapabilities[experimental=null, roots=null, sampling=null], Info: Implementation[name=Spring AI MCP Client, version=1.0.0]
2025-02-09T21:24:27.630+01:00 INFO 94676 --- [http-nio-8080-exec-3] o.s.ai.mcp.spec.DefaultMcpSession : Received notification: JSONRPCNotification[jsonrpc=2.0, method=notifications/initialized, params=null]
2025-02-09T21:24:27.633+01:00 INFO 94676 --- [http-nio-8080-exec-4] o.s.ai.mcp.spec.DefaultMcpSession : Received request: JSONRPCRequest[jsonrpc=2.0, method=ping, id=1f83bbd8-1, params=null]
2025-02-09T21:24:27.637+01:00 INFO 94676 --- [http-nio-8080-exec-5] o.s.ai.mcp.spec.DefaultMcpSession : Received request: JSONRPCRequest[jsonrpc=2.0, method=tools/list, id=1f83bbd8-2, params={}]
2025-02-09T21:24:27.649+01:00 INFO 94676 --- [http-nio-8080-exec-6] o.s.ai.mcp.spec.DefaultMcpSession : Received request: JSONRPCRequest[jsonrpc=2.0, method=tools/call, id=1f83bbd8-3, params={name=toUpperCase, arguments={input=accountName}}]
2025-02-09T21:24:27.672+01:00 INFO 94676 --- [http-nio-8080-exec-7] o.s.ai.mcp.spec.DefaultMcpSession : Received request: JSONRPCRequest[jsonrpc=2.0, method=tools/call, id=1f83bbd8-4, params={name=getBooks, arguments={title=Spring Framework}}]
2025-02-09T21:24:28.635+01:00 INFO 94676 --- [http-nio-8080-exec-8] o.s.ai.mcp.spec.DefaultMcpSession : Received request: JSONRPCRequest[jsonrpc=2.0, method=resources/list, id=1f83bbd8-5, params={}]
2025-02-09T21:24:28.645+01:00 INFO 94676 --- [http-nio-8080-exec-9] o.s.ai.mcp.spec.DefaultMcpSession : Received request: JSONRPCRequest[jsonrpc=2.0, method=resources/read, id=1f83bbd8-6, params={uri=system://info}]
2025-02-09T21:24:28.657+01:00 INFO 94676 --- [http-nio-8080-exec-10] o.s.ai.mcp.spec.DefaultMcpSession : Received request: JSONRPCRequest[jsonrpc=2.0, method=prompts/list, id=1f83bbd8-7, params={}]
2025-02-09T21:24:28.665+01:00 INFO 94676 --- [http-nio-8080-exec-1] o.s.ai.mcp.spec.DefaultMcpSession : Received request: JSONRPCRequest[jsonrpc=2.0, method=prompts/get, id=1f83bbd8-8, params={name=greeting, arguments={name=Spring}}]
2025-02-09T21:24:57.797+01:00 WARN 94676 --- [http-nio-8080-exec-3] .w.s.m.s.DefaultHandlerExceptionResolver : Ignoring exception, response committed already: org.springframework.web.context.request.async.AsyncRequestTimeoutException
2025-02-09T21:24:57.798+01:00 WARN 94676 --- [http-nio-8080-exec-3] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.context.request.async.AsyncRequestTimeoutException]
2025-02-09T21:25:37.487+01:00 WARN 94676 --- [SpringApplicationShutdownHook] o.s.a.m.s.t.WebMvcSseServerTransport : Failed to complete SSE emitter for session 76fdc444-38d2-4884-9e75-dc747611b756: The response object has been recycled and is no longer associated with this facade
2025-02-09T21:25:37.487+01:00 INFO 94676 --- [SpringApplicationShutdownHook] o.s.a.m.s.t.WebMvcSseServerTransport : Graceful shutdown completed
2025-02-09T21:25:37.487+01:00 INFO 94676 --- [SpringApplicationShutdownHook] o.s.a.m.s.t.WebMvcSseServerTransport : Graceful shutdown completed

View File

@@ -1,147 +0,0 @@
# Spring AI Model Context Protocol (MCP) Examples
This directory contains various examples demonstrating the usage of Spring AI's Model Context Protocol (MCP). Each example showcases different aspects of MCP implementation, including various transport methods and client-server configurations.
## Transport Types
The examples demonstrate two main types of transport:
1. **STDIO Transport**
- Process-based communication
- Synchronous communication
- Used in all client examples
- Available in all server implementations
2. **HTTP SSE (Server-Sent Events) Transport**
- HTTP-based streaming communication
- Asynchronous communication
- Available in server implementations
- Implemented in three variants:
- Servlet-based (Spring MVC)
- WebFlux-based (Reactive)
- WebMVC-based
## API Types
The examples demonstrate both synchronous and asynchronous API usage:
- **Synchronous API**: Used in STDIO transport implementations
- **Asynchronous API**: Used in SSE transport implementations
## Getting Started
Each example project can be built using Maven:
```bash
./mvnw clean install
```
For running the examples:
1. For STDIO transport: Run the client application directly
2. For SSE transport: Start the server first, then run the client
## Note
- All server implementations support both STDIO and SSE transport modes
- Transport mode can be configured using the `transport.mode` property
- Client examples primarily use STDIO transport for simplicity
- Server starters provide auto-configuration support for easier integration
## Example Projects Overview
### Current Implementations
These projects use current Spring AI MCP dependencies:
#### Spring Boot Starter Projects
These use the `spring-ai-mcp-spring-boot-starter` dependency:
#### `brave-chatbot`
- **Type**: Client
- **Transport**: STDIO
- **Framework**: Spring Boot with MCP Starter
- **Description**: Enhanced version of the Brave client with chatbot capabilities.
#### `brave-starter`
- **Type**: Client Starter
- **Transport**: STDIO
- **Framework**: Spring Boot with MCP Starter
- **Description**: Spring Boot starter version of the Brave client.
#### `mcp-webflux-server-starter`
- **Type**: Server Starter
- **Transport**: Supports both HTTP SSE and STDIO
- **Framework**: Spring WebFlux with MCP Starter
- **Description**: Spring Boot starter for WebFlux server with auto-configuration support.
#### `mcp-weather-server-quickstart`
- **Type**: Server Quickstart
- **Transport**: Supports both HTTP SSE and STDIO
- **Framework**: Spring Boot with MCP Starter
- **Description**: Simplified version of the weather server for quick start purposes.
#### `mcp-weather-server-starter`
- **Type**: Server Starter
- **Transport**: Supports both HTTP SSE and STDIO
- **Framework**: Spring Boot with MCP Starter
- **Description**: Spring Boot starter version of the weather server, with auto-configuration support.
#### Manual Configuration Examples
These demonstrate how to create MCP applications without Spring Boot auto-configuration:
#### `mcp-weather-server`
- **Type**: Server
- **Transport**: Supports both HTTP SSE and STDIO
- **Framework**: Spring Boot with WebFlux
- **Dependencies**: Uses `spring-ai-bom` and `mcp-bom` for dependency management
- **Description**: Weather service implementation showing how to manually configure an MCP application without using spring-boot-starter. Demonstrates manual configuration patterns while maintaining clean dependency management through BOMs.
### Legacy Projects (Using Experimental Dependency)
These projects use the experimental dependency `org.springframework.experimental:spring-ai-mcp:0.6.0` and need updating:
#### Server Implementations
#### `mcp-servlet-server`
- **Type**: Server
- **Transport**: Supports both HTTP SSE and STDIO
- **Framework**: Spring MVC
- **Status**: Uses experimental dependency
- **Description**: Demonstrates a server implementation using Spring MVC with servlet-based SSE transport. Includes OpenLibrary integration.
#### `mcp-webflux-server`
- **Type**: Server
- **Transport**: Supports both HTTP SSE and STDIO
- **Framework**: Spring WebFlux
- **Status**: Uses experimental dependency
- **Description**: Shows a reactive server implementation using Spring WebFlux with SSE transport. Includes OpenLibrary integration.
#### `mcp-webmvc-server`
- **Type**: Server
- **Transport**: Supports both HTTP SSE and STDIO
- **Framework**: Spring WebMVC
- **Status**: Uses experimental dependency
- **Description**: Another server implementation using Spring WebMVC, demonstrating SSE transport integration.
#### Client Examples
#### `brave`
- **Type**: Client
- **Transport**: STDIO
- **Framework**: Spring Boot
- **Status**: Uses experimental dependency
- **Description**: Client implementation for Brave Search integration.
#### `filesystem`
- **Type**: Client
- **Transport**: STDIO
- **Framework**: Spring Boot
- **Status**: Uses experimental dependency
- **Description**: Example showing filesystem operations through MCP.
#### `sqlite`
- **Type**: Client
- **Transport**: STDIO
- **Framework**: Spring Boot
- **Status**: Uses experimental dependency
- **Description**: Demonstrates SQLite database integration through MCP (includes both simple and chatbot variants).

View File

@@ -18,15 +18,15 @@
<description>Sample application demonstrating MCP Servlet server usage</description>
<properties>
<spring-ai-mcp.version>0.6.0</spring-ai-mcp.version>
<sprign-ai.version>1.0.0-SNAPSHOT</sprign-ai.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.experimental</groupId>
<artifactId>mcp-bom</artifactId>
<version>${spring-ai-mcp.version}</version>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${sprign-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@@ -35,7 +35,7 @@
<dependencies>
<dependency>
<groupId>org.springframework.experimental</groupId>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp</artifactId>
</dependency>
@@ -43,12 +43,6 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
@@ -56,27 +50,6 @@
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>io.spring.javaformat</groupId>
<artifactId>spring-javaformat-maven-plugin</artifactId>
<version>0.0.43</version>
<executions>
<execution>
<phase>validate</phase>
<inherited>true</inherited>
<goals>
<goal>validate</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

View File

@@ -5,29 +5,30 @@ import java.util.Map;
import java.util.function.Function;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.modelcontextprotocol.server.McpServer;
import io.modelcontextprotocol.server.McpServerFeatures.SyncPromptRegistration;
import io.modelcontextprotocol.server.McpServerFeatures.SyncResourceRegistration;
import io.modelcontextprotocol.server.McpServerFeatures.SyncToolRegistration;
import io.modelcontextprotocol.server.McpSyncServer;
import io.modelcontextprotocol.server.transport.HttpServletSseServerTransport;
import io.modelcontextprotocol.server.transport.StdioServerTransport;
import io.modelcontextprotocol.spec.McpSchema;
import io.modelcontextprotocol.spec.McpSchema.GetPromptResult;
import io.modelcontextprotocol.spec.McpSchema.PromptMessage;
import io.modelcontextprotocol.spec.McpSchema.Role;
import io.modelcontextprotocol.spec.McpSchema.TextContent;
import io.modelcontextprotocol.spec.ServerMcpTransport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.mcp.server.McpServer;
import org.springframework.ai.mcp.server.McpServerFeatures.SyncPromptRegistration;
import org.springframework.ai.mcp.server.McpServerFeatures.SyncResourceRegistration;
import org.springframework.ai.mcp.server.McpServerFeatures.SyncToolRegistration;
import org.springframework.ai.mcp.server.McpSyncServer;
import org.springframework.ai.mcp.server.transport.HttpServletSseServerTransport;
import org.springframework.ai.mcp.server.transport.StdioServerTransport;
import org.springframework.ai.mcp.spec.McpSchema;
import org.springframework.ai.mcp.spec.McpSchema.GetPromptResult;
import org.springframework.ai.mcp.spec.McpSchema.PromptMessage;
import org.springframework.ai.mcp.spec.McpSchema.Role;
import org.springframework.ai.mcp.spec.McpSchema.TextContent;
import org.springframework.ai.mcp.spec.ServerMcpTransport;
import org.springframework.ai.mcp.spring.ToolHelper;
import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.ai.mcp.McpToolUtils;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.ToolCallbacks;
import org.springframework.ai.tool.function.FunctionToolCallback;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestClient;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@@ -77,9 +78,8 @@ public class McpServerConfig implements WebMvcConfigurer {
.resources(systemInfoResourceRegistration())
.prompts(greetingPromptRegistration())
.tools(
ToolHelper.toSyncToolRegistration(
FunctionCallback.builder()
.function("toUpperCase", (Function<ToUpperCaseInput, String>) s -> s.input().toUpperCase())
McpToolUtils.toSyncToolRegistration(
FunctionToolCallback.builder("toUpperCase", (Function<ToUpperCaseInput, String>) s -> s.input().toUpperCase())
.description("To upper case")
.inputType(ToUpperCaseInput.class)
.build()))
@@ -89,20 +89,8 @@ public class McpServerConfig implements WebMvcConfigurer {
} // @formatter:on
public static List<SyncToolRegistration> openLibraryToolRegistrations(OpenLibrary openLibrary) {
var books = FunctionCallback.builder()
.method("getBooks", String.class)
.description("Get list of Books by title")
.targetObject(openLibrary)
.build();
var bookTitlesByAuthor = FunctionCallback.builder()
.method("getBookTitlesByAuthor", String.class)
.description("Get book titles by author")
.targetObject(openLibrary)
.build();
return ToolHelper.toSyncToolRegistration(books, bookTitlesByAuthor);
ToolCallback[] tools = ToolCallbacks.from(openLibrary);
return McpToolUtils.toSyncToolRegistration(tools);
}
private static SyncResourceRegistration systemInfoResourceRegistration() {
@@ -156,10 +144,4 @@ public class McpServerConfig implements WebMvcConfigurer {
return new GetPromptResult("A personalized greeting message", List.of(userMessage));
});
}
@Bean
public OpenLibrary openLibrary() {
return new OpenLibrary(RestClient.builder());
}
}

View File

@@ -18,12 +18,14 @@ package org.springframework.ai.mcp.sample.servlet.server;
import java.util.List;
import java.util.Map;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClient;
/**
* @author Christian Tzolov
*/
@Service
public class OpenLibrary {
private RestClient restClient;
@@ -38,6 +40,7 @@ public class OpenLibrary {
public record Book(List<String> isbn, String title, List<String> authorName) {
}
@Tool(description = "Search for books by title")
public List<Book> getBooks(String title) {
Books books = restClient.get()
.uri(uriBuilder -> uriBuilder.path("/search.json").queryParam("q", title).build())
@@ -52,6 +55,7 @@ public class OpenLibrary {
.toList();
}
@Tool(description = "Search for books by author")
public List<String> getBookTitlesByAuthor(String authorName) {
var books = restClient.get()
.uri(uriBuilder -> uriBuilder.path("/search/authors.json").queryParam("q", authorName).build())

View File

@@ -15,8 +15,8 @@
*/
package org.springframework.ai.mcp.sample.servlet.client;
import org.springframework.ai.mcp.client.transport.ServerParameters;
import org.springframework.ai.mcp.client.transport.StdioClientTransport;
import io.modelcontextprotocol.client.transport.ServerParameters;
import io.modelcontextprotocol.client.transport.StdioClientTransport;
/**
* With stdio transport, the MCP server is automatically started by the client. But you

View File

@@ -15,7 +15,7 @@
*/
package org.springframework.ai.mcp.sample.servlet.client;
import org.springframework.ai.mcp.client.transport.HttpClientSseClientTransport;
import io.modelcontextprotocol.client.transport.HttpClientSseClientTransport;
/**
* @author Christian Tzolov

View File

@@ -17,14 +17,15 @@ package org.springframework.ai.mcp.sample.servlet.client;
import java.util.Map;
import org.springframework.ai.mcp.client.McpClient;
import org.springframework.ai.mcp.spec.ClientMcpTransport;
import org.springframework.ai.mcp.spec.McpSchema.CallToolRequest;
import org.springframework.ai.mcp.spec.McpSchema.CallToolResult;
import org.springframework.ai.mcp.spec.McpSchema.GetPromptRequest;
import org.springframework.ai.mcp.spec.McpSchema.ListPromptsResult;
import org.springframework.ai.mcp.spec.McpSchema.ListToolsResult;
import org.springframework.ai.mcp.spec.McpSchema.ReadResourceRequest;
import io.modelcontextprotocol.client.McpClient;
import io.modelcontextprotocol.spec.ClientMcpTransport;
import io.modelcontextprotocol.spec.McpSchema.CallToolResult;
import io.modelcontextprotocol.spec.McpSchema.ListPromptsResult;
import io.modelcontextprotocol.spec.McpSchema.ListToolsResult;
import io.modelcontextprotocol.spec.McpSchema.CallToolRequest;
import io.modelcontextprotocol.spec.McpSchema.GetPromptRequest;
import io.modelcontextprotocol.spec.McpSchema.ReadResourceRequest;
/**
* @author Christian Tzolov

View File

@@ -19,7 +19,6 @@
<description>Sample Spring Boot application demonstrating MCP server usage</description>
<properties>
<mcp.version>0.7.0-SNAPSHOT</mcp.version>
<sprign-ai.version>1.0.0-SNAPSHOT</sprign-ai.version>
</properties>
@@ -32,33 +31,14 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp-bom</artifactId>
<version>${mcp.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp-spring-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-spring-boot-starter</artifactId>
<artifactId>spring-ai-mcp-server-webflux-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
</dependencies>
<build>
@@ -66,27 +46,6 @@
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>io.spring.javaformat</groupId>
<artifactId>spring-javaformat-maven-plugin</artifactId>
<version>0.0.43</version>
<executions>
<execution>
<phase>validate</phase>
<inherited>true</inherited>
<goals>
<goal>validate</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

View File

@@ -1,10 +1,9 @@
# NOTE: You must disable the banner and the console logging
# to allow the STDIO transport to work !!!
spring.main.banner-mode=off
logging.pattern.console=
# logging.pattern.console=
logging.file.name=./model-context-protocol/mcp-webflux-server-starter/target/mcp.webflux-server-starter.log
spring.ai.mcp.server.enabled=true
spring.ai.mcp.server.name=my-webflux-server-starter
spring.ai.mcp.server.version=0.0.1
spring.ai.mcp.server.transport=WEBFLUX

View File

@@ -31,7 +31,7 @@ public class ClientStdio {
public static void main(String[] args) {
var stdioParams = ServerParameters.builder("java")
.args("-Dspring.ai.mcp.server.transport=STDIO", "-Dspring.main.web-application-type=none",
.args("-Dspring.ai.mcp.server.stdio=true", "-Dspring.main.web-application-type=none",
"-Dlogging.pattern.console=", "-jar",
"model-context-protocol/mcp-webflux-server-starter/target/mcp-webflux-server-starter-0.0.1-SNAPSHOT.jar")
.build();

View File

@@ -14,6 +14,7 @@
<version>0.0.1-SNAPSHOT</version>
<name>Spring AI - Model Context Protocol - Brave</name>
<description>Simple AI Application using MCP client to use Brave for Internet search</description>
<properties>
<java.version>17</java.version>
</properties>
@@ -31,14 +32,10 @@
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-spring-boot-starter</artifactId>
<artifactId>spring-ai-mcp-client-spring-boot-starter</artifactId>
</dependency>
<dependency>

View File

@@ -3,5 +3,4 @@ spring.main.web-application-type=none
spring.ai.anthropic.api-key=${ANTHROPIC_API_KEY}
spring.ai.mcp.client.stdio.enabled=true
spring.ai.mcp.client.stdio.servers-configuration=classpath:/mcp-servers-config.json

View File

@@ -31,14 +31,10 @@
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-spring-boot-starter</artifactId>
<artifactId>spring-ai-mcp-client-spring-boot-starter</artifactId>
</dependency>
<dependency>
@@ -46,11 +42,6 @@
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp</artifactId>
</dependency>
</dependencies>
<build>

View File

@@ -1,3 +1,18 @@
/*
* Copyright 2025-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.mcp.samples.brave;
import java.util.List;

View File

@@ -4,5 +4,8 @@ spring.main.web-application-type=none
spring.ai.openai.api-key=${OPENAI_API_KEY}
spring.ai.anthropic.api-key=${ANTHROPIC_API_KEY}
spring.ai.mcp.client.stdio.enabled=true
spring.ai.mcp.client.stdio.servers-configuration=classpath:/mcp-servers-config.json
# spring.ai.mcp.client.stdio.servers-configuration=classpath:/mcp-servers-config.json
spring.ai.mcp.client.stdio.connections.brave-search.command=npx
spring.ai.mcp.client.stdio.connections.brave-search.args=-y,@modelcontextprotocol/server-brave-search
spring.ai.mcp.client.stdio.connections.brave-search.env.FOO=BAAR

View File

@@ -0,0 +1,2 @@
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.4/apache-maven-3.8.4-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar

View File

@@ -0,0 +1,305 @@
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Maven2 Start Up Batch script
#
# Required ENV vars:
# ------------------
# JAVA_HOME - location of a JDK home dir
#
# Optional ENV vars
# -----------------
# M2_HOME - location of maven2's installed home dir
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
# e.g. to debug Maven itself, use
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
# ----------------------------------------------------------------------------
if [ -z "$MAVEN_SKIP_RC" ] ; then
if [ -f /etc/mavenrc ] ; then
. /etc/mavenrc
fi
if [ -f "$HOME/.mavenrc" ] ; then
. "$HOME/.mavenrc"
fi
fi
# OS specific support. $var _must_ be set to either true or false.
cygwin=false;
darwin=false;
mingw=false
case "`uname`" in
CYGWIN*) cygwin=true ;;
MINGW*) mingw=true;;
Darwin*) darwin=true
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
if [ -z "$JAVA_HOME" ]; then
if [ -x "/usr/libexec/java_home" ]; then
export JAVA_HOME="`/usr/libexec/java_home`"
else
export JAVA_HOME="/Library/Java/Home"
fi
fi
;;
esac
if [ -z "$JAVA_HOME" ] ; then
if [ -r /etc/gentoo-release ] ; then
JAVA_HOME=`java-config --jre-home`
fi
fi
if [ -z "$M2_HOME" ] ; then
## resolve links - $0 may be a link to maven's home
PRG="$0"
# need this for relative symlinks
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG="`dirname "$PRG"`/$link"
fi
done
saveddir=`pwd`
M2_HOME=`dirname "$PRG"`/..
# make it fully qualified
M2_HOME=`cd "$M2_HOME" && pwd`
cd "$saveddir"
# echo Using m2 at $M2_HOME
fi
# For Cygwin, ensure paths are in UNIX format before anything is touched
if $cygwin ; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --unix "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
fi
# For Mingw, ensure paths are in UNIX format before anything is touched
if $mingw ; then
[ -n "$M2_HOME" ] &&
M2_HOME="`(cd "$M2_HOME"; pwd)`"
[ -n "$JAVA_HOME" ] &&
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
fi
if [ -z "$JAVA_HOME" ]; then
javaExecutable="`which javac`"
if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
# readlink(1) is not available as standard on Solaris 10.
readLink=`which readlink`
if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
if $darwin ; then
javaHome="`dirname \"$javaExecutable\"`"
javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
else
javaExecutable="`readlink -f \"$javaExecutable\"`"
fi
javaHome="`dirname \"$javaExecutable\"`"
javaHome=`expr "$javaHome" : '\(.*\)/bin'`
JAVA_HOME="$javaHome"
export JAVA_HOME
fi
fi
fi
if [ -z "$JAVACMD" ] ; then
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
else
JAVACMD="`which java`"
fi
fi
if [ ! -x "$JAVACMD" ] ; then
echo "Error: JAVA_HOME is not defined correctly." >&2
echo " We cannot execute $JAVACMD" >&2
exit 1
fi
if [ -z "$JAVA_HOME" ] ; then
echo "Warning: JAVA_HOME environment variable is not set."
fi
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
# traverses directory structure from process work directory to filesystem root
# first directory with .mvn subdirectory is considered project base directory
find_maven_basedir() {
if [ -z "$1" ]
then
echo "Path not specified to find_maven_basedir"
return 1
fi
basedir="$1"
wdir="$1"
while [ "$wdir" != '/' ] ; do
if [ -d "$wdir"/.mvn ] ; then
basedir=$wdir
break
fi
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
if [ -d "${wdir}" ]; then
wdir=`cd "$wdir/.."; pwd`
fi
# end of workaround
done
echo "${basedir}"
}
# concatenates all lines of a file
concat_lines() {
if [ -f "$1" ]; then
echo "$(tr -s '\n' ' ' < "$1")"
fi
}
BASE_DIR=`find_maven_basedir "$(pwd)"`
if [ -z "$BASE_DIR" ]; then
exit 1;
fi
##########################################################################################
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
# This allows using the maven wrapper in projects that prohibit checking in binary data.
##########################################################################################
if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found .mvn/wrapper/maven-wrapper.jar"
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
fi
if [ "$MVNW_REPOURL" = true]; then
jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.2/maven-wrapper-0.5.2.jar"
else
jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.2/maven-wrapper-0.5.2.jar"
fi
while IFS="=" read key value; do
case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
esac
done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
if [ "$MVNW_VERBOSE" = true ]; then
echo "Downloading from: $jarUrl"
fi
wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
if $cygwin; then
wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
fi
if command -v wget > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found wget ... using wget"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
wget "$jarUrl" -O "$wrapperJarPath"
else
wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
fi
elif command -v curl > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found curl ... using curl"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
curl -o "$wrapperJarPath" "$jarUrl" -f
else
curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Falling back to using Java to download"
fi
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
# For Cygwin, switch paths to Windows format before running javac
if $cygwin; then
javaClass=`cygpath --path --windows "$javaClass"`
fi
if [ -e "$javaClass" ]; then
if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Compiling MavenWrapperDownloader.java ..."
fi
# Compiling the Java class
("$JAVA_HOME/bin/javac" "$javaClass")
fi
if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
# Running the downloader
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Running MavenWrapperDownloader.java ..."
fi
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
fi
fi
fi
fi
##########################################################################################
# End of extension
##########################################################################################
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
if [ "$MVNW_VERBOSE" = true ]; then
echo $MAVEN_PROJECTBASEDIR
fi
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
# For Cygwin, switch paths to Windows format before running java
if $cygwin; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --path --windows "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
fi
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
exec "$JAVACMD" \
$MAVEN_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
"-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"

View File

@@ -0,0 +1,172 @@
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM http://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Maven2 Start Up Batch script
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@REM
@REM Optional ENV vars
@REM M2_HOME - location of maven2's installed home dir
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
@REM e.g. to debug Maven itself, use
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
@REM ----------------------------------------------------------------------------
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
@echo off
@REM set title of command window
title %0
@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
@REM set %HOME% to equivalent of $HOME
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
@REM Execute a user defined script before this one
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
:skipRcPre
@setlocal
set ERROR_CODE=0
@REM To isolate internal variables from possible post scripts, we use another setlocal
@setlocal
@REM ==== START VALIDATION ====
if not "%JAVA_HOME%" == "" goto OkJHome
echo.
echo Error: JAVA_HOME not found in your environment. >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
:OkJHome
if exist "%JAVA_HOME%\bin\java.exe" goto init
echo.
echo Error: JAVA_HOME is set to an invalid directory. >&2
echo JAVA_HOME = "%JAVA_HOME%" >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
@REM ==== END VALIDATION ====
:init
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
@REM Fallback to current working directory if not found.
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
set EXEC_DIR=%CD%
set WDIR=%EXEC_DIR%
:findBaseDir
IF EXIST "%WDIR%"\.mvn goto baseDirFound
cd ..
IF "%WDIR%"=="%CD%" goto baseDirNotFound
set WDIR=%CD%
goto findBaseDir
:baseDirFound
set MAVEN_PROJECTBASEDIR=%WDIR%
cd "%EXEC_DIR%"
goto endDetectBaseDir
:baseDirNotFound
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
cd "%EXEC_DIR%"
:endDetectBaseDir
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
@setlocal EnableExtensions EnableDelayedExpansion
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
:endReadAdditionalConfig
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.2/maven-wrapper-0.5.2.jar"
FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
)
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
if exist %WRAPPER_JAR% (
echo Found %WRAPPER_JAR%
) else (
if not "%MVNW_REPOURL%" == "" (
SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.2/maven-wrapper-0.5.2.jar"
)
echo Couldn't find %WRAPPER_JAR%, downloading it ...
echo Downloading from: %DOWNLOAD_URL%
powershell -Command "&{"^
"$webclient = new-object System.Net.WebClient;"^
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
"}"^
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
"}"
echo Finished downloading %WRAPPER_JAR%
)
@REM End of extension
%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error
goto end
:error
set ERROR_CODE=1
:end
@endlocal & set ERROR_CODE=%ERROR_CODE%
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
@REM check for post script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
:skipRcPost
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
if "%MAVEN_BATCH_PAUSE%" == "on" pause
if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
exit /B %ERROR_CODE%

View File

@@ -0,0 +1,94 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.6</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>mcp-starter-default-client</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>Spring AI - MCP Starter Default Client</name>
<description>Spring AI - MCP Starter Default Client</description>
<properties>
<java.version>17</java.version>
<spring-ai.version>1.0.0-SNAPSHOT</spring-ai.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-client-spring-boot-starter</artifactId>
</dependency>
<!-- <dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency> -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-anthropic-spring-boot-starter</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<name>Central Portal Snapshots</name>
<id>central-portal-snapshots</id>
<url>https://central.sonatype.com/repository/maven-snapshots/</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone-local</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</repository>
</repositories>
</project>

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2025-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.mcp.samples.client;
import java.util.List;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Value("${spring.ai.mcp.client.demo.user.input}")
private String userInput;
@Bean
public CommandLineRunner predefinedQuestions(ChatClient.Builder chatClientBuilder, List<ToolCallback> tools,
ConfigurableApplicationContext context) {
return args -> {
var chatClient = chatClientBuilder
.defaultTools(tools)
.build();
System.out.println("\n>>> QUESTION: " + userInput);
System.out.println("\n>>> ASSISTANT: " + chatClient.prompt(userInput).call().content());
context.close();
};
}
}

View File

@@ -0,0 +1,14 @@
spring.application.name=mcp
spring.main.web-application-type=none
spring.ai.openai.api-key=${OPENAI_API_KEY}
spring.ai.anthropic.api-key=${ANTHROPIC_API_KEY}
# spring.ai.mcp.client.stdio.servers-configuration=classpath:/mcp-servers-config.json
spring.ai.mcp.client.stdio.connections.brave-search.command=npx
spring.ai.mcp.client.stdio.connections.brave-search.args=-y,@modelcontextprotocol/server-brave-search
# spring.ai.mcp.client.stdio.connections.brave-search.env.FOO=BAAR
spring.ai.mcp.client.demo.user.input=What tools are available?

View File

@@ -0,0 +1,13 @@
{
"mcpServers": {
"brave-search": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-brave-search"
],
"env": {
}
}
}
}

View File

@@ -0,0 +1,2 @@
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.4/apache-maven-3.8.4-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar

View File

@@ -0,0 +1,305 @@
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Maven2 Start Up Batch script
#
# Required ENV vars:
# ------------------
# JAVA_HOME - location of a JDK home dir
#
# Optional ENV vars
# -----------------
# M2_HOME - location of maven2's installed home dir
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
# e.g. to debug Maven itself, use
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
# ----------------------------------------------------------------------------
if [ -z "$MAVEN_SKIP_RC" ] ; then
if [ -f /etc/mavenrc ] ; then
. /etc/mavenrc
fi
if [ -f "$HOME/.mavenrc" ] ; then
. "$HOME/.mavenrc"
fi
fi
# OS specific support. $var _must_ be set to either true or false.
cygwin=false;
darwin=false;
mingw=false
case "`uname`" in
CYGWIN*) cygwin=true ;;
MINGW*) mingw=true;;
Darwin*) darwin=true
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
if [ -z "$JAVA_HOME" ]; then
if [ -x "/usr/libexec/java_home" ]; then
export JAVA_HOME="`/usr/libexec/java_home`"
else
export JAVA_HOME="/Library/Java/Home"
fi
fi
;;
esac
if [ -z "$JAVA_HOME" ] ; then
if [ -r /etc/gentoo-release ] ; then
JAVA_HOME=`java-config --jre-home`
fi
fi
if [ -z "$M2_HOME" ] ; then
## resolve links - $0 may be a link to maven's home
PRG="$0"
# need this for relative symlinks
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG="`dirname "$PRG"`/$link"
fi
done
saveddir=`pwd`
M2_HOME=`dirname "$PRG"`/..
# make it fully qualified
M2_HOME=`cd "$M2_HOME" && pwd`
cd "$saveddir"
# echo Using m2 at $M2_HOME
fi
# For Cygwin, ensure paths are in UNIX format before anything is touched
if $cygwin ; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --unix "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
fi
# For Mingw, ensure paths are in UNIX format before anything is touched
if $mingw ; then
[ -n "$M2_HOME" ] &&
M2_HOME="`(cd "$M2_HOME"; pwd)`"
[ -n "$JAVA_HOME" ] &&
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
fi
if [ -z "$JAVA_HOME" ]; then
javaExecutable="`which javac`"
if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
# readlink(1) is not available as standard on Solaris 10.
readLink=`which readlink`
if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
if $darwin ; then
javaHome="`dirname \"$javaExecutable\"`"
javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
else
javaExecutable="`readlink -f \"$javaExecutable\"`"
fi
javaHome="`dirname \"$javaExecutable\"`"
javaHome=`expr "$javaHome" : '\(.*\)/bin'`
JAVA_HOME="$javaHome"
export JAVA_HOME
fi
fi
fi
if [ -z "$JAVACMD" ] ; then
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
else
JAVACMD="`which java`"
fi
fi
if [ ! -x "$JAVACMD" ] ; then
echo "Error: JAVA_HOME is not defined correctly." >&2
echo " We cannot execute $JAVACMD" >&2
exit 1
fi
if [ -z "$JAVA_HOME" ] ; then
echo "Warning: JAVA_HOME environment variable is not set."
fi
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
# traverses directory structure from process work directory to filesystem root
# first directory with .mvn subdirectory is considered project base directory
find_maven_basedir() {
if [ -z "$1" ]
then
echo "Path not specified to find_maven_basedir"
return 1
fi
basedir="$1"
wdir="$1"
while [ "$wdir" != '/' ] ; do
if [ -d "$wdir"/.mvn ] ; then
basedir=$wdir
break
fi
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
if [ -d "${wdir}" ]; then
wdir=`cd "$wdir/.."; pwd`
fi
# end of workaround
done
echo "${basedir}"
}
# concatenates all lines of a file
concat_lines() {
if [ -f "$1" ]; then
echo "$(tr -s '\n' ' ' < "$1")"
fi
}
BASE_DIR=`find_maven_basedir "$(pwd)"`
if [ -z "$BASE_DIR" ]; then
exit 1;
fi
##########################################################################################
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
# This allows using the maven wrapper in projects that prohibit checking in binary data.
##########################################################################################
if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found .mvn/wrapper/maven-wrapper.jar"
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
fi
if [ "$MVNW_REPOURL" = true]; then
jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.2/maven-wrapper-0.5.2.jar"
else
jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.2/maven-wrapper-0.5.2.jar"
fi
while IFS="=" read key value; do
case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
esac
done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
if [ "$MVNW_VERBOSE" = true ]; then
echo "Downloading from: $jarUrl"
fi
wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
if $cygwin; then
wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
fi
if command -v wget > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found wget ... using wget"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
wget "$jarUrl" -O "$wrapperJarPath"
else
wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
fi
elif command -v curl > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found curl ... using curl"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
curl -o "$wrapperJarPath" "$jarUrl" -f
else
curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Falling back to using Java to download"
fi
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
# For Cygwin, switch paths to Windows format before running javac
if $cygwin; then
javaClass=`cygpath --path --windows "$javaClass"`
fi
if [ -e "$javaClass" ]; then
if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Compiling MavenWrapperDownloader.java ..."
fi
# Compiling the Java class
("$JAVA_HOME/bin/javac" "$javaClass")
fi
if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
# Running the downloader
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Running MavenWrapperDownloader.java ..."
fi
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
fi
fi
fi
fi
##########################################################################################
# End of extension
##########################################################################################
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
if [ "$MVNW_VERBOSE" = true ]; then
echo $MAVEN_PROJECTBASEDIR
fi
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
# For Cygwin, switch paths to Windows format before running java
if $cygwin; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --path --windows "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
fi
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
exec "$JAVACMD" \
$MAVEN_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
"-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"

View File

@@ -0,0 +1,172 @@
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM http://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Maven2 Start Up Batch script
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@REM
@REM Optional ENV vars
@REM M2_HOME - location of maven2's installed home dir
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
@REM e.g. to debug Maven itself, use
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
@REM ----------------------------------------------------------------------------
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
@echo off
@REM set title of command window
title %0
@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
@REM set %HOME% to equivalent of $HOME
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
@REM Execute a user defined script before this one
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
:skipRcPre
@setlocal
set ERROR_CODE=0
@REM To isolate internal variables from possible post scripts, we use another setlocal
@setlocal
@REM ==== START VALIDATION ====
if not "%JAVA_HOME%" == "" goto OkJHome
echo.
echo Error: JAVA_HOME not found in your environment. >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
:OkJHome
if exist "%JAVA_HOME%\bin\java.exe" goto init
echo.
echo Error: JAVA_HOME is set to an invalid directory. >&2
echo JAVA_HOME = "%JAVA_HOME%" >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
@REM ==== END VALIDATION ====
:init
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
@REM Fallback to current working directory if not found.
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
set EXEC_DIR=%CD%
set WDIR=%EXEC_DIR%
:findBaseDir
IF EXIST "%WDIR%"\.mvn goto baseDirFound
cd ..
IF "%WDIR%"=="%CD%" goto baseDirNotFound
set WDIR=%CD%
goto findBaseDir
:baseDirFound
set MAVEN_PROJECTBASEDIR=%WDIR%
cd "%EXEC_DIR%"
goto endDetectBaseDir
:baseDirNotFound
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
cd "%EXEC_DIR%"
:endDetectBaseDir
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
@setlocal EnableExtensions EnableDelayedExpansion
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
:endReadAdditionalConfig
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.2/maven-wrapper-0.5.2.jar"
FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
)
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
if exist %WRAPPER_JAR% (
echo Found %WRAPPER_JAR%
) else (
if not "%MVNW_REPOURL%" == "" (
SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.2/maven-wrapper-0.5.2.jar"
)
echo Couldn't find %WRAPPER_JAR%, downloading it ...
echo Downloading from: %DOWNLOAD_URL%
powershell -Command "&{"^
"$webclient = new-object System.Net.WebClient;"^
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
"}"^
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
"}"
echo Finished downloading %WRAPPER_JAR%
)
@REM End of extension
%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error
goto end
:error
set ERROR_CODE=1
:end
@endlocal & set ERROR_CODE=%ERROR_CODE%
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
@REM check for post script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
:skipRcPost
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
if "%MAVEN_BATCH_PAUSE%" == "on" pause
if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
exit /B %ERROR_CODE%

View File

@@ -0,0 +1,94 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.6</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>mcp-starter-webflux-client</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>Spring AI - MCP Starter WebFlux Client</name>
<description>Spring AI - MCP Starter WebFlux Client</description>
<properties>
<java.version>17</java.version>
<spring-ai.version>1.0.0-SNAPSHOT</spring-ai.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-server-webmvc-spring-boot-starter</artifactId>
</dependency>
<!-- <dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency> -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-anthropic-spring-boot-starter</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<name>Central Portal Snapshots</name>
<id>central-portal-snapshots</id>
<url>https://central.sonatype.com/repository/maven-snapshots/</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone-local</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</repository>
</repositories>
</project>

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2025-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.mcp.samples.client;
import java.util.List;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Value("${spring.ai.mcp.client.demo.user.input}")
private String userInput;
@Bean
public CommandLineRunner predefinedQuestions(ChatClient.Builder chatClientBuilder, List<ToolCallback> tools,
ConfigurableApplicationContext context) {
return args -> {
var chatClient = chatClientBuilder
.defaultTools(tools)
.build();
System.out.println("\n>>> QUESTION: " + userInput);
System.out.println("\n>>> ASSISTANT: " + chatClient.prompt(userInput).call().content());
context.close();
};
}
}

View File

@@ -0,0 +1,14 @@
spring.application.name=mcp
spring.main.web-application-type=none
spring.ai.openai.api-key=${OPENAI_API_KEY}
spring.ai.anthropic.api-key=${ANTHROPIC_API_KEY}
# spring.ai.mcp.client.stdio.servers-configuration=classpath:/mcp-servers-config.json
spring.ai.mcp.client.stdio.connections.brave-search.command=npx
spring.ai.mcp.client.stdio.connections.brave-search.args=-y,@modelcontextprotocol/server-brave-search
# spring.ai.mcp.client.stdio.connections.brave-search.env.FOO=BAAR
spring.ai.mcp.client.demo.user.input=What tools are available?

View File

@@ -0,0 +1,13 @@
{
"mcpServers": {
"brave-search": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-brave-search"
],
"env": {
}
}
}
}

View File

@@ -1,176 +0,0 @@
# Spring AI MCP Quick Weather Server Sample
This sample project demonstrates a simplified implementation of the Spring AI Model Context Protocol (MCP). It shows how to create an MCP server that exposes weather-related tools using the National Weather Service API.
## Overview
The sample provides:
- A Spring Boot application implementing an MCP server
- Two transport mode implementations: Stdio and SSE (Server-Sent Events)
- Two weather-related tools:
- Get weather forecast by location (latitude/longitude)
- Get weather alerts by US state
## Building the Project
```bash
./mvnw clean package
```
## Running the Server
The server can be started in two transport modes, controlled by the `transport.mode` property:
### Stdio Mode (Default)
```bash
java -Dspring.ai.mcp.server.transport=STDIO -Dspring.main.web-application-type=none -Dlogging.pattern.console= -jar target/mcp-weather-server-quick-0.0.1-SNAPSHOT.jar
```
The Stdio mode server is automatically started by the client - no explicit server startup is needed.
But you have to build the server jar first: `./mvnw clean install -DskipTests`.
In Stdio mode the server must not emit any messages/logs to the console (e.g. standard out) but the JSON messages produced by the server.
### SSE Mode
```bash
java -Dspring.ai.mcp.server.transport=WEBFLUX -jar target/mcp-weather-server-quick-0.0.1-SNAPSHOT.jar
```
## Sample Clients
The project includes example clients for both transport modes:
### Stdio Client (ClientStdio.java)
```java
var stdioParams = ServerParameters.builder("java")
.args("-Dspring.ai.mcp.server.transport=STDIO", "-Dspring.main.web-application-type=none",
"-Dlogging.pattern.console=", "-jar",
"model-context-protocol/mcp-weather-server-quick/target/mcp-weather-server-quick-0.0.1-SNAPSHOT.jar")
.build();
var transport = new StdioClientTransport(stdioParams);
var client = McpClient.sync(transport).build();
```
### SSE Client (ClientSse.java)
```java
var transport = new WebFluxSseClientTransport(WebClient.builder().baseUrl("http://localhost:8080"));
var client = McpClient.using(transport).sync();
```
### Claud Destop
```json
{
"mcpServers": {
"spring-ai-mcp-weather": {
"command": "java",
"args": [
"-Dspring.ai.mcp.server.transport=STDIO",
"-Dspring.main.web-application-type=none",
"-Dlogging.pattern.console=",
"-jar",
"<YOUR ABSOLUTE PATH TO>/mcp-weather-server-quick-0.0.1-SNAPSHOT.jar"
]
}
}
}
```
## Available Tools
### Weather Forecast Tool
- Name: `getWeatherForecastByLocation`
- Description: Get weather forecast for a specific latitude/longitude
- Parameters:
- `latitude`: double - Latitude coordinate
- `longitude`: double - Longitude coordinate
- Example:
```java
CallToolResult forecastResult = client.callTool(new CallToolRequest("getWeatherForecastByLocation",
Map.of("latitude", 47.6062, "longitude", -122.3321)));
```
### Weather Alerts Tool
- Name: `getAlerts`
- Description: Get weather alerts for a US state
- Parameters:
- `state`: String - Two-letter US state code (e.g. CA, NY)
- Example:
```java
CallToolResult alertResult = client.callTool(new CallToolRequest("getAlerts",
Map.of("state", "NY")));
```
## Client Usage Example
```java
// Initialize client
client.initialize();
// Test connection
client.ping();
// List available tools
ListToolsResult tools = client.listTools();
System.out.println("Available tools: " + tools);
// Get weather forecast for Seattle
CallToolResult weatherForcastResult = client.callTool(new CallToolRequest("getWeatherForecastByLocation",
Map.of("latitude", "47.6062", "longitude", "-122.3321")));
System.out.println("Weather Forcast: " + weatherForcastResult);
// Get weather alerts for New York
CallToolResult alertResult = client.callTool(new CallToolRequest("getAlerts", Map.of("state", "NY")));
System.out.println("Alert Response = " + alertResult);
// Close client
client.closeGracefully();
```
## Server Implementation
The server is implemented using Spring Boot and Spring AI's tool annotations:
```java
@SpringBootApplication
public class McpServerApplication {
public static void main(String[] args) {
SpringApplication.run(McpServerApplication.class, args);
}
@Bean
public List<ToolCallback> weatherTools(WeatherService weatherService) {
return List.of(ToolCallbacks.from(weatherService));
}
}
```
The `WeatherService` class provides the tool implementations using the `@Tool` annotation:
```java
@Service
public class WeatherService {
@Tool(description = "Get weather forecast for a specific latitude/longitude")
public String getWeatherForecastByLocation(double latitude, double longitude) {
// Implementation using weather.gov API
}
@Tool(description = "Get weather alerts for a US state. Input is Two-letter US state code (e.g. CA, NY)")
public String getAlerts(String state) {
// Implementation using weather.gov API
}
}
```
## Configuration
The application can be configured through `application.properties`:
- `spring.ai.mcp.server.transport`: Transport mode to use (STDIO/WEBFLUX)
- `server.port`: Server port for WEBFLUX mode (default: 8080)
- `spring.main.banner-mode`: Set to 'off' for STDIO mode
- `logging.pattern.console`: Clear this property for STDIO mode
- `logging.file.name`: Path to log file (useful when console logging is disabled)

View File

@@ -1,175 +0,0 @@
# Spring AI MCP Quick Weather Server Sample
This sample project demonstrates a simplified implementation of the Spring AI Model Context Protocol (MCP). It shows how to create an MCP server that exposes weather-related tools using the National Weather Service API.
## Overview
The sample provides:
- A Spring Boot application implementing an MCP server
- Two transport mode implementations: Stdio and SSE (Server-Sent Events)
- Two weather-related tools:
- Get weather forecast by location (latitude/longitude)
- Get weather alerts by US state
## Building the Project
```bash
./mvnw clean install -DskipTests
```
## Running the Server
The server can be started in two transport modes, controlled by the `transport.mode` property:
### Stdio Mode (Default)
The Stdio mode server is automatically started by the client - no explicit server startup is needed.
But you have to build the server jar first: `./mvnw clean install -DskipTests`.
In Stdio mode the server must not emit any messages/logs to the console (e.g. standard out) but the JSON messages produced by the server.
### SSE Mode
```bash
java -Dspring.ai.mcp.server.transport=WEBFLUX -jar target/mcp-weather-server-quick-0.0.1-SNAPSHOT.jar
```
## Sample Clients
The project includes example clients for both transport modes:
### Stdio Client (ClientStdio.java)
**Note:** When running via command line, you may need to specify the full path to your Java executable since the shell environment (including `.bashrc`) might not be loaded. For example, instead of `"java"`, you might need to use the full path like `"/home/user/.sdkman/candidates/java/current/bin/java"`. You can find your Java path by running `which java` in your terminal.
```java
var stdioParams = ServerParameters.builder("java") // You may need to use full path to Java executable
.args("-Dspring.ai.mcp.server.transport=STDIO", "-Dspring.main.web-application-type=none",
"-Dlogging.pattern.console=", "-jar",
"model-context-protocol/mcp-weather-server-quick/target/mcp-weather-server-quick-0.0.1-SNAPSHOT.jar")
.build();
var transport = new StdioClientTransport(stdioParams);
var client = McpClient.sync(transport).build();
```
### SSE Client (ClientSse.java)
```java
var transport = new WebFluxSseClientTransport(WebClient.builder().baseUrl("http://localhost:8080"));
var client = McpClient.using(transport).sync();
```
### Claude Desktop
```json
{
"mcpServers": {
"spring-ai-mcp-weather": {
"command": "java",
"args": [
"-Dspring.ai.mcp.server.transport=STDIO",
"-Dspring.main.web-application-type=none",
"-Dlogging.pattern.console=",
"-jar",
"<YOUR ABSOLUTE PATH TO>/mcp-weather-server-quick-0.0.1-SNAPSHOT.jar"
]
}
}
}
```
## Available Tools
### Weather Forecast Tool
- Name: `getWeatherForecastByLocation`
- Description: Get weather forecast for a specific latitude/longitude
- Parameters:
- `latitude`: double - Latitude coordinate
- `longitude`: double - Longitude coordinate
- Example:
```java
CallToolResult forecastResult = client.callTool(new CallToolRequest("getWeatherForecastByLocation",
Map.of("latitude", 47.6062, "longitude", -122.3321)));
```
### Weather Alerts Tool
- Name: `getAlerts`
- Description: Get weather alerts for a US state
- Parameters:
- `state`: String - Two-letter US state code (e.g. CA, NY)
- Example:
```java
CallToolResult alertResult = client.callTool(new CallToolRequest("getAlerts",
Map.of("state", "NY")));
```
## Client Usage Example (SampleClient.java)
```java
// Initialize client
client.initialize();
// Test connection
client.ping();
// List available tools
ListToolsResult tools = client.listTools();
System.out.println("Available tools: " + tools);
// Get weather forecast for Seattle
CallToolResult weatherForcastResult = client.callTool(new CallToolRequest("getWeatherForecastByLocation",
Map.of("latitude", "47.6062", "longitude", "-122.3321")));
System.out.println("Weather Forcast: " + weatherForcastResult);
// Get weather alerts for New York
CallToolResult alertResult = client.callTool(new CallToolRequest("getAlerts", Map.of("state", "NY")));
System.out.println("Alert Response = " + alertResult);
// Close client
client.closeGracefully();
```
## Server Implementation
The server is implemented using Spring Boot and Spring AI's tool annotations:
```java
@SpringBootApplication
public class McpServerApplication {
public static void main(String[] args) {
SpringApplication.run(McpServerApplication.class, args);
}
@Bean
public List<ToolCallback> weatherTools(WeatherService weatherService) {
return List.of(ToolCallbacks.from(weatherService));
}
}
```
The `WeatherService` class provides the tool implementations using the `@Tool` annotation:
```java
@Service
public class WeatherService {
@Tool(description = "Get weather forecast for a specific latitude/longitude")
public String getWeatherForecastByLocation(double latitude, double longitude) {
// Implementation using weather.gov API
}
@Tool(description = "Get weather alerts for a US state. Input is Two-letter US state code (e.g. CA, NY)")
public String getAlerts(String state) {
// Implementation using weather.gov API
}
}
```
## Configuration
The application can be configured through `application.properties`:
- `spring.ai.mcp.server.transport`: Transport mode to use (STDIO/WEBFLUX)
- `server.port`: Server port for WEBFLUX mode (default: 8080)
- `spring.main.banner-mode`: Set to 'off' for STDIO mode
- `logging.pattern.console`: Clear this property for STDIO mode
- `logging.file.name`: Path to log file (useful when console logging is disabled)

View File

@@ -0,0 +1,214 @@
# Spring AI MCP Weather STDIO Server
A Spring Boot starter project demonstrating how to build a Model Context Protocol (MCP) server that provides weather-related tools using the National Weather Service (weather.gov) API. This project showcases the Spring AI MCP Server Boot Starter capabilities with STDIO transport implementation.
## Prerequisites
- Java 17 or later
- Maven 3.6+
- Basic understanding of Spring Boot and Spring AI
- (Optional) Claude Desktop for AI assistant integration
## About Spring AI MCP Server Boot Starter
This project uses `spring-ai-mcp-server-spring-boot-starter`, which provides:
- Automatic configuration of MCP server components
- Support for both synchronous and asynchronous operation modes
- STDIO transport layer implementation
- Flexible tool registration through Spring beans
- Change notification capabilities
## Project Structure
```
src/
├── main/
│ ├── java/
│ │ └── org/springframework/ai/mcp/sample/server/
│ │ ├── McpServerApplication.java # Main application class with tool registration
│ │ └── WeatherService.java # Weather service implementation with MCP tools
│ └── resources/
│ └── application.properties # Server and transport configuration
└── test/
└── java/
└── org/springframework/ai/mcp/sample/client/
└── ClientStdio.java # Test client implementation
```
## Building and Running
The server uses STDIO transport mode and is typically started automatically by the client. You only need to build the server jar:
```bash
./mvnw clean install -DskipTests
```
## Tool Implementation
The project demonstrates how to implement and register MCP tools using Spring's dependency injection and auto-configuration:
```java
@Service
public class WeatherService {
@Tool(description = "Get weather forecast for a specific latitude/longitude")
public String getWeatherForecastByLocation(
double latitude, // Latitude coordinate
double longitude // Longitude coordinate
) {
// Implementation
}
@Tool(description = "Get weather alerts for a US state")
public String getAlerts(
String state // Two-letter US state code (e.g. CA, NY)
) {
// Implementation
}
}
@SpringBootApplication
public class McpServerApplication {
@Bean
public List<ToolCallback> weatherTools(WeatherService weatherService) {
return ToolCallbacks.from(weatherService);
}
}
```
The auto-configuration will automatically register these tools with the MCP server. You can have multiple beans producing lists of ToolCallbacks, and the auto-configuration will merge them.
## Available Tools
### 1. Weather Forecast Tool
```java
@Tool(description = "Get weather forecast for a specific latitude/longitude")
public String getWeatherForecastByLocation(
double latitude, // Latitude coordinate
double longitude // Longitude coordinate
) {
// Returns detailed forecast including:
// - Temperature and unit
// - Wind speed and direction
// - Detailed forecast description
}
// Example usage:
CallToolResult forecast = client.callTool(
new CallToolRequest("getWeatherForecastByLocation",
Map.of(
"latitude", 47.6062, // Seattle coordinates
"longitude", -122.3321
)
)
);
```
### 2. Weather Alerts Tool
```java
@Tool(description = "Get weather alerts for a US state")
public String getAlerts(
String state // Two-letter US state code (e.g. CA, NY)
) {
// Returns active alerts including:
// - Event type
// - Affected area
// - Severity
// - Description
// - Safety instructions
}
// Example usage:
CallToolResult alerts = client.callTool(
new CallToolRequest("getAlerts",
Map.of("state", "NY")
)
);
```
## Client Integration
### Java Client Example
```java
// Create server parameters
ServerParameters stdioParams = ServerParameters.builder("java")
.args("-Dspring.ai.mcp.server.transport=STDIO",
"-Dspring.main.web-application-type=none",
"-Dlogging.pattern.console=",
"-jar",
"target/mcp-weather-stdio-server-0.0.1-SNAPSHOT.jar")
.build();
// Initialize transport and client
var transport = new StdioClientTransport(stdioParams);
var client = McpClient.sync(transport).build();
```
### Claude Desktop Integration
To integrate with Claude Desktop, add the following configuration to your Claude Desktop settings:
```json
{
"mcpServers": {
"spring-ai-mcp-weather": {
"command": "java",
"args": [
"-Dspring.ai.mcp.server.transport=STDIO",
"-Dspring.main.web-application-type=none",
"-Dlogging.pattern.console=",
"-jar",
"/absolute/path/to/mcp-weather-stdio-server-0.0.1-SNAPSHOT.jar"
]
}
}
}
```
Replace `/absolute/path/to/` with the actual path to your built jar file.
## Configuration
### Application Properties
All properties are prefixed with `spring.ai.mcp.server`
```properties
# Required STDIO Configuration
spring.main.web-application-type=none
spring.main.banner-mode=off
logging.pattern.console=
# Server Configuration
spring.ai.mcp.server.enabled=true
spring.ai.mcp.server.name=my-weather-server
spring.ai.mcp.server.version=0.0.1
# SYNC or ASYNC
spring.ai.mcp.server.type=SYNC
spring.ai.mcp.server.resource-change-notification=true
spring.ai.mcp.server.tool-change-notification=true
spring.ai.mcp.server.prompt-change-notification=true
# Optional file logging
logging.file.name=mcp-weather-stdio-server.log
```
### Key Configuration Notes
1. **STDIO Mode Requirements**:
- Disable web application type (`spring.main.web-application-type=none`)
- Disable Spring banner (`spring.main.banner-mode=off`)
- Clear console logging pattern (`logging.pattern.console=`)
2. **Server Type**:
- `SYNC` (default): Uses `McpSyncServer` for straightforward request-response patterns
- `ASYNC`: Uses `McpAsyncServer` for non-blocking operations with Project Reactor support
## Additional Resources
- [Spring AI Documentation](https://docs.spring.io/spring-ai/reference/)
- [Model Context Protocol Specification](https://modelcontextprotocol.github.io/specification/)
- [Spring Boot Auto-configuration](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.developing-auto-configuration)

View File

@@ -12,15 +12,12 @@
<groupId>com.example</groupId>
<artifactId>mcp-weather-server-quickstart</artifactId>
<artifactId>mcp-weather-stdio-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>Spring AI MCP Weather Sample</name>
<description>Sample Spring Boot application demonstrating MCP client and server usage</description>
<name>Spring AI MCP Weather STDIO server</name>
<description>Sample Spring Boot application demonstrating MCP stdio server usage</description>
<properties>
<mcp.version>0.7.0-SNAPSHOT</mcp.version>
</properties>
<dependencyManagement>
<dependencies>
@@ -31,34 +28,19 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp-bom</artifactId>
<version>${mcp.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-spring-boot-starter</artifactId>
<artifactId>spring-ai-mcp-server-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
<build>
@@ -66,27 +48,6 @@
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>io.spring.javaformat</groupId>
<artifactId>spring-javaformat-maven-plugin</artifactId>
<version>0.0.43</version>
<executions>
<execution>
<phase>validate</phase>
<inherited>true</inherited>
<goals>
<goal>validate</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

View File

@@ -10,5 +10,5 @@ spring.ai.mcp.server.enabled=true
spring.ai.mcp.server.name=my-weather-server
spring.ai.mcp.server.version=0.0.1
logging.file.name=./model-context-protocol/mcp-weather-server-quickstart/target/mcp.weather-quickstart.log
logging.file.name=./model-context-protocol/weather/mcp-weather-stdio-server/mcp-weather-server-quickstart/target/mcp-weather-stdio-server.log

View File

@@ -15,7 +15,6 @@
*/
package org.springframework.ai.mcp.sample.client;
import java.io.File;
import java.util.Map;
import io.modelcontextprotocol.client.McpClient;
@@ -39,7 +38,7 @@ public class ClientStdio {
var stdioParams = ServerParameters.builder("java")
.args("-jar",
"model-context-protocol/mcp-weather-server-quickstart/target/mcp-weather-server-quickstart-0.0.1-SNAPSHOT.jar")
"model-context-protocol/weather/starter-stdio-server/target/mcp-weather-stdio-server-0.0.1-SNAPSHOT.jar")
.build();
var transport = new StdioClientTransport(stdioParams);

View File

@@ -0,0 +1,213 @@
# Spring AI MCP Weather Server Sample with WebFlux Starter
This sample project demonstrates how to create an MCP server using the Spring AI MCP Server Boot Starter with WebFlux transport. It implements a weather service that exposes tools for retrieving weather information using the National Weather Service API.
## Overview
The sample showcases:
- Integration with `spring-ai-mcp-server-webflux-spring-boot-starter`
- Support for both SSE (Server-Sent Events) and STDIO transports
- Automatic tool registration using Spring AI's `@Tool` annotation
- Two weather-related tools:
- Get weather forecast by location (latitude/longitude)
- Get weather alerts by US state
## Dependencies
The project uses the Spring AI MCP Server WebFlux Boot Starter:
```xml
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-server-webflux-spring-boot-starter</artifactId>
</dependency>
```
This starter provides:
- Reactive transport using Spring WebFlux (`WebFluxSseServerTransport`)
- Automatically configured reactive SSE endpoints
- Optional STDIO transport
- Included `spring-boot-starter-webflux` and `mcp-spring-webflux` dependencies
## Building the Project
```bash
./mvnw clean install -DskipTests
```
## Running the Server
The server supports two transport modes:
### WebFlux SSE Mode (Default)
```bash
java -jar target/mcp-weather-starter-webflux-server-0.0.1-SNAPSHOT.jar
```
### STDIO Mode
Enable STDIO transport by setting the appropriate properties:
```bash
java -Dspring.ai.mcp.server.stdio=true -Dspring.main.web-application-type=none -jar target/mcp-weather-starter-webflux-server-0.0.1-SNAPSHOT.jar
```
## Configuration
The server can be configured through `application.properties`:
```properties
# Server identification
spring.ai.mcp.server.name=my-weather-server
spring.ai.mcp.server.version=0.0.1
# Server type (SYNC/ASYNC)
spring.ai.mcp.server.type=SYNC
# Transport configuration
spring.ai.mcp.server.stdio=false
spring.ai.mcp.server.sse-message-endpoint=/mcp/message
# Change notifications
spring.ai.mcp.server.resource-change-notification=true
spring.ai.mcp.server.tool-change-notification=true
spring.ai.mcp.server.prompt-change-notification=true
# Logging (required for STDIO transport)
spring.main.banner-mode=off
logging.file.name=./target/starter-webflux-server.log
```
## Sample Clients
### WebFlux SSE Client
```java
var transport = new WebFluxSseClientTransport(WebClient.builder().baseUrl("http://localhost:8080"));
var client = McpClient.sync(transport).build();
```
### STDIO Client
```java
var stdioParams = ServerParameters.builder("java")
.args("-Dspring.ai.mcp.server.stdio=true",
"-Dspring.main.web-application-type=none",
"-Dspring.main.banner-mode=off",
"-Dlogging.pattern.console=",
"-jar",
"target/mcp-weather-starter-webflux-server-0.0.1-SNAPSHOT.jar")
.build();
var transport = new StdioClientTransport(stdioParams);
var client = McpClient.sync(transport).build();
```
### Claude Desktop Configuration
```json
{
"mcpServers": {
"spring-ai-mcp-weather": {
"command": "java",
"args": [
"-Dspring.ai.mcp.server.stdio=true",
"-Dspring.main.web-application-type=none",
"-Dspring.main.banner-mode=off",
"-Dlogging.pattern.console=",
"-jar",
"<YOUR ABSOLUTE PATH TO>/mcp-weather-starter-webflux-server-0.0.1-SNAPSHOT.jar"
]
}
}
}
```
## Available Tools
### Weather Forecast Tool
- Name: `getWeatherForecastByLocation`
- Description: Get weather forecast for a specific latitude/longitude
- Parameters:
- `latitude`: double - Latitude coordinate
- `longitude`: double - Longitude coordinate
- Example:
```java
CallToolResult forecastResult = client.callTool(new CallToolRequest("getWeatherForecastByLocation",
Map.of("latitude", 47.6062, "longitude", -122.3321)));
```
### Weather Alerts Tool
- Name: `getAlerts`
- Description: Get weather alerts for a US state
- Parameters:
- `state`: String - Two-letter US state code (e.g. CA, NY)
- Example:
```java
CallToolResult alertResult = client.callTool(new CallToolRequest("getAlerts",
Map.of("state", "NY")));
```
## Server Implementation
The server uses Spring Boot and Spring AI's tool annotations for automatic tool registration:
```java
@SpringBootApplication
public class McpServerApplication {
public static void main(String[] args) {
SpringApplication.run(McpServerApplication.class, args);
}
@Bean
public List<ToolCallback> weatherTools(WeatherService weatherService) {
return List.of(ToolCallbacks.from(weatherService));
}
}
```
The `WeatherService` implements the weather tools using the `@Tool` annotation:
```java
@Service
public class WeatherService {
@Tool(description = "Get weather forecast for a specific latitude/longitude")
public String getWeatherForecastByLocation(double latitude, double longitude) {
// Implementation using weather.gov API
}
@Tool(description = "Get weather alerts for a US state. Input is Two-letter US state code (e.g. CA, NY)")
public String getAlerts(String state) {
// Implementation using weather.gov API
}
}
```
## Client Usage Example
```java
// Initialize client
client.initialize();
// Test connection
client.ping();
// List available tools
ListToolsResult tools = client.listTools();
System.out.println("Available tools: " + tools);
// Get weather forecast for Seattle
CallToolResult weatherForcastResult = client.callTool(new CallToolRequest("getWeatherForecastByLocation",
Map.of("latitude", 47.6062, "longitude", -122.3321)));
System.out.println("Weather Forecast: " + weatherForcastResult);
// Get weather alerts for New York
CallToolResult alertResult = client.callTool(new CallToolRequest("getAlerts", Map.of("state", "NY")));
System.out.println("Alert Response = " + alertResult);
// Close client
client.closeGracefully();
```
## Additional Resources
* [Spring AI Documentation](https://docs.spring.io/spring-ai/reference/)
* [Model Context Protocol Specification](https://modelcontextprotocol.github.io/specification/)
* [Spring Boot Auto-configuration](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.developing-auto-configuration)

Some files were not shown because too many files have changed in this diff Show More