Skip to main content
Glama
prsantos-com

AirNow MCP Server

by prsantos-com

get-observations-by-monitoring-site-by-geographic-bounding-box

Retrieve air quality observations from monitoring sites within a specific geographic area. Specify pollutants, data format, and time range to access detailed air quality data.

Instructions

Get observations by monitoring site within a geographic bounding box.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
bboxYesGeographic bounding box of the area of interest in latitude and longitude. The format is a comma separated list minX,minY,maxX,maxY. Example: 122.715607,38.181254,-120.012970,39.022646
datatypeYesData type to return. Example: C
enddateNoThe end date and time of the data requested. Format: UTC Date or DateTime as end of measurement period. Examples: 2014-01-02T13:00, 2014-01-02T13, or 2014-01-02
formatYesMIME type of the file to be returned. Example: application/json
includerawconcentrationsNoWhen set to 1, an additional field that contains the raw concentration will be added to the output. Default is 0.
monitortypeNoThe type of monitor to be returned. Options include: Permanent Only (0), Mobile Only (1), Permanent and Mobile (2). Example: 0
parametersYesComma separated list of pollutant parameters short codes to return data for. Options include: ozone, pm25, pm10, co, no2, so2. Example: ozone,pm25
startdateNoThe start date and time of the data requested. Format: UTC Date or DateTime as beginning of measurement period. Examples: 2014-01-01T13:00, 2014-01-01T13, or 2014-01-01
verboseNoWhen set to 1, provides additional site information including Site Name, Agency Name, AQS ID, and Full AQS ID. Default is 0.

Implementation Reference

  • The handler function for the MCP tool that calls the AirNow API helper, handles errors, and returns the result as text content.
    async (params) => {
      const result =
        await airnowApi.fetchObservationsByMonitoringSiteByGeographicBoundingBox(
          params
        );
      if (result === null) {
        return {
          content: [
            {
              type: "text",
              text: "Failed to fetch observations data from AirNow API.",
            },
          ],
          isError: true,
        };
      }
      return {
        content: [
          {
            type: "text",
            text: result,
          },
        ],
      };
    }
  • Zod schema for input parameters including bounding box, parameters, datatype, format, dates, monitor type, verbose, and raw concentrations.
    {
      bbox: z
        .string()
        .describe(
          "Geographic bounding box of the area of interest in latitude and longitude. The format is a comma separated list minX,minY,maxX,maxY. Example: 122.715607,38.181254,-120.012970,39.022646"
        ),
      parameters: z
        .string()
        .describe(
          "Comma separated list of pollutant parameters short codes to return data for. Options include: ozone, pm25, pm10, co, no2, so2. Example: ozone,pm25"
        ),
      datatype: z
        .enum(["A", "C", "B"])
        .describe("Data type to return. Example: C"),
      format: z
        .enum([
          "text/csv",
          "application/json",
          "application/xml",
          "application/vnd.google-earth.kml",
        ])
        .describe(
          "MIME type of the file to be returned. Example: application/json"
        ),
      startdate: z
        .string()
        .optional()
        .describe(
          "The start date and time of the data requested. Format: UTC Date or DateTime as beginning of measurement period. Examples: 2014-01-01T13:00, 2014-01-01T13, or 2014-01-01"
        ),
      enddate: z
        .string()
        .optional()
        .describe(
          "The end date and time of the data requested. Format: UTC Date or DateTime as end of measurement period. Examples: 2014-01-02T13:00, 2014-01-02T13, or 2014-01-02"
        ),
      monitortype: z
        .enum(["0", "1", "2"])
        .optional()
        .describe(
          "The type of monitor to be returned. Options include: Permanent Only (0), Mobile Only (1), Permanent and Mobile (2). Example: 0"
        ),
      verbose: z
        .enum(["0", "1"])
        .optional()
        .describe(
          "When set to 1, provides additional site information including Site Name, Agency Name, AQS ID, and Full AQS ID. Default is 0."
        ),
      includerawconcentrations: z
        .enum(["0", "1"])
        .optional()
        .describe(
          "When set to 1, an additional field that contains the raw concentration will be added to the output. Default is 0."
        ),
    },
  • The registration function exported from the tool file that registers the tool on the MCP server using server.tool(), including schema and handler.
    export const registerObservationsByBoundingBox = (server: McpServer): void => {
      server.tool(
        "get-observations-by-monitoring-site-by-geographic-bounding-box",
        "Get observations by monitoring site within a geographic bounding box.",
        {
          bbox: z
            .string()
            .describe(
              "Geographic bounding box of the area of interest in latitude and longitude. The format is a comma separated list minX,minY,maxX,maxY. Example: 122.715607,38.181254,-120.012970,39.022646"
            ),
          parameters: z
            .string()
            .describe(
              "Comma separated list of pollutant parameters short codes to return data for. Options include: ozone, pm25, pm10, co, no2, so2. Example: ozone,pm25"
            ),
          datatype: z
            .enum(["A", "C", "B"])
            .describe("Data type to return. Example: C"),
          format: z
            .enum([
              "text/csv",
              "application/json",
              "application/xml",
              "application/vnd.google-earth.kml",
            ])
            .describe(
              "MIME type of the file to be returned. Example: application/json"
            ),
          startdate: z
            .string()
            .optional()
            .describe(
              "The start date and time of the data requested. Format: UTC Date or DateTime as beginning of measurement period. Examples: 2014-01-01T13:00, 2014-01-01T13, or 2014-01-01"
            ),
          enddate: z
            .string()
            .optional()
            .describe(
              "The end date and time of the data requested. Format: UTC Date or DateTime as end of measurement period. Examples: 2014-01-02T13:00, 2014-01-02T13, or 2014-01-02"
            ),
          monitortype: z
            .enum(["0", "1", "2"])
            .optional()
            .describe(
              "The type of monitor to be returned. Options include: Permanent Only (0), Mobile Only (1), Permanent and Mobile (2). Example: 0"
            ),
          verbose: z
            .enum(["0", "1"])
            .optional()
            .describe(
              "When set to 1, provides additional site information including Site Name, Agency Name, AQS ID, and Full AQS ID. Default is 0."
            ),
          includerawconcentrations: z
            .enum(["0", "1"])
            .optional()
            .describe(
              "When set to 1, an additional field that contains the raw concentration will be added to the output. Default is 0."
            ),
        },
        async (params) => {
          const result =
            await airnowApi.fetchObservationsByMonitoringSiteByGeographicBoundingBox(
              params
            );
          if (result === null) {
            return {
              content: [
                {
                  type: "text",
                  text: "Failed to fetch observations data from AirNow API.",
                },
              ],
              isError: true,
            };
          }
          return {
            content: [
              {
                type: "text",
                text: result,
              },
            ],
          };
        }
      );
    };
  • Core helper function that builds the query parameters for the AirNow API endpoint '/aq/data/' and fetches the observations data.
    export async function fetchObservationsByMonitoringSiteByGeographicBoundingBox(params: Record<string, string>): Promise<string | null> {
      const endpoint = 'aq/data/';
      const queryParams = new URLSearchParams();
      queryParams.append('bbox', params.bbox);
      queryParams.append('parameters', params.parameters);
      queryParams.append('datatype', params.datatype);
      queryParams.append('format', params.format);
      queryParams.append('verbose', params.verbose || '0');
      queryParams.append('includerawconcentrations', params.includerawconcentrations || '0');
      if (params.startdate && params.enddate) {
        queryParams.append('startdate', params.startdate);
        queryParams.append('enddate', params.enddate);
      }
      if (params.monitortype) queryParams.append('monitortype', params.monitortype);
    
      return airnowGet(endpoint, queryParams);
    }
  • Top-level registration call in the tools index that invokes the tool's registration function on the MCP server.
    registerObservationsByBoundingBox(server);
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries full burden but offers minimal behavioral disclosure. It states what the tool does but doesn't describe response format, pagination, rate limits, authentication requirements, or error conditions. For a data retrieval tool with 9 parameters, this leaves significant behavioral aspects undocumented.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, efficient sentence that states the core functionality without unnecessary words. It's appropriately sized for the tool's complexity and front-loads the essential information. Every word earns its place in conveying the tool's purpose.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a complex data retrieval tool with 9 parameters, no annotations, and no output schema, the description is inadequate. It doesn't explain what 'observations' contain, the data format returned, or how results are structured. The description should provide more context about the nature of the observations and typical use cases given the tool's complexity.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema fully documents all 9 parameters. The description adds no parameter-specific information beyond what's in the schema. This meets the baseline of 3 since the schema does the heavy lifting, but the description provides no additional context about parameter interactions or usage patterns.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the verb ('Get') and resource ('observations by monitoring site') with geographic bounding scope. It distinguishes from siblings like contour maps, forecasts, and reporting-area tools by specifying monitoring sites and bounding box. However, it doesn't explicitly contrast with similar tools like 'get-historical-observations-by-reporting-area-by-lat-long' beyond the obvious site vs reporting-area difference.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No guidance on when to use this tool versus alternatives is provided. The description doesn't mention prerequisites, typical use cases, or comparisons to sibling tools. While the name implies geographic bounding box filtering, there's no explicit 'when-not' or alternative tool recommendations for different scenarios.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Related Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/prsantos-com/airnow-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server