Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Quickstart (C#) -- GetForecast needs to make two client api calls #228

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 37 additions & 19 deletions quickstart/server.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -747,12 +747,12 @@ Save the file, and restart **Claude for Desktop**.
<Tab title='Java'>

<Note>
This is a quickstart demo based on Spring AI MCP auto-configuration and boot starters.
This is a quickstart demo based on Spring AI MCP auto-configuration and boot starters.
To learn how to create sync and async MCP Servers, manually, consult the [Java SDK Server](/sdk/java/mcp-server) documentation.
</Note>


Let's get started with building our weather server!
Let's get started with building our weather server!
[You can find the complete code for what we'll be building here.](https://github.com/spring-projects/spring-ai-examples/tree/main/model-context-protocol/weather/starter-stdio-server)

For more information, see the [MCP Server Boot Starter](https://docs.spring.io/spring-ai/reference/api/mcp/mcp-server-boot-starter-docs.html) reference documentation.
Expand Down Expand Up @@ -789,7 +789,7 @@ You will need to add the following dependencies:
```groovy
dependencies {
implementation platform("org.springframework.ai:spring-ai-starter-mcp-server")
implementation platform("org.springframework:spring-web")
implementation platform("org.springframework:spring-web")
}
```
</Tab>
Expand Down Expand Up @@ -848,7 +848,7 @@ public class WeatherService {
// - Wind speed and direction
// - Detailed forecast description
}

@Tool(description = "Get weather alerts for a US state")
public String getAlerts(
@ToolParam(description = "Two-letter US state code (e.g. CA, NY") String state)
Expand All @@ -868,7 +868,7 @@ public class WeatherService {
The `@Service` annotation with auto-register the service in your application context.
The Spring AI `@Tool` annotation, making it easy to create and maintain MCP tools.

The auto-configuration will automatically register these tools with the MCP server.
The auto-configuration will automatically register these tools with the MCP server.

### Create your Boot Application

Expand Down Expand Up @@ -907,11 +907,11 @@ Let's now test your server from an existing MCP host, Claude for Desktop.
Claude for Desktop is not yet available on Linux.
</Note>

First, make sure you have Claude for Desktop installed.
First, make sure you have Claude for Desktop installed.
[You can install the latest version here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**

We'll need to configure Claude for Desktop for whichever MCP servers you want to use.
To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor.
We'll need to configure Claude for Desktop for whichever MCP servers you want to use.
To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor.
Make sure to create the file if it doesn't exist.

For example, if you have [VS Code](https://code.visualstudio.com/) installed:
Expand All @@ -929,7 +929,7 @@ For example, if you have [VS Code](https://code.visualstudio.com/) installed:
</Tab>
</Tabs>

You'll then add your servers in the `mcpServers` key.
You'll then add your servers in the `mcpServers` key.
The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.

In this case, we'll add our single weather server like so:
Expand Down Expand Up @@ -1033,7 +1033,7 @@ For more information, see the [MCP Client Boot Starters](https://docs.spring.io/

## More Java MCP Server examples

The [starter-webflux-server](https://github.com/spring-projects/spring-ai-examples/tree/main/model-context-protocol/weather/starter-webflux-server) demonstrates how to create a MCP server using SSE transport.
The [starter-webflux-server](https://github.com/spring-projects/spring-ai-examples/tree/main/model-context-protocol/weather/starter-webflux-server) demonstrates how to create a MCP server using SSE transport.
It showcases how to define and register MCP Tools, Resources, and Prompts, using the Spring Boot's auto-configuration capabilities.

</Tab>
Expand Down Expand Up @@ -1497,13 +1497,19 @@ using ModelContextProtocol.Server;
using System.ComponentModel;
using System.Net.Http.Json;
using System.Text.Json;
using System.Globalization;

namespace QuickstartWeatherServer.Tools;

[McpServerToolType]
public static class WeatherTools
{
[McpServerTool, Description("Get weather alerts for a US state.")]
[McpServerTool,
Description(@"
Get weather alerts for a US state.
Args:
State: Two-letter US state code (e.g. CA, NY)
Comment on lines +1510 to +1511
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You shouldn't need to add args info here, it should be inferred from the parameters (and their description). If that info isn't getting surfaced correctly, we should look at improving the SDK rather than requiring a duplication of info.

Copy link
Author

@mikekidder mikekidder Apr 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without the additional description, the model was attempting to pass the full state name to the weather api resulting in a 404. Ex. "Give me the current alerts in Kansas"

The addition replicates what the python sample did.. and it worked.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree with @aaronpowell, this additional info can be added in the description of the state parameter. For example:
public static async Task<string> GetAlerts(HttpClient client, [Description("The US state to get alerts for. Use two-letter US state code (e.g. CA, NY).")] string state)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't disagree that this is important information, more that it shouldn't be in the description of the tool, but as @puran1218 points out, it should be on the description of the argument.

With Python, the reason it is that was is because they don't have argument annotations, in fact they don't use annotations in the same way we use attributes (providing the description), instead they use the doc comments, which they regex at runtime to extract the description/args info from.

")]
public static async Task<string> GetAlerts(
HttpClient client,
[Description("The US state to get alerts for.")] string state)
Expand All @@ -1516,29 +1522,41 @@ public static class WeatherTools
return "No active alerts for this state.";
}

return string.Join("\n--\n", alerts.Select(alert =>
{
JsonElement properties = alert.GetProperty("properties");
return $"""
return string.Join("\n--\n",
alerts.Select(alert =>
{
JsonElement properties = alert.GetProperty("properties");
return $"""
Event: {properties.GetProperty("event").GetString()}
Area: {properties.GetProperty("areaDesc").GetString()}
Severity: {properties.GetProperty("severity").GetString()}
Description: {properties.GetProperty("description").GetString()}
Instruction: {properties.GetProperty("instruction").GetString()}
""";
}));
}));
}

[McpServerTool, Description("Get weather forecast for a location.")]
[McpServerTool,
Description(@"
Get weather forecast for a location.
Args:
Latitude: Latitude of the location.
Longitude: Longitude of the location.
")]
public static async Task<string> GetForecast(
HttpClient client,
[Description("Latitude of the location.")] double latitude,
[Description("Longitude of the location.")] double longitude)
{
var jsonElement = await client.GetFromJsonAsync<JsonElement>($"/points/{latitude},{longitude}");
var pointUrl = string.Format(CultureInfo.InvariantCulture, "points/{0},{1}", latitude, longitude);
var jsonElement = await client.GetFromJsonAsync<JsonElement>(pointUrl);
var forecastUrl = jsonElement.GetProperty("properties").GetProperty("forecast").GetString();

jsonElement = await client.GetFromJsonAsync<JsonElement>(forecastUrl);
var periods = jsonElement.GetProperty("properties").GetProperty("periods").EnumerateArray();

return string.Join("\n---\n", periods.Select(period => $"""
return string.Join("\n---\n",
periods.Select(period => $"""
{period.GetProperty("name").GetString()}
Temperature: {period.GetProperty("temperature").GetInt32()}°F
Wind: {period.GetProperty("windSpeed").GetString()} {period.GetProperty("windDirection").GetString()}
Expand Down