Restructure
Signed-off-by: Christian Tzolov <christian.tzolov@broadcom.com>
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
package org.springframework.ai.mcp.sample.server;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.ai.tool.ToolCallback;
|
||||
import org.springframework.ai.tool.ToolCallbacks;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
@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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* 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.server;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import org.springframework.ai.tool.annotation.Tool;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.client.RestClient;
|
||||
import org.springframework.web.client.RestClientException;
|
||||
|
||||
@Service
|
||||
public class WeatherService {
|
||||
|
||||
private static final String BASE_URL = "https://api.weather.gov";
|
||||
|
||||
private final RestClient restClient;
|
||||
|
||||
public WeatherService() {
|
||||
|
||||
this.restClient = RestClient.builder()
|
||||
.baseUrl(BASE_URL)
|
||||
.defaultHeader("Accept", "application/geo+json")
|
||||
.defaultHeader("User-Agent", "WeatherApiClient/1.0 (your@email.com)")
|
||||
.build();
|
||||
}
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public record Points(@JsonProperty("properties") Props properties) {
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public record Props(@JsonProperty("forecast") String forecast) {
|
||||
}
|
||||
}
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public record Forecast(@JsonProperty("properties") Props properties) {
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public record Props(@JsonProperty("periods") List<Period> periods) {
|
||||
}
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public record Period(@JsonProperty("number") Integer number, @JsonProperty("name") String name,
|
||||
@JsonProperty("startTime") String startTime, @JsonProperty("endTime") String endTime,
|
||||
@JsonProperty("isDaytime") Boolean isDayTime, @JsonProperty("temperature") Integer temperature,
|
||||
@JsonProperty("temperatureUnit") String temperatureUnit,
|
||||
@JsonProperty("temperatureTrend") String temperatureTrend,
|
||||
@JsonProperty("probabilityOfPrecipitation") Map probabilityOfPrecipitation,
|
||||
@JsonProperty("windSpeed") String windSpeed, @JsonProperty("windDirection") String windDirection,
|
||||
@JsonProperty("icon") String icon, @JsonProperty("shortForecast") String shortForecast,
|
||||
@JsonProperty("detailedForecast") String detailedForecast) {
|
||||
}
|
||||
}
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public record Alert(@JsonProperty("features") List<Feature> 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"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
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.enabled=true
|
||||
|
||||
spring.ai.mcp.server.name=my-weather-server
|
||||
spring.ai.mcp.server.version=0.0.1
|
||||
|
||||
logging.file.name=./model-context-protocol/weather/mcp-weather-stdio-server/mcp-weather-server-quickstart/target/mcp-weather-stdio-server.log
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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.client.transport.ServerParameters;
|
||||
import io.modelcontextprotocol.client.transport.StdioClientTransport;
|
||||
import io.modelcontextprotocol.spec.McpSchema.CallToolRequest;
|
||||
import io.modelcontextprotocol.spec.McpSchema.CallToolResult;
|
||||
import io.modelcontextprotocol.spec.McpSchema.ListToolsResult;
|
||||
|
||||
/**
|
||||
* With stdio transport, the MCP server is automatically started by the client. But you
|
||||
* have to build the server jar first:
|
||||
*
|
||||
* <pre>
|
||||
* ./mvnw clean install -DskipTests
|
||||
* </pre>
|
||||
*/
|
||||
public class ClientStdio {
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
var stdioParams = ServerParameters.builder("java")
|
||||
.args("-jar",
|
||||
"model-context-protocol/weather/starter-stdio-server/target/mcp-weather-stdio-server-0.0.1-SNAPSHOT.jar")
|
||||
.build();
|
||||
|
||||
var transport = new StdioClientTransport(stdioParams);
|
||||
var client = McpClient.sync(transport).build();
|
||||
|
||||
client.initialize();
|
||||
|
||||
// List and demonstrate tools
|
||||
ListToolsResult toolsList = client.listTools();
|
||||
System.out.println("Available Tools = " + toolsList);
|
||||
|
||||
CallToolResult weatherForcastResult = client.callTool(new CallToolRequest("getWeatherForecastByLocation",
|
||||
Map.of("latitude", "47.6062", "longitude", "-122.3321")));
|
||||
System.out.println("Weather Forcast: " + weatherForcastResult);
|
||||
|
||||
CallToolResult alertResult = client.callTool(new CallToolRequest("getAlerts", Map.of("state", "NY")));
|
||||
System.out.println("Alert Response = " + alertResult);
|
||||
|
||||
client.closeGracefully();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user