Get started building your own server to use in Claude for Desktop and other clients.
In this tutorial, we’ll build a simple MCP weather server and connect it to a host, Claude for Desktop. We’ll start with a basic setup, and then progress to more complex use cases.
Many LLMs (including Claude) do not currently have the ability to fetch the forecast and severe weather alerts. Let’s use MCP to solve that!
We’ll build a server that exposes two tools: get-alerts and get-forecast. Then we’ll connect the server to an MCP host (in this case, Claude for Desktop):
The tool execution handler is responsible for actually executing the logic of each tool. Let’s add it:
Copy
@mcp.tool()async def get_alerts(state: str) -> str: """Get weather alerts for a US state. Args: state: Two-letter US state code (e.g. CA, NY) """ url = f"{NWS_API_BASE}/alerts/active/area/{state}" data = await make_nws_request(url) if not data or "features" not in data: return "Unable to fetch alerts or no alerts found." if not data["features"]: return "No active alerts for this state." alerts = [format_alert(feature) for feature in data["features"]] return "\n---\n".join(alerts)@mcp.tool()async def get_forecast(latitude: float, longitude: float) -> str: """Get weather forecast for a location. Args: latitude: Latitude of the location longitude: Longitude of the location """ # First get the forecast grid endpoint points_url = f"{NWS_API_BASE}/points/{latitude},{longitude}" points_data = await make_nws_request(points_url) if not points_data: return "Unable to fetch forecast data for this location." # Get the forecast URL from the points response forecast_url = points_data["properties"]["forecast"] forecast_data = await make_nws_request(forecast_url) if not forecast_data: return "Unable to fetch detailed forecast." # Format the periods into a readable forecast periods = forecast_data["properties"]["periods"] forecasts = [] for period in periods[:5]: # Only show next 5 periods forecast = f"""{period['name']}:Temperature: {period['temperature']}°{period['temperatureUnit']}Wind: {period['windSpeed']} {period['windDirection']}Forecast: {period['detailedForecast']}""" forecasts.append(forecast) return "\n---\n".join(forecasts)
Claude for Desktop is not yet available on Linux. Linux users can proceed to the Building a client tutorial to build an MCP client that connects to the server we just built.
First, make sure you have Claude for Desktop installed. You can install the latest version
here. 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. Make sure to create the file if it doesn’t exist.
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:
You may need to put the full path to the uv executable in the command field. You can get this by running which uv on MacOS/Linux or where uv on Windows.
Make sure you pass in the absolute path to your server.
This tells Claude for Desktop:
There’s an MCP server named “weather”
To launch it by running uv --directory /ABSOLUTE/PATH/TO/PARENT/FOLDER/weather run weather
The tool execution handler is responsible for actually executing the logic of each tool. Let’s add it:
Copy
@mcp.tool()async def get_alerts(state: str) -> str: """Get weather alerts for a US state. Args: state: Two-letter US state code (e.g. CA, NY) """ url = f"{NWS_API_BASE}/alerts/active/area/{state}" data = await make_nws_request(url) if not data or "features" not in data: return "Unable to fetch alerts or no alerts found." if not data["features"]: return "No active alerts for this state." alerts = [format_alert(feature) for feature in data["features"]] return "\n---\n".join(alerts)@mcp.tool()async def get_forecast(latitude: float, longitude: float) -> str: """Get weather forecast for a location. Args: latitude: Latitude of the location longitude: Longitude of the location """ # First get the forecast grid endpoint points_url = f"{NWS_API_BASE}/points/{latitude},{longitude}" points_data = await make_nws_request(points_url) if not points_data: return "Unable to fetch forecast data for this location." # Get the forecast URL from the points response forecast_url = points_data["properties"]["forecast"] forecast_data = await make_nws_request(forecast_url) if not forecast_data: return "Unable to fetch detailed forecast." # Format the periods into a readable forecast periods = forecast_data["properties"]["periods"] forecasts = [] for period in periods[:5]: # Only show next 5 periods forecast = f"""{period['name']}:Temperature: {period['temperature']}°{period['temperatureUnit']}Wind: {period['windSpeed']} {period['windDirection']}Forecast: {period['detailedForecast']}""" forecasts.append(forecast) return "\n---\n".join(forecasts)
Claude for Desktop is not yet available on Linux. Linux users can proceed to the Building a client tutorial to build an MCP client that connects to the server we just built.
First, make sure you have Claude for Desktop installed. You can install the latest version
here. 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. Make sure to create the file if it doesn’t exist.
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:
You may need to put the full path to the uv executable in the command field. You can get this by running which uv on MacOS/Linux or where uv on Windows.
Make sure you pass in the absolute path to your server.
This tells Claude for Desktop:
There’s an MCP server named “weather”
To launch it by running uv --directory /ABSOLUTE/PATH/TO/PARENT/FOLDER/weather run weather
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";import { z } from "zod";const NWS_API_BASE = "https://api.weather.gov";const USER_AGENT = "weather-app/1.0";// Create server instanceconst server = new McpServer({ name: "weather", version: "1.0.0",});
Finally, implement the main function to run the server:
Copy
async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error("Weather MCP Server running on stdio");}main().catch((error) => { console.error("Fatal error in main():", error); process.exit(1);});
Make sure to run npm run build to build your server! This is a very important step in getting your server to connect.
Let’s now test your server from an existing MCP host, Claude for Desktop.
Claude for Desktop is not yet available on Linux. Linux users can proceed to the Building a client tutorial to build an MCP client that connects to the server we just built.
First, make sure you have Claude for Desktop installed. You can install the latest version
here. 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. Make sure to create the file if it doesn’t exist.
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:
Launch it by running node /ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/build/index.js
Save the file, and restart Claude for Desktop.
This is a quickstart demo based on Spring AI MCP auto-configuraiton and boot starters.
To learn how to create sync and async MCP Servers, manually, consult the Java SDK Server documentation.
Let’s implement a WeatheService.java that uses a REST client to query the data from the National Weather Service API:
Copy
@Servicepublic class WeatherService { private final RestClient restClient; public WeatherService() { this.restClient = RestClient.builder() .baseUrl("https://api.weather.gov") .defaultHeader("Accept", "application/geo+json") .defaultHeader("User-Agent", "WeatherApiClient/1.0 (your@email.com)") .build(); } @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 } @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) ) { // Returns active alerts including: // - Event type // - Affected area // - Severity // - Description // - Safety instructions } // ......}
The @Service annotation with auto-register the service in your applicaiton 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.
First, make sure you have Claude for Desktop installed.
You can install the latest version here. 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.
Make sure to create the file if it doesn’t exist.
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:
and set the spring.ai.mcp.client.stdio.servers-configuration property to point to your claude_desktop_config.json.
You can re-use the existing Anthropic Destop configuration:
The 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.