diff --git a/weather-server-typescript/package-lock.json b/weather-server-typescript/package-lock.json index d378f50..f42d3cb 100644 --- a/weather-server-typescript/package-lock.json +++ b/weather-server-typescript/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@modelcontextprotocol/sdk": "^1.0.3" + "@modelcontextprotocol/sdk": "^1.4.0" }, "bin": { "weather": "build/index.js" @@ -20,13 +20,19 @@ } }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.0.3", - "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/@modelcontextprotocol/sdk/-/sdk-1.0.3.tgz", - "integrity": "sha512-2as3cX/VJ0YBHGmdv3GFyTpoM8q2gqE98zh3Vf1NwnsSY0h3mvoO07MUzfygCKkWsFjcZm4otIiqD6Xh7kiSBQ==", + "version": "1.4.0", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/@modelcontextprotocol/sdk/-/sdk-1.4.0.tgz", + "integrity": "sha512-50YTsT4H9PuqmgwXkr/BPl0ankfEfxqVtFG8I378XqUThasnYRdNwE2PqktiJNm5MncOf3s5q37juv6kMe6WZQ==", + "license": "MIT", "dependencies": { "content-type": "^1.0.5", + "eventsource": "^3.0.2", "raw-body": "^3.0.0", - "zod": "^3.23.8" + "zod": "^3.23.8", + "zod-to-json-schema": "^3.24.1" + }, + "engines": { + "node": ">=18" } }, "node_modules/@types/node": { @@ -62,6 +68,27 @@ "node": ">= 0.8" } }, + "node_modules/eventsource": { + "version": "3.0.2", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/eventsource/-/eventsource-3.0.2.tgz", + "integrity": "sha512-YolzkJNxsTL3tCJMWFxpxtG2sCjbZ4LQUBUrkdaJK0ub0p6lmJt+2+1SwhKjLc652lpH9L/79Ptez972H9tphw==", + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.0", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/eventsource-parser/-/eventsource-parser-3.0.0.tgz", + "integrity": "sha512-T1C0XCUimhxVQzW4zFipdx0SficT651NnkR0ZSH3yQwh+mFMdLfgjABVi4YtMTtaL4s168593DaoaRLMqryavA==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/http-errors/2.0.0/http-errors-2.0.0.tgz", @@ -161,12 +188,22 @@ } }, "node_modules/zod": { - "version": "3.23.8", - "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/zod/3.23.8/zod-3.23.8.tgz", - "integrity": "sha1-43uVe11SB5dp+4CXCZtZLw70Bn0=", + "version": "3.24.1", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/zod/-/zod-3.24.1.tgz", + "integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" } + }, + "node_modules/zod-to-json-schema": { + "version": "3.24.1", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/zod-to-json-schema/-/zod-to-json-schema-3.24.1.tgz", + "integrity": "sha512-3h08nf3Vw3Wl3PK+q3ow/lIil81IT2Oa7YpQyUUDsEWbXveMesdfK1xBd2RhCkynwZndAxixji/7SYJJowr62w==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.24.1" + } } } } diff --git a/weather-server-typescript/package.json b/weather-server-typescript/package.json index fc30375..bdb88ba 100644 --- a/weather-server-typescript/package.json +++ b/weather-server-typescript/package.json @@ -21,6 +21,6 @@ "typescript": "^5.7.2" }, "dependencies": { - "@modelcontextprotocol/sdk": "^1.0.3" + "@modelcontextprotocol/sdk": "^1.4.0" } } diff --git a/weather-server-typescript/src/index.ts b/weather-server-typescript/src/index.ts index 49e5788..c158be0 100644 --- a/weather-server-typescript/src/index.ts +++ b/weather-server-typescript/src/index.ts @@ -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 { - CallToolRequestSchema, - ListToolsRequestSchema, -} from "@modelcontextprotocol/sdk/types.js"; import { z } from "zod"; 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: {}, - }, - } -); - -// 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 async function makeNWSRequest(url: string): Promise { const headers = { @@ -139,153 +72,149 @@ interface ForecastResponse { }; } -// 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(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(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(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; - } +// Create server instance +const server = new McpServer({ + name: "weather", + version: "1.0.0", }); +// Register weather tools +server.tool( + "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 alertsUrl = `${NWS_API_BASE}/alerts?area=${stateCode}`; + const alertsData = await makeNWSRequest(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, + }, + ], + }; + }, +); + +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 + const pointsUrl = `${NWS_API_BASE}/points/${latitude.toFixed(4)},${longitude.toFixed(4)}`; + const pointsData = await makeNWSRequest(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(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, + }, + ], + }; + }, +); + // Start the server async function main() { const transport = new StdioServerTransport();