features) {
+
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public record Feature(@JsonProperty("properties") Properties properties) {
+ }
+
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public record Properties(@JsonProperty("event") String event, @JsonProperty("areaDesc") String areaDesc,
+ @JsonProperty("severity") String severity, @JsonProperty("description") String description,
+ @JsonProperty("instruction") String instruction) {
+ }
+ }
+
+ /**
+ * Get forecast for a specific latitude/longitude
+ * @param latitude Latitude
+ * @param longitude Longitude
+ * @return The forecast for the given location
+ * @throws RestClientException if the request fails
+ */
+ @Tool(description = "Get weather forecast for a specific latitude/longitude")
+ public String getWeatherForecastByLocation(double latitude, double longitude) {
+
+ var points = restClient.get()
+ .uri("/points/{latitude},{longitude}", latitude, longitude)
+ .retrieve()
+ .body(Points.class);
+
+ var forecast = restClient.get().uri(points.properties().forecast()).retrieve().body(Forecast.class);
+
+ String forecastText = forecast.properties().periods().stream().map(p -> {
+ return String.format("""
+ %s:
+ Temperature: %s %s
+ Wind: %s %s
+ Forecast: %s
+ """, p.name(), p.temperature(), p.temperatureUnit(), p.windSpeed(), p.windDirection(),
+ p.detailedForecast());
+ }).collect(Collectors.joining());
+
+ return forecastText;
+ }
+
+ /**
+ * Get alerts for a specific area
+ * @param state Area code. Two-letter US state code (e.g. CA, NY)
+ * @return Human readable alert information
+ * @throws RestClientException if the request fails
+ */
+ @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) {
+ Alert alert = restClient.get().uri("/alerts/active/area/{state}", state).retrieve().body(Alert.class);
+
+ return alert.features()
+ .stream()
+ .map(f -> String.format("""
+ Event: %s
+ Area: %s
+ Severity: %s
+ Description: %s
+ Instructions: %s
+ """, f.properties().event(), f.properties.areaDesc(), f.properties.severity(),
+ f.properties.description(), f.properties.instruction()))
+ .collect(Collectors.joining("\n"));
+ }
+
+ public static void main(String[] args) {
+ WeatherService client = new WeatherService();
+ System.out.println(client.getWeatherForecastByLocation(47.6062, -122.3321));
+ System.out.println(client.getAlerts("NY"));
+ }
+
+}
\ No newline at end of file
diff --git a/model-context-protocol/mcp-annotations-server/src/main/resources/application.properties b/model-context-protocol/mcp-annotations-server/src/main/resources/application.properties
new file mode 100644
index 0000000..e1ab7a4
--- /dev/null
+++ b/model-context-protocol/mcp-annotations-server/src/main/resources/application.properties
@@ -0,0 +1,13 @@
+# spring.main.web-application-type=none
+
+# 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=
+
+# spring.ai.mcp.server.stdio=false
+
+spring.ai.mcp.server.name=my-weather-server
+spring.ai.mcp.server.version=0.0.1
+
+logging.file.name=./model-context-protocol/weather/starter-webmvc-server/target/starter-webmvc-server.log
diff --git a/model-context-protocol/mcp-annotations-server/src/test/java/org/springframework/ai/mcp/sample/client/ClientSse.java b/model-context-protocol/mcp-annotations-server/src/test/java/org/springframework/ai/mcp/sample/client/ClientSse.java
new file mode 100644
index 0000000..a74e653
--- /dev/null
+++ b/model-context-protocol/mcp-annotations-server/src/test/java/org/springframework/ai/mcp/sample/client/ClientSse.java
@@ -0,0 +1,33 @@
+/*
+* Copyright 2024 - 2024 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.sample.client;
+
+import io.modelcontextprotocol.client.transport.HttpClientSseClientTransport;
+
+
+/**
+ * @author Christian Tzolov
+ */
+public class ClientSse {
+
+ public static void main(String[] args) {
+
+ var transport = HttpClientSseClientTransport.builder("http://localhost:8080").build();
+
+ new SampleClient(transport).run();
+ }
+
+}
diff --git a/model-context-protocol/mcp-annotations-server/src/test/java/org/springframework/ai/mcp/sample/client/ClientStdio.java b/model-context-protocol/mcp-annotations-server/src/test/java/org/springframework/ai/mcp/sample/client/ClientStdio.java
new file mode 100644
index 0000000..4343ab0
--- /dev/null
+++ b/model-context-protocol/mcp-annotations-server/src/test/java/org/springframework/ai/mcp/sample/client/ClientStdio.java
@@ -0,0 +1,49 @@
+/*
+* Copyright 2024 - 2024 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.sample.client;
+
+import java.io.File;
+
+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
+ * have to build the server jar first:
+ *
+ *
+ * ./mvnw clean install -DskipTests
+ *
+ */
+public class ClientStdio {
+
+ public static void main(String[] args) {
+
+ System.out.println(new File(".").getAbsolutePath());
+
+ var stdioParams = ServerParameters.builder("java")
+ .args("-Dspring.ai.mcp.server.stdio=true", "-Dspring.main.web-application-type=none",
+ "-Dlogging.pattern.console=", "-jar",
+ "model-context-protocol/weather/starter-webmvc-server/target/mcp-weather-starter-webmvc-server-0.0.1-SNAPSHOT.jar")
+ .build();
+
+ var transport = new StdioClientTransport(stdioParams);
+
+ new SampleClient(transport).run();
+ }
+
+}
diff --git a/model-context-protocol/mcp-annotations-server/src/test/java/org/springframework/ai/mcp/sample/client/SampleClient.java b/model-context-protocol/mcp-annotations-server/src/test/java/org/springframework/ai/mcp/sample/client/SampleClient.java
new file mode 100644
index 0000000..2ef43c3
--- /dev/null
+++ b/model-context-protocol/mcp-annotations-server/src/test/java/org/springframework/ai/mcp/sample/client/SampleClient.java
@@ -0,0 +1,97 @@
+/*
+* Copyright 2024 - 2024 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.sample.client;
+
+import java.util.Map;
+
+import io.modelcontextprotocol.client.McpClient;
+import io.modelcontextprotocol.spec.McpClientTransport;
+import io.modelcontextprotocol.spec.McpSchema.CallToolRequest;
+import io.modelcontextprotocol.spec.McpSchema.CallToolResult;
+import io.modelcontextprotocol.spec.McpSchema.CompleteRequest;
+import io.modelcontextprotocol.spec.McpSchema.CompleteResult;
+import io.modelcontextprotocol.spec.McpSchema.GetPromptRequest;
+import io.modelcontextprotocol.spec.McpSchema.GetPromptResult;
+import io.modelcontextprotocol.spec.McpSchema.ListToolsResult;
+import io.modelcontextprotocol.spec.McpSchema.ReadResourceRequest;
+import io.modelcontextprotocol.spec.McpSchema.ReadResourceResult;
+import io.modelcontextprotocol.spec.McpSchema.ResourceReference;
+import io.modelcontextprotocol.spec.McpSchema.PromptReference;
+
+/**
+ * @author Christian Tzolov
+ */
+
+public class SampleClient {
+
+ private final McpClientTransport transport;
+
+ public SampleClient(McpClientTransport transport) {
+ this.transport = transport;
+ }
+
+ public void run() {
+
+ var client = McpClient.sync(this.transport)
+ .loggingConsumer(message -> {
+ System.out.println(">> Client Logging: " + message);
+ })
+ .build();
+
+ client.initialize();
+
+ client.ping();
+
+ // List and demonstrate tools
+ ListToolsResult toolsList = client.listTools();
+ System.out.println("Available Tools = " + toolsList);
+ toolsList.tools().stream().forEach(tool -> {
+ System.out.println("Tool: " + tool.name() + ", description: " + tool.description() + ", schema: "
+ + tool.inputSchema());
+ });
+
+ CallToolResult weatherForcastResult = client.callTool(new CallToolRequest("getWeatherForecastByLocation",
+ Map.of("latitude", "47.6062", "longitude", "-122.3321")));
+ System.out.println("Weather Forcast: " + weatherForcastResult);
+
+
+ // // Resources
+ ReadResourceResult resource = client.readResource(new ReadResourceRequest("user-status://alice"));
+
+ System.out.println("Resource = " + resource);
+
+ // Prompts
+ GetPromptResult prompt = client.getPrompt(
+ new GetPromptRequest("personalized-message", Map.of("name", "Alice", "age", "14", "interests", "AI")));
+
+ System.out.println("Prompt = " + prompt);
+
+ // Completions
+ CompleteResult completion = client.completeCompletion(new CompleteRequest(new ResourceReference("user-status://{username}"),
+ new CompleteRequest.CompleteArgument("username", "a")));
+
+ System.out.println("Completion = " + completion);
+
+ CompleteResult completion2 = client.completeCompletion(new CompleteRequest(new PromptReference("personalized-message"),
+ new CompleteRequest.CompleteArgument("name", "a")));
+
+ System.out.println("Completion2 = " + completion2);
+
+ client.closeGracefully();
+
+ }
+
+}
diff --git a/pom.xml b/pom.xml
index 8fd1224..e21a373 100644
--- a/pom.xml
+++ b/pom.xml
@@ -41,6 +41,8 @@
model-context-protocol/web-search/brave-chatbot
model-context-protocol/sampling/mcp-weather-webmvc-server
model-context-protocol/sampling/mcp-sampling-client
+
+ model-context-protocol/mcp-annotations-server
model-context-protocol/dynamic-tool-update/client
model-context-protocol/dynamic-tool-update/server