@cyanheads/bls-mcp-server

v0.1.6 pre-1.0

Fetch US Bureau of Labor Statistics data — CPI, unemployment, wages, JOLTS, and more via MCP. STDIO or Streamable HTTP.

@cyanheads/bls-mcp-server
claude mcp add --transport http bls-mcp-server https://bls.caseyjhand.com/mcp
codex mcp add bls-mcp-server --url https://bls.caseyjhand.com/mcp
{
  "mcpServers": {
    "bls-mcp-server": {
      "url": "https://bls.caseyjhand.com/mcp"
    }
  }
}
gemini mcp add --transport http bls-mcp-server https://bls.caseyjhand.com/mcp
{
  "mcpServers": {
    "bls-mcp-server": {
      "command": "bunx",
      "args": [
        "@cyanheads/bls-mcp-server@latest"
      ]
    }
  }
}
{
  "mcpServers": {
    "bls-mcp-server": {
      "type": "http",
      "url": "https://bls.caseyjhand.com/mcp"
    }
  }
}
curl -X POST https://bls.caseyjhand.com/mcp \
  -H "Content-Type: application/json" \
  -H "MCP-Protocol-Version: 2025-11-25" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"curl","version":"1.0.0"}}}'

Tools

7

read 6

bls_list_surveys

List BLS survey programs with their abbreviation codes, full names, and metadata about calculation support and annual averages. Use to discover which survey covers a topic before calling bls_search_series. Optional category filter narrows results to prices, employment, wages, productivity, injuries, or time_use surveys.

read
invocation
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "bls_list_surveys",
    "arguments": {}
  }
}
schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "category": {
      "description": "Optional category filter. One of: prices, employment, wages, productivity, injuries, time_use. Omit to list all surveys.",
      "type": "string",
      "enum": [
        "prices",
        "employment",
        "wages",
        "productivity",
        "injuries",
        "time_use"
      ]
    }
  },
  "additionalProperties": false
}
view source ↗

bls_search_series

open-world

Search the BLS series catalog by natural language query, survey code, geographic area, or keywords to resolve cryptic SeriesIDs. Returns matching series with decoded components (survey, area, item, seasonal flag) and plain-language names. Use this before bls_get_series when you have a concept but not a SeriesID. Operates offline — no API quota consumed. Survey filter accepts two-letter codes (CU, CE, LN, LA, PC, JT, OE, EC, PR). Area filter accepts state names, MSA names, or FIPS area codes.

read
invocation
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "bls_search_series",
    "arguments": {
      "query": "<query>"
    }
  }
}
schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "query": {
      "type": "string",
      "minLength": 1,
      "description": "Natural language or keyword query (e.g. \"unemployment rate\", \"CPI food\", \"nonfarm payrolls\"). Also accepts a SeriesID directly for exact lookup."
    },
    "survey": {
      "description": "Two-letter LABSTAT survey abbreviation to filter results (e.g. CU for CPI, CE for CES, LN for CPS, LA for LAUS, JT for JOLTS, OE for OEWS). Omit to search all loaded surveys.",
      "type": "string"
    },
    "area": {
      "description": "State name, MSA name, or FIPS area code to narrow results to a geographic area. Omit for national series.",
      "type": "string"
    },
    "seasonal_adjustment": {
      "description": "When true, return only seasonally adjusted series. When false, return only not-seasonally-adjusted. Omit to return both.",
      "type": "boolean"
    },
    "limit": {
      "default": 10,
      "description": "Maximum number of results to return (1–50, default 10).",
      "type": "integer",
      "minimum": 1,
      "maximum": 50
    }
  },
  "required": [
    "query",
    "limit"
  ],
  "additionalProperties": false
}
view source ↗

bls_get_latest

open-world

Return the single most recent observation for one or more BLS series. Use for "what is X right now" questions — the current unemployment rate, the latest CPI reading, etc. Each series consumes one API query against the 500/day limit; for the current value of many series, bls_get_series with a 1-year window is more quota-efficient (one query for up to 50 series). Recommended limit: 10 series; maximum: 50.

read
invocation
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "bls_get_latest",
    "arguments": {
      "series_ids": "<series_ids>"
    }
  }
}
schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "series_ids": {
      "minItems": 1,
      "maxItems": 50,
      "type": "array",
      "items": {
        "type": "string",
        "minLength": 1
      },
      "description": "One or more BLS SeriesIDs (1–50). Each consumes one daily API query. Use bls_search_series to resolve concepts to SeriesIDs. Recommended: ≤10 series."
    }
  },
  "required": [
    "series_ids"
  ],
  "additionalProperties": false
}
view source ↗

bls_get_series

open-world

Fetch time-series data for 1–50 BLS series by SeriesID in a single API request (one query against the 500/day limit). Supports optional year range (up to 20 years per request) and BLS-computed period-over-period calculations (net change + percent change together — not individually; not all surveys support it, check bls_list_surveys first). When the total observation count would exceed the inline context budget, results spill to a canvas dataframe and the response includes a dataset.name handle for follow-up SQL via bls_dataframe_query. Use bls_search_series first if you need to resolve a concept to a SeriesID.

read
invocation
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "bls_get_series",
    "arguments": {
      "series_ids": "<series_ids>"
    }
  }
}
schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "series_ids": {
      "minItems": 1,
      "maxItems": 50,
      "type": "array",
      "items": {
        "type": "string",
        "minLength": 1
      },
      "description": "One or more BLS SeriesIDs (1–50). The entire batch counts as one API query. Use bls_search_series to resolve concepts to SeriesIDs."
    },
    "start_year": {
      "description": "Start year for the data range (inclusive). The BLS API allows up to 20 years per request. Omit for the API default (typically 3–20 years depending on survey).",
      "type": "integer",
      "minimum": 1900,
      "maximum": 2100
    },
    "end_year": {
      "description": "End year for the data range (inclusive). Defaults to the current year when omitted.",
      "type": "integer",
      "minimum": 1900,
      "maximum": 2100
    },
    "calculations": {
      "description": "When true, request BLS-computed net change and percent change together (cannot request one independently). Not all surveys support this — check bls_list_surveys first. The API returns an error if requested for an unsupported survey.",
      "type": "boolean"
    }
  },
  "required": [
    "series_ids"
  ],
  "additionalProperties": false
}
view source ↗

bls_dataframe_describe

List canvas dataframes materialized by bls_get_series, with provenance (source tool, query parameters), TTL, row count, and column schema. Use before writing SQL to confirm column names. Lazy-sweeps expired tables before responding. Requires CANVAS_PROVIDER_TYPE=duckdb.

read
invocation
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "bls_dataframe_describe",
    "arguments": {}
  }
}
schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "name": {
      "description": "Optional table name (df_XXXXX_XXXXX) to describe a single dataframe. Omit to list all active dataframes.",
      "type": "string"
    }
  },
  "additionalProperties": false
}
view source ↗

bls_dataframe_query

Run a single-statement SELECT against the canvas dataframes registered by bls_get_series. Read-only: writes, DDL, DROP, COPY, PRAGMA, ATTACH, and external-file table functions are rejected. System catalogs (information_schema, pg_catalog, sqlite_master, duckdb_*) are denied at the bridge layer — use bls_dataframe_describe to list available dataframes. Supports JOINs, aggregates, window functions, and CTEs. Optional register_as persists the result as a new dataframe with a fresh TTL for chained analysis. Canvas SQL operations consume zero BLS API quota. Requires CANVAS_PROVIDER_TYPE=duckdb.

read
invocation
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "bls_dataframe_query",
    "arguments": {
      "sql": "<sql>"
    }
  }
}
schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "sql": {
      "type": "string",
      "minLength": 1,
      "description": "Single-statement SELECT against df_<id> tables on the shared canvas. Reference dataframes by the names returned in bls_get_series responses or listed by bls_dataframe_describe. Standard DuckDB SQL — joins, aggregates, window functions, CTEs all supported. Example: SELECT series_id, year, period, value FROM df_AAAAA_BBBBB WHERE year >= '2020' ORDER BY year DESC."
    },
    "register_as": {
      "description": "When set, persist the query result as a new dataframe under this name. Fresh TTL — not inherited from parent tables. Use to chain analyses without re-running source SQL or consuming additional BLS quota.",
      "type": "string"
    },
    "preview": {
      "description": "Inline row preview count. Defaults to row_limit. Set lower (e.g. 50) when chaining via register_as and only a sample is needed immediately.",
      "type": "integer",
      "minimum": 0,
      "maximum": 10000
    },
    "row_limit": {
      "default": 1000,
      "description": "Hard cap on rows materialized in the response (default 1000, max 10000). Full results live on-canvas under register_as when provided.",
      "type": "integer",
      "minimum": 1,
      "maximum": 10000
    }
  },
  "required": [
    "sql",
    "row_limit"
  ],
  "additionalProperties": false
}
view source ↗

disabled 1

bls_dataframe_drop

Drop a canvas dataframe by name. Idempotent — returns dropped=false when nothing matched. Use to free canvas resources ahead of the per-table TTL when an analysis is complete. This tool must be explicitly enabled via BLS_DATAFRAME_DROP_ENABLED=true.

disabledwould be destructive

Disabled. bls_dataframe_drop is disabled by default — TTL handles lifecycle.

BLS_DATAFRAME_DROP_ENABLED=true
schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "minLength": 1,
      "description": "Canvas table name (df_XXXXX_XXXXX) to drop."
    }
  },
  "required": [
    "name"
  ],
  "additionalProperties": false
}
view source ↗