|
| 1 | +# frozen_string_literal: true |
| 2 | + |
| 3 | +require "json" |
| 4 | +require "mcp" |
| 5 | +require "net/http" |
| 6 | +require "uri" |
| 7 | + |
| 8 | +NWS_API_BASE = "https://api.weather.gov" |
| 9 | +USER_AGENT = "weather-app/1.0" |
| 10 | + |
| 11 | +module HelperMethods |
| 12 | + def make_nws_request(url) |
| 13 | + uri = URI(url) |
| 14 | + request = Net::HTTP::Get.new(uri) |
| 15 | + request["User-Agent"] = USER_AGENT |
| 16 | + request["Accept"] = "application/geo+json" |
| 17 | + |
| 18 | + response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http| |
| 19 | + http.request(request) |
| 20 | + end |
| 21 | + |
| 22 | + raise "HTTP #{response.code}: #{response.message}" unless response.is_a?(Net::HTTPSuccess) |
| 23 | + |
| 24 | + JSON.parse(response.body) |
| 25 | + end |
| 26 | + |
| 27 | + def format_alert(feature) |
| 28 | + properties = feature["properties"] |
| 29 | + |
| 30 | + <<~ALERT |
| 31 | + Event: #{properties["event"] || "Unknown"} |
| 32 | + Area: #{properties["areaDesc"] || "Unknown"} |
| 33 | + Severity: #{properties["severity"] || "Unknown"} |
| 34 | + Description: #{properties["description"] || "No description available"} |
| 35 | + Instructions: #{properties["instruction"] || "No specific instructions provided"} |
| 36 | + ALERT |
| 37 | + end |
| 38 | +end |
| 39 | + |
| 40 | +class GetAlerts < MCP::Tool |
| 41 | + extend HelperMethods |
| 42 | + |
| 43 | + tool_name "get_alerts" |
| 44 | + description "Get weather alerts for a US state" |
| 45 | + input_schema( |
| 46 | + properties: { |
| 47 | + state: { |
| 48 | + type: "string", |
| 49 | + description: "Two-letter US state code (e.g. CA, NY)" |
| 50 | + } |
| 51 | + }, |
| 52 | + required: ["state"] |
| 53 | + ) |
| 54 | + |
| 55 | + def self.call(state:) |
| 56 | + url = "#{NWS_API_BASE}/alerts/active/area/#{state.upcase}" |
| 57 | + data = make_nws_request(url) |
| 58 | + |
| 59 | + if data["features"].empty? |
| 60 | + return MCP::Tool::Response.new([{ |
| 61 | + type: "text", |
| 62 | + text: "No active alerts for this state." |
| 63 | + }]) |
| 64 | + end |
| 65 | + |
| 66 | + alerts = data["features"].map { |feature| format_alert(feature) } |
| 67 | + MCP::Tool::Response.new([{ |
| 68 | + type: "text", |
| 69 | + text: alerts.join("\n---\n") |
| 70 | + }]) |
| 71 | + end |
| 72 | +end |
| 73 | + |
| 74 | +class GetForecast < MCP::Tool |
| 75 | + extend HelperMethods |
| 76 | + |
| 77 | + tool_name "get_forecast" |
| 78 | + description "Get weather forecast for a location" |
| 79 | + input_schema( |
| 80 | + properties: { |
| 81 | + latitude: { |
| 82 | + type: "number", |
| 83 | + description: "Latitude of the location" |
| 84 | + }, |
| 85 | + longitude: { |
| 86 | + type: "number", |
| 87 | + description: "Longitude of the location" |
| 88 | + } |
| 89 | + }, |
| 90 | + required: ["latitude", "longitude"] |
| 91 | + ) |
| 92 | + |
| 93 | + def self.call(latitude:, longitude:) |
| 94 | + # First get the forecast grid endpoint. |
| 95 | + points_url = "#{NWS_API_BASE}/points/#{latitude},#{longitude}" |
| 96 | + points_data = make_nws_request(points_url) |
| 97 | + |
| 98 | + # Get the forecast URL from the points response. |
| 99 | + forecast_url = points_data["properties"]["forecast"] |
| 100 | + forecast_data = make_nws_request(forecast_url) |
| 101 | + |
| 102 | + # Format the periods into a readable forecast. |
| 103 | + periods = forecast_data["properties"]["periods"] |
| 104 | + forecasts = periods.first(5).map do |period| |
| 105 | + <<~FORECAST |
| 106 | + #{period["name"]}: |
| 107 | + Temperature: #{period["temperature"]}°#{period["temperatureUnit"]} |
| 108 | + Wind: #{period["windSpeed"]} #{period["windDirection"]} |
| 109 | + Forecast: #{period["detailedForecast"]} |
| 110 | + FORECAST |
| 111 | + end |
| 112 | + |
| 113 | + MCP::Tool::Response.new([{ |
| 114 | + type: "text", |
| 115 | + text: forecasts.join("\n---\n") |
| 116 | + }]) |
| 117 | + end |
| 118 | +end |
| 119 | + |
| 120 | +server = MCP::Server.new( |
| 121 | + name: "weather", |
| 122 | + version: "1.0.0", |
| 123 | + tools: [GetAlerts, GetForecast] |
| 124 | +) |
| 125 | + |
| 126 | +transport = MCP::Server::Transports::StdioTransport.new(server) |
| 127 | +transport.open |
0 commit comments