changes
This commit is contained in:
parent
091d4654c8
commit
5938c8150e
1 changed files with 1086 additions and 454 deletions
678
quickstart.mdx
678
quickstart.mdx
|
|
@ -5,15 +5,6 @@ description: "Get started with building your first MCP server and connecting it
|
|||
|
||||
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.
|
||||
|
||||
### What is MCP?
|
||||
|
||||
MCP is an open protocol that standardizes how applications provide context to LLMs. Think of it as the "Language Server Protocol for AI" - it separates the concerns of providing context from the LLM interaction itself. This separation enables:
|
||||
|
||||
1. **Modularity**: Each MCP server focuses on one specific integration or capability
|
||||
2. **Reusability**: The same server can be used with any MCP-compatible client
|
||||
3. **Composability**: Multiple servers can be combined to create powerful workflows
|
||||
4. **Standardization**: A common protocol for all integrations reduces complexity
|
||||
|
||||
### What we'll be building
|
||||
|
||||
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!
|
||||
|
|
@ -45,20 +36,21 @@ MCP servers can provide three main types of capabilities:
|
|||
|
||||
This tutorial focuses on tools, but we have intermediate tutorials if you'd like to learn more about Resources and Prompts.
|
||||
|
||||
<Tabs>
|
||||
<Tab title='Python'>
|
||||
|
||||
### Prerequisite knowledge
|
||||
|
||||
This quickstart assumes you have familiarity with:
|
||||
- Python or TypeScript
|
||||
- Python
|
||||
- LLMs like Claude
|
||||
|
||||
### System requirements
|
||||
|
||||
For Python, make sure you have Python 3.9 or higher installed. For TypeScript, make sure you have the latest version of Node installed.
|
||||
For Python, make sure you have Python 3.9 or higher installed.
|
||||
|
||||
### Set up your environment
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Python">
|
||||
First, let's install `uv` and set up our Python project and environment:
|
||||
|
||||
<CodeGroup>
|
||||
|
|
@ -75,22 +67,8 @@ powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | ie
|
|||
|
||||
Make sure to restart your terminal afterwards to ensure that the `uv` command gets picked up.
|
||||
|
||||
</Tab>
|
||||
<Tab title="Node">
|
||||
First, let's install Node.js and npm if you haven't already. You can download them from [nodejs.org](https://nodejs.org/).
|
||||
Verify your Node.js installation:
|
||||
```bash
|
||||
node --version
|
||||
npm --version
|
||||
```
|
||||
For this tutorial, you'll need Node.js version 16 or higher.
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
Now, let's create and set up our project:
|
||||
|
||||
<Tabs>
|
||||
<Tab title='Python'>
|
||||
<CodeGroup>
|
||||
```bash MacOS/Linux
|
||||
# Create a new directory for our project
|
||||
|
|
@ -162,9 +140,354 @@ def main():
|
|||
# Optionally expose other important items at package level
|
||||
__all__ = ['main', 'server']
|
||||
```
|
||||
</Tab>
|
||||
|
||||
Now let's dive into building your server.
|
||||
|
||||
## Building your server
|
||||
|
||||
### Importing packages
|
||||
|
||||
Add these to the top of your `server.py`:
|
||||
```python
|
||||
from typing import Any
|
||||
import asyncio
|
||||
import httpx
|
||||
from mcp.server.models import InitializationOptions
|
||||
import mcp.types as types
|
||||
from mcp.server import NotificationOptions, Server
|
||||
import mcp.server.stdio
|
||||
```
|
||||
|
||||
### Setting up the instance
|
||||
|
||||
Then initialize the server instance and the base URL for the NWS API:
|
||||
|
||||
```python
|
||||
NWS_API_BASE = "https://api.weather.gov"
|
||||
USER_AGENT = "weather-app/1.0"
|
||||
|
||||
server = Server("weather")
|
||||
```
|
||||
|
||||
### Implementing tool listing
|
||||
|
||||
We need to tell clients what tools are available. The `list_tools()` decorator registers this handler:
|
||||
|
||||
```python
|
||||
@server.list_tools()
|
||||
async def handle_list_tools() -> list[types.Tool]:
|
||||
"""
|
||||
List available tools.
|
||||
Each tool specifies its arguments using JSON Schema validation.
|
||||
"""
|
||||
return [
|
||||
types.Tool(
|
||||
name="get-alerts",
|
||||
description="Get weather alerts for a state",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"state": {
|
||||
"type": "string",
|
||||
"description": "Two-letter state code (e.g. CA, NY)",
|
||||
},
|
||||
},
|
||||
"required": ["state"],
|
||||
},
|
||||
),
|
||||
types.Tool(
|
||||
name="get-forecast",
|
||||
description="Get weather forecast for a location",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"latitude": {
|
||||
"type": "number",
|
||||
"description": "Latitude of the location",
|
||||
},
|
||||
"longitude": {
|
||||
"type": "number",
|
||||
"description": "Longitude of the location",
|
||||
},
|
||||
},
|
||||
"required": ["latitude", "longitude"],
|
||||
},
|
||||
),
|
||||
]
|
||||
|
||||
```
|
||||
|
||||
This defines our two tools: `get-alerts` and `get-forecast`.
|
||||
|
||||
### Helper functions
|
||||
|
||||
Next, let's add our helper functions for querying and formatting the data from the National Weather Service API:
|
||||
|
||||
```python
|
||||
async def make_nws_request(client: httpx.AsyncClient, url: str) -> dict[str, Any] | None:
|
||||
"""Make a request to the NWS API with proper error handling."""
|
||||
headers = {
|
||||
"User-Agent": USER_AGENT,
|
||||
"Accept": "application/geo+json"
|
||||
}
|
||||
|
||||
try:
|
||||
response = await client.get(url, headers=headers, timeout=30.0)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def format_alert(feature: dict) -> str:
|
||||
"""Format an alert feature into a concise string."""
|
||||
props = feature["properties"]
|
||||
return (
|
||||
f"Event: {props.get('event', 'Unknown')}\n"
|
||||
f"Area: {props.get('areaDesc', 'Unknown')}\n"
|
||||
f"Severity: {props.get('severity', 'Unknown')}\n"
|
||||
f"Status: {props.get('status', 'Unknown')}\n"
|
||||
f"Headline: {props.get('headline', 'No headline')}\n"
|
||||
"---"
|
||||
)
|
||||
```
|
||||
|
||||
### Implementing tool execution
|
||||
|
||||
The tool execution handler is responsible for actually executing the logic of each tool. Let's add it:
|
||||
|
||||
```python
|
||||
@server.call_tool()
|
||||
async def handle_call_tool(
|
||||
name: str, arguments: dict | None
|
||||
) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
|
||||
"""
|
||||
Handle tool execution requests.
|
||||
Tools can fetch weather data and notify clients of changes.
|
||||
"""
|
||||
if not arguments:
|
||||
raise ValueError("Missing arguments")
|
||||
|
||||
if name == "get-alerts":
|
||||
state = arguments.get("state")
|
||||
if not state:
|
||||
raise ValueError("Missing state parameter")
|
||||
|
||||
# Convert state to uppercase to ensure consistent format
|
||||
state = state.upper()
|
||||
if len(state) != 2:
|
||||
raise ValueError("State must be a two-letter code (e.g. CA, NY)")
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
alerts_url = f"{NWS_API_BASE}/alerts?area={state}"
|
||||
alerts_data = await make_nws_request(client, alerts_url)
|
||||
|
||||
if not alerts_data:
|
||||
return [types.TextContent(type="text", text="Failed to retrieve alerts data")]
|
||||
|
||||
features = alerts_data.get("features", [])
|
||||
if not features:
|
||||
return [types.TextContent(type="text", text=f"No active alerts for {state}")]
|
||||
|
||||
# Format each alert into a concise string
|
||||
formatted_alerts = [format_alert(feature) for feature in features[:20]] # only take the first 20 alerts
|
||||
alerts_text = f"Active alerts for {state}:\n\n" + "\n".join(formatted_alerts)
|
||||
|
||||
return [
|
||||
types.TextContent(
|
||||
type="text",
|
||||
text=alerts_text
|
||||
)
|
||||
]
|
||||
elif name == "get-forecast":
|
||||
try:
|
||||
latitude = float(arguments.get("latitude"))
|
||||
longitude = float(arguments.get("longitude"))
|
||||
except (TypeError, ValueError):
|
||||
return [types.TextContent(
|
||||
type="text",
|
||||
text="Invalid coordinates. Please provide valid numbers for latitude and longitude."
|
||||
)]
|
||||
|
||||
# Basic coordinate validation
|
||||
if not (-90 <= latitude <= 90) or not (-180 <= longitude <= 180):
|
||||
return [types.TextContent(
|
||||
type="text",
|
||||
text="Invalid coordinates. Latitude must be between -90 and 90, longitude between -180 and 180."
|
||||
)]
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
# First get the grid point
|
||||
lat_str = f"{latitude}"
|
||||
lon_str = f"{longitude}"
|
||||
points_url = f"{NWS_API_BASE}/points/{lat_str},{lon_str}"
|
||||
points_data = await make_nws_request(client, points_url)
|
||||
|
||||
if not points_data:
|
||||
return [types.TextContent(type="text", text=f"Failed to retrieve grid point data for coordinates: {latitude}, {longitude}. This location may not be supported by the NWS API (only US locations are supported).")]
|
||||
|
||||
# Extract forecast URL from the response
|
||||
properties = points_data.get("properties", {})
|
||||
forecast_url = properties.get("forecast")
|
||||
|
||||
if not forecast_url:
|
||||
return [types.TextContent(type="text", text="Failed to get forecast URL from grid point data")]
|
||||
|
||||
# Get the forecast
|
||||
forecast_data = await make_nws_request(client, forecast_url)
|
||||
|
||||
if not forecast_data:
|
||||
return [types.TextContent(type="text", text="Failed to retrieve forecast data")]
|
||||
|
||||
# Format the forecast periods
|
||||
periods = forecast_data.get("properties", {}).get("periods", [])
|
||||
if not periods:
|
||||
return [types.TextContent(type="text", text="No forecast periods available")]
|
||||
|
||||
# Format each period into a concise string
|
||||
formatted_forecast = []
|
||||
for period in periods:
|
||||
forecast_text = (
|
||||
f"{period.get('name', 'Unknown')}:\n"
|
||||
f"Temperature: {period.get('temperature', 'Unknown')}°{period.get('temperatureUnit', 'F')}\n"
|
||||
f"Wind: {period.get('windSpeed', 'Unknown')} {period.get('windDirection', '')}\n"
|
||||
f"{period.get('shortForecast', 'No forecast available')}\n"
|
||||
"---"
|
||||
)
|
||||
formatted_forecast.append(forecast_text)
|
||||
|
||||
forecast_text = f"Forecast for {latitude}, {longitude}:\n\n" + "\n".join(formatted_forecast)
|
||||
|
||||
return [types.TextContent(
|
||||
type="text",
|
||||
text=forecast_text
|
||||
)]
|
||||
else:
|
||||
raise ValueError(f"Unknown tool: {name}")
|
||||
```
|
||||
|
||||
### Running the server
|
||||
|
||||
Finally, implement the main function to run the server:
|
||||
|
||||
```python
|
||||
async def main():
|
||||
# Run the server using stdin/stdout streams
|
||||
async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
|
||||
await server.run(
|
||||
read_stream,
|
||||
write_stream,
|
||||
InitializationOptions(
|
||||
server_name="weather",
|
||||
server_version="0.1.0",
|
||||
capabilities=server.get_capabilities(
|
||||
notification_options=NotificationOptions(),
|
||||
experimental_capabilities={},
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
# This is needed if you'd like to connect to a custom client
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
Your server is complete! Run `uv run src/weather/server.py` to confirm that everything's working.
|
||||
|
||||
Let's now test your server from an existing MCP host, Claude for Desktop.
|
||||
|
||||
## Testing your server with Claude for Desktop
|
||||
|
||||
<Note>
|
||||
Unfortunately, Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](/tutorials/building-a-client) tutorial for a workaround.
|
||||
</Note>
|
||||
|
||||
First, make sure you have Claude for Desktop installed. [You can install the latest version here.](https://claude.ai/download)
|
||||
|
||||
Next, open your Claude Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor.
|
||||
|
||||
For example, if you have [VS Code](https://code.visualstudio.com/) installed:
|
||||
|
||||
<Tabs>
|
||||
<Tab title="MacOS/Linux">
|
||||
```bash
|
||||
code ~/Library/Application\ Support/Claude/claude_desktop_config.json
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Windows">
|
||||
```powershell
|
||||
code $env:AppData\Claude\claude_desktop_config.json
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
|
||||
Add this configuration (replace the parent folder path):
|
||||
|
||||
<Tabs>
|
||||
<Tab title="MacOS/Linux">
|
||||
```json Python
|
||||
{
|
||||
"mcpServers": {
|
||||
"weather": {
|
||||
"command": "uv",
|
||||
"args": [
|
||||
"--directory",
|
||||
"/PATH/TO/PARENT/FOLDER/weather",
|
||||
"run",
|
||||
"weather"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Windows">
|
||||
```json Python
|
||||
{
|
||||
"mcpServers": {
|
||||
"weather": {
|
||||
"command": "uv",
|
||||
"args": [
|
||||
"--directory",
|
||||
"C:\\PATH\TO\PARENT\FOLDER\weather",
|
||||
"run",
|
||||
"weather"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
This tells Claude Desktop:
|
||||
1. There's an MCP server named "weather"
|
||||
2. Launch it by running `uv --directory /PATH/TO/PARENT/FOLDER/weather run weather`
|
||||
|
||||
Save the file, and restart **Claude Desktop**.
|
||||
</Tab>
|
||||
<Tab title="Node">
|
||||
### Prerequisite knowledge
|
||||
|
||||
This quickstart assumes you have familiarity with:
|
||||
- TypeScript
|
||||
- LLMs like Claude
|
||||
|
||||
### System requirements
|
||||
|
||||
For TypeScript, make sure you have the latest version of Node installed.
|
||||
|
||||
### Set up your environment
|
||||
|
||||
First, let's install Node.js and npm if you haven't already. You can download them from [nodejs.org](https://nodejs.org/).
|
||||
Verify your Node.js installation:
|
||||
```bash
|
||||
node --version
|
||||
npm --version
|
||||
```
|
||||
For this tutorial, you'll need Node.js version 16 or higher.
|
||||
|
||||
Now, let's create and set up our project:
|
||||
|
||||
<CodeGroup>
|
||||
```bash MacOS/Linux
|
||||
|
|
@ -238,8 +561,6 @@ Create a `tsconfig.json` in the root of your project:
|
|||
"exclude": ["node_modules"]
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
Now let's dive into building your server.
|
||||
|
||||
|
|
@ -247,20 +568,6 @@ Now let's dive into building your server.
|
|||
|
||||
### Importing packages
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Python">
|
||||
Add these to the top of your `server.py`:
|
||||
```python
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
from mcp.server.models import InitializationOptions
|
||||
import mcp.types as types
|
||||
from mcp.server import NotificationOptions, Server
|
||||
import mcp.server.stdio
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Node">
|
||||
Add these to the top of your `src/index.ts`:
|
||||
|
||||
```typescript
|
||||
|
|
@ -272,23 +579,9 @@ import {
|
|||
} from "@modelcontextprotocol/sdk/types.js";
|
||||
import { z } from "zod";
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
### Setting up the instance
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Python">
|
||||
Then initialize the server instance and the base URL for the NWS API:
|
||||
|
||||
```python
|
||||
NWS_API_BASE = "https://api.weather.gov"
|
||||
USER_AGENT = "weather-app/1.0"
|
||||
|
||||
server = Server("weather")
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Node">
|
||||
Then initialize the NWS API base URL, validation schemas, and server instance:
|
||||
|
||||
```typescript
|
||||
|
|
@ -318,60 +611,9 @@ const server = new Server(
|
|||
}
|
||||
);
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
### Implementing tool listing
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Python">
|
||||
We need to tell clients what tools are available. The `list_tools()` decorator registers this handler:
|
||||
|
||||
```python
|
||||
@server.list_tools()
|
||||
async def handle_list_tools() -> list[types.Tool]:
|
||||
"""
|
||||
List available tools.
|
||||
Each tool specifies its arguments using JSON Schema validation.
|
||||
"""
|
||||
return [
|
||||
types.Tool(
|
||||
name="get-alerts",
|
||||
description="Get weather alerts for a state",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"state": {
|
||||
"type": "string",
|
||||
"description": "Two-letter state code (e.g. CA, NY)",
|
||||
},
|
||||
},
|
||||
"required": ["state"],
|
||||
},
|
||||
),
|
||||
types.Tool(
|
||||
name="get-forecast",
|
||||
description="Get weather forecast for a location",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"latitude": {
|
||||
"type": "number",
|
||||
"description": "Latitude of the location",
|
||||
},
|
||||
"longitude": {
|
||||
"type": "number",
|
||||
"description": "Longitude of the location",
|
||||
},
|
||||
},
|
||||
"required": ["latitude", "longitude"],
|
||||
},
|
||||
),
|
||||
]
|
||||
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Node">
|
||||
We need to tell clients what tools are available. This `server.setRequestHandler` call will register this list for us:
|
||||
|
||||
```typescript
|
||||
|
|
@ -415,8 +657,6 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|||
};
|
||||
});
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
This defines our two tools: `get-alerts` and `get-forecast`.
|
||||
|
||||
|
|
@ -424,37 +664,6 @@ This defines our two tools: `get-alerts` and `get-forecast`.
|
|||
|
||||
Next, let's add our helper functions for querying and formatting the data from the National Weather Service API:
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Python">
|
||||
```python
|
||||
async def make_nws_request(client: httpx.AsyncClient, url: str) -> dict[str, Any] | None:
|
||||
"""Make a request to the NWS API with proper error handling."""
|
||||
headers = {
|
||||
"User-Agent": USER_AGENT,
|
||||
"Accept": "application/geo+json"
|
||||
}
|
||||
|
||||
try:
|
||||
response = await client.get(url, headers=headers, timeout=30.0)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def format_alert(feature: dict) -> str:
|
||||
"""Format an alert feature into a concise string."""
|
||||
props = feature["properties"]
|
||||
return (
|
||||
f"Event: {props.get('event', 'Unknown')}\n"
|
||||
f"Area: {props.get('areaDesc', 'Unknown')}\n"
|
||||
f"Severity: {props.get('severity', 'Unknown')}\n"
|
||||
f"Status: {props.get('status', 'Unknown')}\n"
|
||||
f"Headline: {props.get('headline', 'No headline')}\n"
|
||||
"---"
|
||||
)
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Node">
|
||||
```typescript
|
||||
// Helper function for making NWS API requests
|
||||
async function makeNWSRequest<T>(url: string): Promise<T | null> {
|
||||
|
|
@ -523,127 +732,11 @@ interface ForecastResponse {
|
|||
};
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
### Implementing tool execution
|
||||
|
||||
The tool execution handler is responsible for actually executing the logic of each tool. Let's add it:
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Python">
|
||||
```python
|
||||
@server.call_tool()
|
||||
async def handle_call_tool(
|
||||
name: str, arguments: dict | None
|
||||
) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
|
||||
"""
|
||||
Handle tool execution requests.
|
||||
Tools can fetch weather data and notify clients of changes.
|
||||
"""
|
||||
if not arguments:
|
||||
raise ValueError("Missing arguments")
|
||||
|
||||
if name == "get-alerts":
|
||||
state = arguments.get("state")
|
||||
if not state:
|
||||
raise ValueError("Missing state parameter")
|
||||
|
||||
# Convert state to uppercase to ensure consistent format
|
||||
state = state.upper()
|
||||
if len(state) != 2:
|
||||
raise ValueError("State must be a two-letter code (e.g. CA, NY)")
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
alerts_url = f"{NWS_API_BASE}/alerts?area={state}"
|
||||
alerts_data = await make_nws_request(client, alerts_url)
|
||||
|
||||
if not alerts_data:
|
||||
return [types.TextContent(type="text", text="Failed to retrieve alerts data")]
|
||||
|
||||
features = alerts_data.get("features", [])
|
||||
if not features:
|
||||
return [types.TextContent(type="text", text=f"No active alerts for {state}")]
|
||||
|
||||
# Format each alert into a concise string
|
||||
formatted_alerts = [format_alert(feature) for feature in features[:20]] # only take the first 20 alerts
|
||||
alerts_text = f"Active alerts for {state}:\n\n" + "\n".join(formatted_alerts)
|
||||
|
||||
return [
|
||||
types.TextContent(
|
||||
type="text",
|
||||
text=alerts_text
|
||||
)
|
||||
]
|
||||
elif name == "get-forecast":
|
||||
try:
|
||||
latitude = float(arguments.get("latitude"))
|
||||
longitude = float(arguments.get("longitude"))
|
||||
except (TypeError, ValueError):
|
||||
return [types.TextContent(
|
||||
type="text",
|
||||
text="Invalid coordinates. Please provide valid numbers for latitude and longitude."
|
||||
)]
|
||||
|
||||
# Basic coordinate validation
|
||||
if not (-90 <= latitude <= 90) or not (-180 <= longitude <= 180):
|
||||
return [types.TextContent(
|
||||
type="text",
|
||||
text="Invalid coordinates. Latitude must be between -90 and 90, longitude between -180 and 180."
|
||||
)]
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
# First get the grid point
|
||||
lat_str = f"{latitude:.4f}"
|
||||
lon_str = f"{longitude:.4f}"
|
||||
points_url = f"{NWS_API_BASE}/points/{lat_str},{lon_str}"
|
||||
points_data = await make_nws_request(client, points_url)
|
||||
|
||||
if not points_data:
|
||||
return [types.TextContent(type="text", text=f"Failed to retrieve grid point data for coordinates: {latitude}, {longitude}. This location may not be supported by the NWS API (only US locations are supported).")]
|
||||
|
||||
# Extract forecast URL from the response
|
||||
properties = points_data.get("properties", {})
|
||||
forecast_url = properties.get("forecast")
|
||||
|
||||
if not forecast_url:
|
||||
return [types.TextContent(type="text", text="Failed to get forecast URL from grid point data")]
|
||||
|
||||
# Get the forecast
|
||||
forecast_data = await make_nws_request(client, forecast_url)
|
||||
|
||||
if not forecast_data:
|
||||
return [types.TextContent(type="text", text="Failed to retrieve forecast data")]
|
||||
|
||||
# Format the forecast periods
|
||||
periods = forecast_data.get("properties", {}).get("periods", [])
|
||||
if not periods:
|
||||
return [types.TextContent(type="text", text="No forecast periods available")]
|
||||
|
||||
# Format each period into a concise string
|
||||
formatted_forecast = []
|
||||
for period in periods:
|
||||
forecast_text = (
|
||||
f"{period.get('name', 'Unknown')}:\n"
|
||||
f"Temperature: {period.get('temperature', 'Unknown')}°{period.get('temperatureUnit', 'F')}\n"
|
||||
f"Wind: {period.get('windSpeed', 'Unknown')} {period.get('windDirection', '')}\n"
|
||||
f"{period.get('shortForecast', 'No forecast available')}\n"
|
||||
"---"
|
||||
)
|
||||
formatted_forecast.append(forecast_text)
|
||||
|
||||
forecast_text = f"Forecast for {latitude}, {longitude}:\n\n" + "\n".join(formatted_forecast)
|
||||
|
||||
return [types.TextContent(
|
||||
type="text",
|
||||
text=forecast_text
|
||||
)]
|
||||
else:
|
||||
raise ValueError(f"Unknown tool: {name}")
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Node">
|
||||
|
||||
```typescript
|
||||
// Handle tool execution
|
||||
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
|
|
@ -792,34 +885,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|||
}
|
||||
});
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
### Running the server
|
||||
|
||||
Finally, implement the main function to run the server:
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Python">
|
||||
```python
|
||||
async def main():
|
||||
# Run the server using stdin/stdout streams
|
||||
async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
|
||||
await server.run(
|
||||
read_stream,
|
||||
write_stream,
|
||||
InitializationOptions(
|
||||
server_name="weather",
|
||||
server_version="0.1.0",
|
||||
capabilities=server.get_capabilities(
|
||||
notification_options=NotificationOptions(),
|
||||
experimental_capabilities={},
|
||||
),
|
||||
),
|
||||
)
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Node">
|
||||
```typescript
|
||||
// Start the server
|
||||
async function main() {
|
||||
|
|
@ -835,10 +905,6 @@ main().catch((error) => {
|
|||
```
|
||||
|
||||
Make sure to run `npm run build` to build your server! This is a very important step in getting your server to connect.
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
Your server is complete! Run `uv run src/weather/server.py` to confirm that everything's working.
|
||||
|
||||
Let's now test your server from an existing MCP host, Claude for Desktop.
|
||||
|
||||
|
|
@ -873,22 +939,6 @@ Add this configuration (replace the parent folder path):
|
|||
<Tabs>
|
||||
<Tab title="MacOS/Linux">
|
||||
<CodeGroup>
|
||||
```json Python
|
||||
{
|
||||
"mcpServers": {
|
||||
"weather": {
|
||||
"command": "uv",
|
||||
"args": [
|
||||
"--directory",
|
||||
"/PATH/TO/PARENT/FOLDER/weather",
|
||||
"run",
|
||||
"weather"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```json Node
|
||||
{
|
||||
"mcpServers": {
|
||||
|
|
@ -905,45 +955,29 @@ Add this configuration (replace the parent folder path):
|
|||
</Tab>
|
||||
<Tab title="Windows">
|
||||
<CodeGroup>
|
||||
|
||||
```json Python
|
||||
{
|
||||
"mcpServers": {
|
||||
"weather": {
|
||||
"command": "uv",
|
||||
"args": [
|
||||
"--directory",
|
||||
"C:\\PATH\TO\PARENT\FOLDER\weather",
|
||||
"run",
|
||||
"weather"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```json Node
|
||||
{
|
||||
"mcpServers": {
|
||||
"weather": {
|
||||
"command": "node",
|
||||
"args": [
|
||||
"C:\\PATH\TO\PARENT\FOLDER\weather\build\index.ts"
|
||||
"C:\\PATH\TO\PARENT\FOLDER\weather\build\index.js"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
This tells Claude Desktop:
|
||||
1. There's an MCP server named "weather"
|
||||
2. Launch it by running `uv --directory /PATH/TO/PARENT/FOLDER/weather run weather` if using Python or `node /PATH/TO/PARENT/FOLDER/weather/build/index.js` if using Node
|
||||
2. Launch it by running `node /PATH/TO/PARENT/FOLDER/weather/build/index.js`
|
||||
|
||||
Save the file, and restart **Claude Desktop**.
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
### Test with commands
|
||||
|
||||
|
|
@ -1039,7 +1073,7 @@ If Claude attempts to use the tools but they fail:
|
|||
icon="outlet"
|
||||
href="/tutorials/building-a-client"
|
||||
>
|
||||
Learn how to build your first MCP client
|
||||
Learn how to build your an MCP client that can connect to your server
|
||||
</Card>
|
||||
<Card
|
||||
title="Building a local fileserver"
|
||||
|
|
|
|||
Loading…
Reference in a new issue