Skip to content

Weather Server Example

A full MCP server example with validation, resources, templates, and realistic weather tool contracts.

Overview

This example demonstrates:

  • argument validation for tool inputs
  • multiple tools with focused responsibilities
  • resource and resource-template registration
  • capability toggles (logging, completions, tasks)

Full Source Code

zig
//! Weather Server Example
//!
//! This example replicates the weather server from the MCP documentation,
//! demonstrating how to create a practical MCP server.

const std = @import("std");
const mcp = @import("mcp");

const NWS_API_BASE = "https://api.weather.gov";

pub fn main(init: std.process.Init) void {
    run(init.io, init.gpa) catch |err| {
        mcp.reportError(err);
    };
}

fn run(io: std.Io, allocator: std.mem.Allocator) !void {
    // Create weather server
    var server: mcp.Server = .init(allocator, .{
        .name = "weather-server",
        .version = "1.0.0",
        .title = "Weather Server",
        .description = "Get weather alerts and forecasts for US locations",
        .instructions = "Use get_alerts to check weather alerts for a US state, or get_forecast to get the forecast for a location.",
    });
    defer server.deinit();

    // Add get_alerts tool
    try server.addTool(.{
        .name = "get_alerts",
        .description = "Get weather alerts for a US state",
        .title = "Get Weather Alerts",
        .annotations = .{
            .readOnlyHint = true,
            .idempotentHint = true,
            .destructiveHint = false,
        },
        .handler = getAlertsHandler,
    });

    // Add get_forecast tool
    try server.addTool(.{
        .name = "get_forecast",
        .description = "Get weather forecast for a location",
        .title = "Get Weather Forecast",
        .annotations = .{
            .readOnlyHint = true,
            .idempotentHint = true,
            .destructiveHint = false,
        },
        .handler = getForecastHandler,
    });

    // Add a weather info resource
    try server.addResource(.{
        .uri = "weather://info",
        .name = "Weather API Info",
        .description = "Information about the weather data source",
        .mimeType = "text/plain",
        .handler = weatherInfoHandler,
    });

    // Add resource template for state alerts
    try server.addResourceTemplate(.{
        .uriTemplate = "weather://alerts/{state}",
        .name = "state-alerts",
        .title = "State Weather Alerts",
        .description = "Get weather alerts for a specific US state",
        .mimeType = "application/json",
    });

    // Enable logging, completions, and tasks
    server.enableLogging();
    server.enableCompletions();
    server.enableTasks();

    // Run the server
    try server.run(io, allocator, .stdio);

    // To run with HTTP transport:
    // try server.run(io, allocator, .{ .http = .{ .host = "localhost", .port = 8080 } });
}

fn getAlertsHandler(_: ?*anyopaque, _: std.Io, allocator: std.mem.Allocator, args: ?std.json.Value) mcp.tools.ToolError!mcp.tools.ToolResult {
    const state = mcp.tools.getString(args, "state") orelse {
        return mcp.tools.errorResult(allocator, "Missing required argument: state (two-letter US state code)") catch return mcp.tools.ToolError.OutOfMemory;
    };

    // Validate state code
    if (state.len != 2) {
        return mcp.tools.errorResult(allocator, "State must be a two-letter code (e.g., CA, NY, TX)") catch return mcp.tools.ToolError.OutOfMemory;
    }

    var buf: [1024]u8 = undefined;
    const result = std.fmt.bufPrint(&buf,
        \\Weather Alerts for {s}:
        \\
        \\No active alerts at this time.
        \\
        \\Note: This is a demo. In production, this would fetch real data from:
        \\{s}/alerts/active/area/{s}
    , .{ state, NWS_API_BASE, state }) catch "Error formatting response";

    return mcp.tools.textResult(allocator, result) catch return mcp.tools.ToolError.OutOfMemory;
}

fn getForecastHandler(_: ?*anyopaque, _: std.Io, allocator: std.mem.Allocator, args: ?std.json.Value) mcp.tools.ToolError!mcp.tools.ToolResult {
    const lat = mcp.tools.getFloat(args, "latitude") orelse {
        return mcp.tools.errorResult(allocator, "Missing required argument: latitude") catch return mcp.tools.ToolError.OutOfMemory;
    };

    const lon = mcp.tools.getFloat(args, "longitude") orelse {
        return mcp.tools.errorResult(allocator, "Missing required argument: longitude") catch return mcp.tools.ToolError.OutOfMemory;
    };

    if (lat < -90 or lat > 90) {
        return mcp.tools.errorResult(allocator, "Latitude must be between -90 and 90") catch return mcp.tools.ToolError.OutOfMemory;
    }
    if (lon < -180 or lon > 180) {
        return mcp.tools.errorResult(allocator, "Longitude must be between -180 and 180") catch return mcp.tools.ToolError.OutOfMemory;
    }

    var buf: [2048]u8 = undefined;
    const result = std.fmt.bufPrint(&buf,
        \\Weather Forecast for ({d:.4}, {d:.4}):
        \\
        \\Today:
        \\  Temperature: 72°F
        \\  Wind: 5 mph NW
        \\  Conditions: Partly cloudy
        \\
        \\Tonight:
        \\  Temperature: 55°F
        \\  Wind: 3 mph W
        \\  Conditions: Clear
        \\
        \\Tomorrow:
        \\  Temperature: 75°F
        \\  Wind: 8 mph SW
        \\  Conditions: Sunny
        \\
        \\Note: This is demo data. Production would fetch from:
        \\{s}/points/{d:.4},{d:.4}
    , .{ lat, lon, NWS_API_BASE, lat, lon }) catch "Error formatting response";

    return mcp.tools.textResult(allocator, result) catch return mcp.tools.ToolError.OutOfMemory;
}

fn weatherInfoHandler(_: ?*anyopaque, _: std.Io, _: std.mem.Allocator, uri: []const u8) mcp.resources.ResourceError!mcp.resources.ResourceContent {
    return .{
        .uri = uri,
        .mimeType = "text/plain",
        .text =
        \\Weather Server - Data Source Information
        \\
        \\This server uses the National Weather Service API.
        \\API Base URL: https://api.weather.gov
        \\
        \\Available Tools:
        \\- get_alerts: Get active weather alerts for a US state
        \\- get_forecast: Get weather forecast for coordinates
        \\
        \\Note: Only US locations are supported.
        ,
    };
}

Example Requests

Get alerts:

json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_alerts",
    "arguments": {
      "state": "CA"
    }
  }
}

Get forecast:

json
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "get_forecast",
    "arguments": {
      "latitude": 37.7749,
      "longitude": -122.4194
    }
  }
}

Output Behavior

  • invalid state/coordinates return MCP error results
  • valid inputs return formatted text output payloads
  • resource reads return weather API information text

Next Steps