Update weather server to use McpServer convenience API

This commit is contained in:
Justin Spahr-Summers 2025-01-23 15:45:03 +00:00
commit bc3d399a57
No known key found for this signature in database

View file

@ -1,77 +1,10 @@
import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { z } from "zod"; import { z } from "zod";
const NWS_API_BASE = "https://api.weather.gov"; const NWS_API_BASE = "https://api.weather.gov";
const USER_AGENT = "weather-app/1.0"; 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: {},
},
}
);
// 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"],
},
},
],
};
});
// Helper function for making NWS API requests // Helper function for making NWS API requests
async function makeNWSRequest<T>(url: string): Promise<T | null> { async function makeNWSRequest<T>(url: string): Promise<T | null> {
const headers = { const headers = {
@ -139,15 +72,21 @@ interface ForecastResponse {
}; };
} }
// Handle tool execution // Create server instance
server.setRequestHandler(CallToolRequestSchema, async (request) => { const server = new McpServer({
const { name, arguments: args } = request.params; name: "weather",
version: "1.0.0",
});
try { // Register weather tools
if (name === "get-alerts") { server.tool(
const { state } = AlertsArgumentsSchema.parse(args); "get-alerts",
"Get weather alerts for a state",
{
state: z.string().length(2).describe("Two-letter state code (e.g. CA, NY)"),
},
async ({ state }) => {
const stateCode = state.toUpperCase(); const stateCode = state.toUpperCase();
const alertsUrl = `${NWS_API_BASE}/alerts?area=${stateCode}`; const alertsUrl = `${NWS_API_BASE}/alerts?area=${stateCode}`;
const alertsData = await makeNWSRequest<AlertsResponse>(alertsUrl); const alertsData = await makeNWSRequest<AlertsResponse>(alertsUrl);
@ -175,9 +114,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
} }
const formattedAlerts = features.map(formatAlert); const formattedAlerts = features.map(formatAlert);
const alertsText = `Active alerts for ${stateCode}:\n\n${formattedAlerts.join( const alertsText = `Active alerts for ${stateCode}:\n\n${formattedAlerts.join("\n")}`;
"\n"
)}`;
return { return {
content: [ content: [
@ -187,13 +124,23 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
}, },
], ],
}; };
} else if (name === "get-forecast") { },
const { latitude, longitude } = ForecastArgumentsSchema.parse(args); );
server.tool(
"get-forecast",
"Get weather forecast for a location",
{
latitude: z.number().min(-90).max(90).describe("Latitude of the location"),
longitude: z
.number()
.min(-180)
.max(180)
.describe("Longitude of the location"),
},
async ({ latitude, longitude }) => {
// Get grid point data // Get grid point data
const pointsUrl = `${NWS_API_BASE}/points/${latitude.toFixed( const pointsUrl = `${NWS_API_BASE}/points/${latitude.toFixed(4)},${longitude.toFixed(4)}`;
4
)},${longitude.toFixed(4)}`;
const pointsData = await makeNWSRequest<PointsResponse>(pointsUrl); const pointsData = await makeNWSRequest<PointsResponse>(pointsUrl);
if (!pointsData) { if (!pointsData) {
@ -248,20 +195,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
const formattedForecast = periods.map((period: ForecastPeriod) => const formattedForecast = periods.map((period: ForecastPeriod) =>
[ [
`${period.name || "Unknown"}:`, `${period.name || "Unknown"}:`,
`Temperature: ${period.temperature || "Unknown"}°${ `Temperature: ${period.temperature || "Unknown"}°${period.temperatureUnit || "F"}`,
period.temperatureUnit || "F" `Wind: ${period.windSpeed || "Unknown"} ${period.windDirection || ""}`,
}`,
`Wind: ${period.windSpeed || "Unknown"} ${
period.windDirection || ""
}`,
`${period.shortForecast || "No forecast available"}`, `${period.shortForecast || "No forecast available"}`,
"---", "---",
].join("\n") ].join("\n"),
); );
const forecastText = `Forecast for ${latitude}, ${longitude}:\n\n${formattedForecast.join( const forecastText = `Forecast for ${latitude}, ${longitude}:\n\n${formattedForecast.join("\n")}`;
"\n"
)}`;
return { return {
content: [ content: [
@ -271,20 +212,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
}, },
], ],
}; };
} 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;
}
});
// Start the server // Start the server
async function main() { async function main() {