Skip to content

Commit bc3d399

Browse files
committed
Update weather server to use McpServer convenience API
1 parent 0f60e53 commit bc3d399

File tree

1 file changed

+118
-189
lines changed

1 file changed

+118
-189
lines changed

weather-server-typescript/src/index.ts

Lines changed: 118 additions & 189 deletions
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,10 @@
1-
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
1+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
22
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3-
import {
4-
CallToolRequestSchema,
5-
ListToolsRequestSchema,
6-
} from "@modelcontextprotocol/sdk/types.js";
73
import { z } from "zod";
84

95
const NWS_API_BASE = "https://api.weather.gov";
106
const USER_AGENT = "weather-app/1.0";
117

12-
// Define Zod schemas for validation
13-
const AlertsArgumentsSchema = z.object({
14-
state: z.string().length(2),
15-
});
16-
17-
const ForecastArgumentsSchema = z.object({
18-
latitude: z.number().min(-90).max(90),
19-
longitude: z.number().min(-180).max(180),
20-
});
21-
22-
// Create server instance
23-
const server = new Server(
24-
{
25-
name: "weather",
26-
version: "1.0.0",
27-
},
28-
{
29-
capabilities: {
30-
tools: {},
31-
},
32-
}
33-
);
34-
35-
// List available tools
36-
server.setRequestHandler(ListToolsRequestSchema, async () => {
37-
return {
38-
tools: [
39-
{
40-
name: "get-alerts",
41-
description: "Get weather alerts for a state",
42-
inputSchema: {
43-
type: "object",
44-
properties: {
45-
state: {
46-
type: "string",
47-
description: "Two-letter state code (e.g. CA, NY)",
48-
},
49-
},
50-
required: ["state"],
51-
},
52-
},
53-
{
54-
name: "get-forecast",
55-
description: "Get weather forecast for a location",
56-
inputSchema: {
57-
type: "object",
58-
properties: {
59-
latitude: {
60-
type: "number",
61-
description: "Latitude of the location",
62-
},
63-
longitude: {
64-
type: "number",
65-
description: "Longitude of the location",
66-
},
67-
},
68-
required: ["latitude", "longitude"],
69-
},
70-
},
71-
],
72-
};
73-
});
74-
758
// Helper function for making NWS API requests
769
async function makeNWSRequest<T>(url: string): Promise<T | null> {
7710
const headers = {
@@ -139,152 +72,148 @@ interface ForecastResponse {
13972
};
14073
}
14174

142-
// Handle tool execution
143-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
144-
const { name, arguments: args } = request.params;
145-
146-
try {
147-
if (name === "get-alerts") {
148-
const { state } = AlertsArgumentsSchema.parse(args);
149-
const stateCode = state.toUpperCase();
150-
151-
const alertsUrl = `${NWS_API_BASE}/alerts?area=${stateCode}`;
152-
const alertsData = await makeNWSRequest<AlertsResponse>(alertsUrl);
153-
154-
if (!alertsData) {
155-
return {
156-
content: [
157-
{
158-
type: "text",
159-
text: "Failed to retrieve alerts data",
160-
},
161-
],
162-
};
163-
}
164-
165-
const features = alertsData.features || [];
166-
if (features.length === 0) {
167-
return {
168-
content: [
169-
{
170-
type: "text",
171-
text: `No active alerts for ${stateCode}`,
172-
},
173-
],
174-
};
175-
}
75+
// Create server instance
76+
const server = new McpServer({
77+
name: "weather",
78+
version: "1.0.0",
79+
});
17680

177-
const formattedAlerts = features.map(formatAlert);
178-
const alertsText = `Active alerts for ${stateCode}:\n\n${formattedAlerts.join(
179-
"\n"
180-
)}`;
81+
// Register weather tools
82+
server.tool(
83+
"get-alerts",
84+
"Get weather alerts for a state",
85+
{
86+
state: z.string().length(2).describe("Two-letter state code (e.g. CA, NY)"),
87+
},
88+
async ({ state }) => {
89+
const stateCode = state.toUpperCase();
90+
const alertsUrl = `${NWS_API_BASE}/alerts?area=${stateCode}`;
91+
const alertsData = await makeNWSRequest<AlertsResponse>(alertsUrl);
18192

93+
if (!alertsData) {
18294
return {
18395
content: [
18496
{
18597
type: "text",
186-
text: alertsText,
98+
text: "Failed to retrieve alerts data",
18799
},
188100
],
189101
};
190-
} else if (name === "get-forecast") {
191-
const { latitude, longitude } = ForecastArgumentsSchema.parse(args);
192-
193-
// Get grid point data
194-
const pointsUrl = `${NWS_API_BASE}/points/${latitude.toFixed(
195-
4
196-
)},${longitude.toFixed(4)}`;
197-
const pointsData = await makeNWSRequest<PointsResponse>(pointsUrl);
102+
}
198103

199-
if (!pointsData) {
200-
return {
201-
content: [
202-
{
203-
type: "text",
204-
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).`,
205-
},
206-
],
207-
};
208-
}
104+
const features = alertsData.features || [];
105+
if (features.length === 0) {
106+
return {
107+
content: [
108+
{
109+
type: "text",
110+
text: `No active alerts for ${stateCode}`,
111+
},
112+
],
113+
};
114+
}
209115

210-
const forecastUrl = pointsData.properties?.forecast;
211-
if (!forecastUrl) {
212-
return {
213-
content: [
214-
{
215-
type: "text",
216-
text: "Failed to get forecast URL from grid point data",
217-
},
218-
],
219-
};
220-
}
116+
const formattedAlerts = features.map(formatAlert);
117+
const alertsText = `Active alerts for ${stateCode}:\n\n${formattedAlerts.join("\n")}`;
221118

222-
// Get forecast data
223-
const forecastData = await makeNWSRequest<ForecastResponse>(forecastUrl);
224-
if (!forecastData) {
225-
return {
226-
content: [
227-
{
228-
type: "text",
229-
text: "Failed to retrieve forecast data",
230-
},
231-
],
232-
};
233-
}
119+
return {
120+
content: [
121+
{
122+
type: "text",
123+
text: alertsText,
124+
},
125+
],
126+
};
127+
},
128+
);
234129

235-
const periods = forecastData.properties?.periods || [];
236-
if (periods.length === 0) {
237-
return {
238-
content: [
239-
{
240-
type: "text",
241-
text: "No forecast periods available",
242-
},
243-
],
244-
};
245-
}
130+
server.tool(
131+
"get-forecast",
132+
"Get weather forecast for a location",
133+
{
134+
latitude: z.number().min(-90).max(90).describe("Latitude of the location"),
135+
longitude: z
136+
.number()
137+
.min(-180)
138+
.max(180)
139+
.describe("Longitude of the location"),
140+
},
141+
async ({ latitude, longitude }) => {
142+
// Get grid point data
143+
const pointsUrl = `${NWS_API_BASE}/points/${latitude.toFixed(4)},${longitude.toFixed(4)}`;
144+
const pointsData = await makeNWSRequest<PointsResponse>(pointsUrl);
246145

247-
// Format forecast periods
248-
const formattedForecast = periods.map((period: ForecastPeriod) =>
249-
[
250-
`${period.name || "Unknown"}:`,
251-
`Temperature: ${period.temperature || "Unknown"}°${
252-
period.temperatureUnit || "F"
253-
}`,
254-
`Wind: ${period.windSpeed || "Unknown"} ${
255-
period.windDirection || ""
256-
}`,
257-
`${period.shortForecast || "No forecast available"}`,
258-
"---",
259-
].join("\n")
260-
);
146+
if (!pointsData) {
147+
return {
148+
content: [
149+
{
150+
type: "text",
151+
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).`,
152+
},
153+
],
154+
};
155+
}
261156

262-
const forecastText = `Forecast for ${latitude}, ${longitude}:\n\n${formattedForecast.join(
263-
"\n"
264-
)}`;
157+
const forecastUrl = pointsData.properties?.forecast;
158+
if (!forecastUrl) {
159+
return {
160+
content: [
161+
{
162+
type: "text",
163+
text: "Failed to get forecast URL from grid point data",
164+
},
165+
],
166+
};
167+
}
265168

169+
// Get forecast data
170+
const forecastData = await makeNWSRequest<ForecastResponse>(forecastUrl);
171+
if (!forecastData) {
266172
return {
267173
content: [
268174
{
269175
type: "text",
270-
text: forecastText,
176+
text: "Failed to retrieve forecast data",
271177
},
272178
],
273179
};
274-
} else {
275-
throw new Error(`Unknown tool: ${name}`);
276180
}
277-
} catch (error) {
278-
if (error instanceof z.ZodError) {
279-
throw new Error(
280-
`Invalid arguments: ${error.errors
281-
.map((e) => `${e.path.join(".")}: ${e.message}`)
282-
.join(", ")}`
283-
);
181+
182+
const periods = forecastData.properties?.periods || [];
183+
if (periods.length === 0) {
184+
return {
185+
content: [
186+
{
187+
type: "text",
188+
text: "No forecast periods available",
189+
},
190+
],
191+
};
284192
}
285-
throw error;
286-
}
287-
});
193+
194+
// Format forecast periods
195+
const formattedForecast = periods.map((period: ForecastPeriod) =>
196+
[
197+
`${period.name || "Unknown"}:`,
198+
`Temperature: ${period.temperature || "Unknown"}°${period.temperatureUnit || "F"}`,
199+
`Wind: ${period.windSpeed || "Unknown"} ${period.windDirection || ""}`,
200+
`${period.shortForecast || "No forecast available"}`,
201+
"---",
202+
].join("\n"),
203+
);
204+
205+
const forecastText = `Forecast for ${latitude}, ${longitude}:\n\n${formattedForecast.join("\n")}`;
206+
207+
return {
208+
content: [
209+
{
210+
type: "text",
211+
text: forecastText,
212+
},
213+
],
214+
};
215+
},
216+
);
288217

289218
// Start the server
290219
async function main() {

0 commit comments

Comments
 (0)