tested on mac for both python and node

This commit is contained in:
Zachary Lee 2024-12-10 12:58:14 -05:00
commit de7df3fd78
No known key found for this signature in database
7 changed files with 703 additions and 85 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

BIN
images/current-weather.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 756 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

BIN
images/weather-alerts.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

View file

@ -1,11 +1,9 @@
--- ---
title: "Quickstart" title: "Quickstart"
description: "Get started with building your first MCP server and connecting it to an MCP host" description: "Get started with building your first MCP server and connecting it to a host"
--- ---
In this tutorial, we'll build a simple MCP server with a `get_weather` tool and connect it to a popular MCP host, Claude for Desktop. We'll start with a basic setup, and then progress to more complex use cases. 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.
You can think of MCP as a bridge between your LLMs and your data and tools. Model Context Protocol was developed by Anthropic (the makers of [Claude](https://claude.ai)), but MCP can work with any Large Language Model provider that supports tool calling.
### What is MCP? ### What is MCP?
@ -18,13 +16,20 @@ MCP is an open protocol that standardizes how applications provide context to LL
### What we'll be building ### What we'll be building
Many LLMs (including Claude) do not currently have the ability to fetch the current temperature and weather conditions. Let's use MCP to solve that! 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 Desktop):
<Frame> <Frame>
<img src="/images/quickstart-weather.png" /> <img src="/images/weather-alerts.png" />
</Frame>
<Frame>
<img src="/images/current-weather.png" />
</Frame> </Frame>
Servers can connect to any host. We'll walk through using Claude Desktop as the host and eventually show you how to build your own. <Note>
Servers can connect to any host. We've chosen Claude desktop here for simplicity, but we also have guides on [building your own client](/tutorials/building-a-client)
</Note>
<Accordion title="Why Claude Desktop and not Claude.ai?"> <Accordion title="Why Claude Desktop and not Claude.ai?">
Because servers are locally run, MCP currently only supports desktop hosts. Remote hosts are in active development. Because servers are locally run, MCP currently only supports desktop hosts. Remote hosts are in active development.
@ -50,7 +55,7 @@ It also helps to be familiar with [tool use (aka function calling)](https://docs
### System requirements ### System requirements
For Python, make sure you have the latest version of Python 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. For TypeScript, make sure you have the latest version of Node installed.
### Set up your environment ### Set up your environment
@ -64,11 +69,14 @@ First, let's install `uv` and set up our Python project and environment:
curl -LsSf https://astral.sh/uv/install.sh | sh curl -LsSf https://astral.sh/uv/install.sh | sh
``` ```
```bash Windows ```powershell Windows
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
``` ```
</CodeGroup> </CodeGroup>
Make sure to restart your terminal afterwards to ensure that the `uv` command gets picked up.
</Tab> </Tab>
<Tab title="Node"> <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/). First, let's install Node.js and npm if you haven't already. You can download them from [nodejs.org](https://nodejs.org/).
@ -98,17 +106,16 @@ source .venv/bin/activate
# Install dependencies # Install dependencies
uv pip install mcp httpx uv pip install mcp httpx
# Clean up boilerplate code # Remove template file
rm -rf hello.py rm hello.py
# Create our files # Create our files
mkdir src mkdir -p src/weather
mkdir src/weather
touch src/weather/__init__.py touch src/weather/__init__.py
touch src/weather/server.py touch src/weather/server.py
``` ```
```bash Windows ```powershell Windows
# Create a new directory for our project # Create a new directory for our project
uv init mcp-quickstart uv init mcp-quickstart
cd mcp-quickstart cd mcp-quickstart
@ -121,13 +128,13 @@ uv venv
uv pip install mcp httpx uv pip install mcp httpx
# Clean up boilerplate code # Clean up boilerplate code
del /f /q hello.py rm hello.py
# Create our files # Create our files
md src md src
md src\weather md src\weather
echo.> src\__init__.py new-item src\weather\__init__.py
echo.> src\server.py new-item src\weather\server.py
``` ```
</CodeGroup> </CodeGroup>
@ -160,7 +167,9 @@ __all__ = ['main', 'server']
</Tab> </Tab>
<Tab title="Node"> <Tab title="Node">
```bash
<CodeGroup>
```bash MacOS/Linux
# Create a new directory for our project # Create a new directory for our project
mkdir mcp-quickstart mkdir mcp-quickstart
cd mcp-quickstart cd mcp-quickstart
@ -169,23 +178,39 @@ cd mcp-quickstart
npm init -y npm init -y
# Install dependencies # Install dependencies
npm install @modelcontextprotocol/sdk node-fetch npm install @modelcontextprotocol/sdk zod
npm install -D @types/node typescript npm install -D @types/node typescript
```
Then create a new file called `src/index.ts` in your project directory: # Create our files
```bash
mkdir src mkdir src
touch src/index.ts touch src/index.ts
``` ```
```powershell Windows
# Create a new directory for our project
md mcp-quickstart
cd mcp-quickstart
# Initialize a new npm project
npm init -y
# Install dependencies
npm install @modelcontextprotocol/sdk zod
npm install -D @types/node typescript
# Create our files
md src
new-item src\index.ts
```
</CodeGroup>
Update your package.json to add type: "module" and a build script: Update your package.json to add type: "module" and a build script:
```json package.json ```json package.json
{ {
"type": "module", "type": "module",
"bin": { "bin": {
"mcp-quickstart": "./build/index.js" "weather": "./build/index.js"
}, },
"scripts": { "scripts": {
"build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"", "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
@ -196,7 +221,7 @@ Update your package.json to add type: "module" and a build script:
} }
``` ```
Create a tsconfig.json: Create a `tsconfig.json` in the root of your project:
```json tsconfig.json ```json tsconfig.json
{ {
@ -224,113 +249,563 @@ Now let's dive into building your server.
### Importing packages ### Importing packages
Add these to your server.py: <Tabs>
<Tab title="Python">
Add these to the top of your `server.py`:
```python ```python
from typing import Any from typing import Any
import httpx import httpx
from mcp.server.models import InitializationOptions from mcp.server.models import InitializationOptions
import mcp.types as types import mcp.types as types
from mcp.server import NotificationOptions, Server from mcp.server import NotificationOptions, Server
import mcp.server.stdio import mcp.server.stdio
``` ```
</Tab>
<Tab title="Node">
Add these to the top of your `src/index.ts`:
```typescript
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { z } from "zod";
```
</Tab>
</Tabs>
### Setting up the instance ### Setting up the instance
Then add the cache, server instance and the base URL for the NWS API: <Tabs>
<Tab title="Python">
Then initialize the server instance and the base URL for the NWS API:
```python ```python
# Cache weather data
weather_cache: dict[str, Any] = {}
NWS_API_BASE = "https://api.weather.gov" NWS_API_BASE = "https://api.weather.gov"
USER_AGENT = "weather-app/1.0"
server = Server("weather") server = Server("weather")
``` ```
</Tab>
<Tab title="Node">
Then initialize the NWS API base URL, validation schemas, and server instance:
```typescript
const NWS_API_BASE = "https://api.weather.gov";
const USER_AGENT = "weather-app/1.0";
// Define Zod schemas for validation
const AlertsArgumentsSchema = z.object({
state: z.string().length(2),
});
const ForecastArgumentsSchema = z.object({
latitude: z.number().min(-90).max(90),
longitude: z.number().min(-180).max(180),
});
// Create server instance
const server = new Server(
{
name: "weather",
version: "1.0.0",
},
{
capabilities: {
tools: {},
},
}
);
```
</Tab>
</Tabs>
### Implementing tool listing ### Implementing tool listing
<Tabs>
<Tab title="Python">
We need to tell clients what tools are available. The `list_tools()` decorator registers this handler: We need to tell clients what tools are available. The `list_tools()` decorator registers this handler:
```python ```python
@server.list_tools() @server.list_tools()
async def handle_list_tools() -> list[types.Tool]: async def handle_list_tools() -> list[types.Tool]:
"""
List available tools.
Each tool specifies its arguments using JSON Schema validation.
"""
return [ return [
types.Tool( types.Tool(
name="get-weather", name="get-alerts",
description="Get weather data for a location", description="Get weather alerts for a state",
inputSchema={ inputSchema={
"type": "object", "type": "object",
"properties": { "properties": {
"latitude": {"type": "number"}, "state": {
"longitude": {"type": "number"}, "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"], "required": ["latitude", "longitude"],
}, },
) ),
] ]
```
This defines a single tool that requires latitude and longitude parameters. ```
</Tab>
<Tab title="Node">
We need to tell clients what tools are available. This `server.setRequestHandler` call will register this list for us:
```typescript
// List available tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
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"],
},
},
{
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>
</Tabs>
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:
<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> {
const headers = {
"User-Agent": USER_AGENT,
Accept: "application/geo+json",
};
try {
const response = await fetch(url, { headers });
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return (await response.json()) as T;
} catch (error) {
console.error("Error making NWS request:", error);
return null;
}
}
interface AlertFeature {
properties: {
event?: string;
areaDesc?: string;
severity?: string;
status?: string;
headline?: string;
};
}
// Format alert data
function formatAlert(feature: AlertFeature): string {
const props = feature.properties;
return [
`Event: ${props.event || "Unknown"}`,
`Area: ${props.areaDesc || "Unknown"}`,
`Severity: ${props.severity || "Unknown"}`,
`Status: ${props.status || "Unknown"}`,
`Headline: ${props.headline || "No headline"}`,
"---",
].join("\n");
}
interface ForecastPeriod {
name?: string;
temperature?: number;
temperatureUnit?: string;
windSpeed?: string;
windDirection?: string;
shortForecast?: string;
}
interface AlertsResponse {
features: AlertFeature[];
}
interface PointsResponse {
properties: {
forecast?: string;
};
}
interface ForecastResponse {
properties: {
periods: ForecastPeriod[];
};
}
```
</Tab>
</Tabs>
### Implementing tool execution ### Implementing tool execution
The tool execution handler processes requests to get weather data: The tool execution handler is responsible for actually executing the logic of each tool. Let's add it:
<Tabs>
<Tab title="Python">
```python ```python
@server.call_tool() @server.call_tool()
async def handle_call_tool( async def handle_call_tool(
name: str, name: str, arguments: dict | None
arguments: dict | None
) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]: ) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
# Validate tool name and arguments """
if name != "get-weather": Handle tool execution requests.
raise ValueError(f"Unknown tool: {name}") Tools can fetch weather data and notify clients of changes.
"""
if not arguments: if not arguments:
raise ValueError("Missing arguments") raise ValueError("Missing arguments")
lat = arguments.get("latitude") if name == "get-alerts":
lon = arguments.get("longitude") state = arguments.get("state")
if lat is None or lon is None: if not state:
raise ValueError("Missing latitude or longitude") raise ValueError("Missing state parameter")
# Fetch weather data # Convert state to uppercase to ensure consistent format
async with httpx.AsyncClient() as client: state = state.upper()
# Get grid point if len(state) != 2:
points_url = f"{NWS_API_BASE}/points/{lat},{lon}" raise ValueError("State must be a two-letter code (e.g. CA, NY)")
points_response = await client.get(
points_url,
headers={"Accept": "application/json"}
)
points_data = points_response.json()
# Get forecast async with httpx.AsyncClient() as client:
forecast_url = points_data["properties"]["forecast"] alerts_url = f"{NWS_API_BASE}/alerts?area={state}"
forecast_response = await client.get( alerts_data = await make_nws_request(client, alerts_url)
forecast_url,
headers={"Accept": "application/json"}
)
forecast_data = forecast_response.json()
# Cache the forecast data if not alerts_data:
location_key = f"{lat},{lon}" return [types.TextContent(type="text", text="Failed to retrieve alerts data")]
weather_cache[location_key] = forecast_data
# Notify clients of resource changes features = alerts_data.get("features", [])
await server.request_context.session.send_resource_list_changed() if not features:
return [types.TextContent(type="text", text=f"No active alerts for {state}")]
return [ # Format each alert into a concise string
types.TextContent( formatted_alerts = [format_alert(feature) for feature in features]
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", type="text",
text=f"Retrieved weather data for location ({lat}, {lon})", 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) => {
const { name, arguments: args } = request.params;
try {
if (name === "get-alerts") {
const { state } = AlertsArgumentsSchema.parse(args);
const stateCode = state.toUpperCase();
const alertsUrl = `${NWS_API_BASE}/alerts?area=${stateCode}`;
const alertsData = await makeNWSRequest<AlertsResponse>(alertsUrl);
if (!alertsData) {
return {
content: [
{
type: "text",
text: "Failed to retrieve alerts data",
},
],
};
}
const features = alertsData.features || [];
if (features.length === 0) {
return {
content: [
{
type: "text",
text: `No active alerts for ${stateCode}`,
},
],
};
}
const formattedAlerts = features.map(formatAlert);
const alertsText = `Active alerts for ${stateCode}:\n\n${formattedAlerts.join(
"\n"
)}`;
return {
content: [
{
type: "text",
text: alertsText,
},
],
};
} else if (name === "get-forecast") {
const { latitude, longitude } = ForecastArgumentsSchema.parse(args);
// Get grid point data
const pointsUrl = `${NWS_API_BASE}/points/${latitude.toFixed(
4
)},${longitude.toFixed(4)}`;
const pointsData = await makeNWSRequest<PointsResponse>(pointsUrl);
if (!pointsData) {
return {
content: [
{
type: "text",
text: `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).`,
},
],
};
}
const forecastUrl = pointsData.properties?.forecast;
if (!forecastUrl) {
return {
content: [
{
type: "text",
text: "Failed to get forecast URL from grid point data",
},
],
};
}
// Get forecast data
const forecastData = await makeNWSRequest<ForecastResponse>(forecastUrl);
if (!forecastData) {
return {
content: [
{
type: "text",
text: "Failed to retrieve forecast data",
},
],
};
}
const periods = forecastData.properties?.periods || [];
if (periods.length === 0) {
return {
content: [
{
type: "text",
text: "No forecast periods available",
},
],
};
}
// Format forecast periods
const formattedForecast = periods.map((period: ForecastPeriod) =>
[
`${period.name || "Unknown"}:`,
`Temperature: ${period.temperature || "Unknown"}°${
period.temperatureUnit || "F"
}`,
`Wind: ${period.windSpeed || "Unknown"} ${
period.windDirection || ""
}`,
`${period.shortForecast || "No forecast available"}`,
"---",
].join("\n")
);
const forecastText = `Forecast for ${latitude}, ${longitude}:\n\n${formattedForecast.join(
"\n"
)}`;
return {
content: [
{
type: "text",
text: forecastText,
},
],
};
} else {
throw new Error(`Unknown tool: ${name}`);
}
} catch (error) {
if (error instanceof z.ZodError) {
throw new Error(
`Invalid arguments: ${error.errors
.map((e) => `${e.path.join(".")}: ${e.message}`)
.join(", ")}`
);
}
throw error;
}
});
```
</Tab>
</Tabs>
### Running the server ### Running the server
Finally, implement the main function to run the server: Finally, implement the main function to run the server:
<Tabs>
<Tab title="Python">
```python ```python
async def main(): async def main():
# Run the server using stdin/stdout streams
async with mcp.server.stdio.stdio_server() as (read_stream, write_stream): async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
await server.run( await server.run(
read_stream, read_stream,
@ -345,6 +820,25 @@ async def main():
), ),
) )
``` ```
</Tab>
<Tab title="Node">
```typescript
// Start the server
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.
</Tab>
</Tabs>
Your server is complete! Let's now test your server from an existing MCP host, Claude for Desktop. Your server is complete! Let's now test your server from an existing MCP host, Claude for Desktop.
@ -356,7 +850,7 @@ Unfortunately, Claude for Desktop is not yet available on Linux. Linux users can
First, make sure you have Claude for Desktop installed. [You can install the latest version here.](https://claude.ai/download) First, make sure you have Claude for Desktop installed. [You can install the latest version here.](https://claude.ai/download)
Open your Claude Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor. 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: For example, if you have [VS Code](https://code.visualstudio.com/) installed:
@ -378,7 +872,8 @@ Add this configuration (replace the parent folder path):
<Tabs> <Tabs>
<Tab title="MacOS/Linux"> <Tab title="MacOS/Linux">
```json <CodeGroup>
```json Python
{ {
"mcpServers": { "mcpServers": {
"weather": { "weather": {
@ -393,9 +888,25 @@ Add this configuration (replace the parent folder path):
} }
} }
``` ```
```json Node
{
"mcpServers": {
"weather": {
"command": "node",
"args": [
"/PATH/TO/PARENT/FOLDER/mcp-quickstart/build/index.js",
]
}
}
}
```
</CodeGroup>
</Tab> </Tab>
<Tab title="Windows"> <Tab title="Windows">
```json <CodeGroup>
```json Python
{ {
"mcpServers": { "mcpServers": {
"weather": { "weather": {
@ -410,24 +921,56 @@ Add this configuration (replace the parent folder path):
} }
} }
``` ```
```json Node
{
"mcpServers": {
"weather": {
"command": "node",
"args": [
"C:\\PATH\TO\PARENT\FOLDER\mcp-quickstart\build\index.ts",
]
}
}
}
```
</CodeGroup>
</Tab> </Tab>
</Tabs> </Tabs>
This tells Claude Desktop: This tells Claude Desktop:
1. There's an MCP server named "weather" 1. There's an MCP server named "weather"
2. Launch it by running `uv --directory /PATH/TO/PARENT/FOLDER/mcp-quickstart run weather` 2. Launch it by running `uv --directory /PATH/TO/PARENT/FOLDER/mcp-quickstart run weather` if using Python or `node /PATH/TO/PARENT/FOLDER/mcp-quickstart/build/index.js` if using Node
Save the file, and restart **Claude Desktop**. Save the file, and restart **Claude Desktop**.
### Test with commands ### Test with commands
You can now test your server by running the following commands in Claude Desktop: First, make sure Claude Desktop is picking up the two tools we've exposed in our `weather` server. You can do this by looking for the hammer <img src="/images/claude-desktop-mcp-hammer-icon.svg" style={{display: 'inline', margin: 0, height: '1.3em'}} /> icon:
- What's the weather in Brooklyn?
- What's the weather in San Francisco?
<Frame> <Frame>
<img src="/images/quickstart-weather.png" /> <img src="/images/visual-indicator-mcp-tools.png" />
</Frame>
After clicking on the hammer icon, you should see two tools listed:
<Frame>
<img src="/images/available-mcp-tools.png" />
</Frame>
If your server isn't being picked up by Claude Desktop, proceed to the **Troubleshooting** section for debugging advice.
You can now test your server by running the following commands in Claude Desktop:
- What's the weather in Sacramento?
- What are the active weather alerts in Texas?
<Frame>
<img src="/images/current-weather.png" />
</Frame>
<Frame>
<img src="/images/weather-alerts.png" />
</Frame> </Frame>
<Note> <Note>
@ -445,4 +988,79 @@ When you ask a question:
5. Claude formulates a natural language response 5. Claude formulates a natural language response
6. The response is displayed to you! 6. The response is displayed to you!
## Troubleshooting
<AccordionGroup>
<Accordion title="Weather API Issues">
**Error: Failed to retrieve grid point data**
This usually means either:
1. The coordinates are outside the US
2. The NWS API is having issues
3. You're being rate limited
Fix:
- Verify you're using US coordinates
- Add a small delay between requests
- Check the NWS API status page
**Error: No active alerts for [STATE]**
This isn't an error - it just means there are no current weather alerts for that state. Try a different state or check during severe weather.
</Accordion>
<Accordion title="Claude Desktop Integration Issues">
**Server not showing up in Claude**
1. Check your configuration file syntax
2. Make sure the path to your project is correct
3. Restart Claude Desktop completely
You can also check Claude's logs for errors like so:
```bash
# Check Claude's logs for errors
tail -f ~/Library/Application\ Support/Claude/Claude.log
```
**Tool calls failing silently**
If Claude attempts to use the tools but they fail:
1. Check Claude's logs for errors
2. Verify your server runs without errors
3. Try restarting Claude Desktop
</Accordion>
</AccordionGroup>
## Next steps ## Next steps
<CardGroup cols={2}>
<Card
title="Building a client"
icon="outlet"
href="/tutorials/building-a-client"
>
Learn how to build your first MCP client
</Card>
<Card
title="Building a local fileserver"
icon="files"
href="/examples"
>
Build an intermediate MCP server that communicates with your local filesystem
</Card>
<Card
title="Example servers"
icon="grid"
href="/examples"
>
Check out our gallery of official MCP servers and implementations
</Card>
<Card
title="Core architecture"
icon="sitemap"
href="/docs/concepts/architecture"
>
Understand how MCP connects clients, servers, and LLMs
</Card>
</CardGroup>