Skip to main content
Glama
cyanheads

@cyanheads/bls-labor-mcp-server

by cyanheads

Version License Docker MCP SDK npm TypeScript Bun

Install in Claude Desktop Install in Cursor Install in VS Code

Framework

Public Hosted Server: https://bls-labor.caseyjhand.com/mcp


Overview

US labor statistics from the Bureau of Labor Statistics public API v2 and LABSTAT flat-file catalog. Resolve opaque SeriesIDs from natural language, fetch historical time-series or the latest observation, and query large multi-series results with SQL through an optional DataCanvas. Runs as a stdio process, a local Streamable HTTP server, or the public hosted endpoint above.

Tools

Tool

Description

bls_list_surveys

List BLS survey programs (CPI, CPS, CES, JOLTS, PPI, OEWS, …) with codes, descriptions, and calculation-support flags.

bls_search_series

Search the BLS series catalog by natural language, survey, area, or keywords to resolve cryptic SeriesIDs.

bls_get_series

Fetch time-series data for 1–50 BLS series by SeriesID, with optional year range and period-over-period calculations.

bls_get_latest

Return the single most recent observation for one or more BLS series.

bls_dataframe_describe

List canvas dataframes registered by bls_get_series or bls_dataframe_query — provenance, TTL, row count, column schema. Available when CANVAS_PROVIDER_TYPE=duckdb.

bls_dataframe_query

Run a SELECT against canvas dataframes registered by bls_get_series or an earlier register_as. Supports JOINs, aggregates, window functions, CTEs. Available when CANVAS_PROVIDER_TYPE=duckdb.

bls_dataframe_drop

Drop a canvas dataframe by name. Available when CANVAS_PROVIDER_TYPE=duckdb and BLS_DATAFRAME_DROP_ENABLED=true; TTL handles cleanup by default.

Related MCP server: BLS MCP

Capability reference

bls_list_surveys tool

  • Optional category filters survey programs by subject; returns codes, names, and calculation-support flags (allowsNetChange, allowsPercentChange, hasAnnualAverages).

  • Backed by the BLS surveys API with monthly caching. Annual-average support is advisory; read bls_get_series's annualAverageRows for the rows actually returned.


bls_search_series tool

  • Search by text or SeriesID, with optional survey, area (substring of area, title, or SeriesID), and seasonal_adjustment; blank filters are omitted. limit is 1–50 (default 10), with offset pagination; without an area filter, equally matching national series rank first.

  • Returns decoded SeriesIDs, titles, and frequency. Follow nextOffset while truncated is true; capped: true makes totalCount a lower bound, and paging stops at the FTS candidate pool.

  • Searches the offline LABSTAT index without using API quota; OE/OEWS is opt-in through BLS_CATALOG_INCLUDE_OES. Unindexed surveys remain fetchable by SeriesID.


bls_get_series tool

  • Fetch 1–50 SeriesIDs per call, with a start_year/end_year window of up to 20 years, optional calculations, and annual_average. A start alone resolves the end to the current year capped at start + 19; an end alone is rejected. One batch consumes one API query; calculations depend on survey support (CPI/PPI return percent change only).

  • Returns observations with available, preserves the raw - missing-value sentinel, and keeps valid series in mixed batches. Enrichment reports the applied year window, calculations, and annual-average rows.

  • Large results spill to dataset.name when CANVAS_PROVIDER_TYPE=duckdb; otherwise canvas_unavailable asks for a narrower window.


bls_get_latest tool

  • Fetch the latest observation for up to 50 SeriesIDs (recommended ≤10). Each ID consumes one API query; use bls_get_series for a more efficient multi-series batch.

  • Returns successful results[] beside per-series failed[] entries. latestObservation.available distinguishes a published value from BLS's missing-value sentinel.


bls_dataframe_describe tool

  • Optional name describes one registered dataframe; omit it to list active dataframes for the tenant. Requires CANVAS_PROVIDER_TYPE=duckdb.

  • Returns source tool, query params, row count, TTL (created_at/expires_at), and column_schema; BLS columns are nullable. Registered-query types outside the canvas set (DECIMAL, HUGEINT, SMALLINT, lists) report as VARCHAR. Expired entries are removed when their canvas drop succeeds.


bls_dataframe_query tool

  • Run one SELECT against registered tables, with JOINs, aggregates, window functions, or CTEs. row_limit defaults to 1000 and caps at 10000; writes, external-file reads, and system catalogs are denied.

  • Returns rows without consuming BLS API quota. Optional register_as stages the result with a fresh TTL for chained analysis; requires CANVAS_PROVIDER_TYPE=duckdb.


bls_dataframe_drop tool

  • Required name identifies the dataframe to drop. Available only with CANVAS_PROVIDER_TYPE=duckdb and BLS_DATAFRAME_DROP_ENABLED=true; TTL handles cleanup by default.

  • Returns dropped: false for a missing table. A failed drop returns retryable canvas_drop_failed and leaves the dataframe registered.

Features

Built on @cyanheads/mcp-ts-core: stdio and Streamable HTTP transports, pluggable auth (none / jwt / oauth), swappable storage (in-memory, filesystem, Supabase, Cloudflare KV/R2/D1), structured logging with optional OpenTelemetry tracing.

BLS-specific:

  • BLS API v2 client with retry/backoff and daily quota tracking

  • Offline series catalog search against LABSTAT flat files, indexed as an on-disk SQLite/FTS5 store — zero API quota for discovery; the OES/OEWS wage survey (~6M series) is opt-in via BLS_CATALOG_INCLUDE_OES

  • Typed error contracts for BLS-specific failure modes — quota exhaustion, locked database, calculations not supported

  • Period-over-period net/percent-change calculations via BLS's own server-side flag, consistent with BLS's published numbers

  • Optional DataCanvas spillover (DuckDB) for large multi-series result sets — schema discovery and SQL access without re-querying the API

Agent-friendly output:

  • Provenance — canvas-spilled results carry a dataset.name handle plus row count and expiry; bls_search_series echoes effectiveQuery, catalogSize, and whether the FTS candidate pool was capped

  • Graceful partial failure — bls_get_latest returns per-item failed[] (seriesId + error) alongside successful results[] instead of failing the whole batch; bls_get_series keeps valid series when another SeriesID in the same batch is invalid or empty

  • Discriminated outputs — every observation carries an available boolean for BLS's - missing-value sentinel, so callers branch on a typed field instead of parsing raw values

  • Actionable notices — enrichment.notice explains empty results, canvas spillover, and unavailable data with concrete next steps (e.g. using bls_search_series to verify a SeriesID)

Getting started

Public Hosted Instance

A public instance is available at https://bls-labor.caseyjhand.com/mcp — no installation required. Point any MCP client at it via Streamable HTTP:

{
  "mcpServers": {
    "bls-labor-mcp-server": {
      "type": "streamable-http",
      "url": "https://bls-labor.caseyjhand.com/mcp"
    }
  }
}

Self-Hosted / Local

Add the following to your MCP client configuration file. A free BLS API key unlocks 500 queries/day — register at bls.gov/developers. The server works without a key at 25 req/day.

{
  "mcpServers": {
    "bls-labor-mcp-server": {
      "type": "stdio",
      "command": "bunx",
      "args": ["@cyanheads/bls-labor-mcp-server@latest"],
      "env": {
        "MCP_TRANSPORT_TYPE": "stdio",
        "MCP_LOG_LEVEL": "info",
        "BLS_API_KEY": "your-key-here"
      }
    }
  }
}

Or with npx (no Bun required):

{
  "mcpServers": {
    "bls-labor-mcp-server": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@cyanheads/bls-labor-mcp-server@latest"],
      "env": {
        "MCP_TRANSPORT_TYPE": "stdio",
        "MCP_LOG_LEVEL": "info",
        "BLS_API_KEY": "your-key-here"
      }
    }
  }
}

Or with Docker:

{
  "mcpServers": {
    "bls-labor-mcp-server": {
      "type": "stdio",
      "command": "docker",
      "args": ["run", "-i", "--rm", "-e", "MCP_TRANSPORT_TYPE=stdio", "-e", "BLS_API_KEY=your-key-here", "ghcr.io/cyanheads/bls-labor-mcp-server:latest"]
    }
  }
}

For Streamable HTTP, set the transport and start the server:

MCP_TRANSPORT_TYPE=http MCP_SESSION_MODE=stateless MCP_HTTP_PORT=3010 BLS_API_KEY=... bun run start:http
# Server listens at http://localhost:3010/mcp

Prerequisites

  • Bun v1.4.0 or higher (or Node.js v24+).

  • A free BLS API v2 key — register at bls.gov/developers. Grants 500 queries/day; the server also works without a key at 25 req/day.

Installation

  1. Clone the repository:

git clone https://github.com/cyanheads/bls-labor-mcp-server.git
  1. Navigate into the directory:

cd bls-labor-mcp-server
  1. Install dependencies:

bun install
  1. Configure environment:

cp .env.example .env
# edit .env and set BLS_API_KEY

Configuration

All configuration is validated at startup via Zod schemas in src/config/server-config.ts.

Variable

Description

Default

BLS_API_KEY

BLS v2 API key. Optional — 25 req/day without, 500 req/day with. Register free at bls.gov/developers.

—

BLS_BASE_URL

BLS API v2 base URL.

https://api.bls.gov/publicAPI/v2

BLS_CATALOG_BASE_URL

LABSTAT flat-file base URL. Override to point at a local mirror.

https://download.bls.gov/pub/time.series

BLS_CATALOG_DB_PATH

On-disk SQLite catalog index — queried on demand and persisted across restarts. Empty uses an in-memory DB (re-harvested each boot). Mount a volume here in containers.

.cache/bls-catalog.db

BLS_CATALOG_CACHE_TTL_HOURS

Catalog freshness window in hours — re-harvest once the index is older, checked at startup and hourly while running.

168 (7 days)

BLS_CATALOG_INCLUDE_OES

Include the OES/OEWS wage survey (~6M series / ~1.2 GB; multi-minute first harvest). Off by default — OES series stay fetchable by ID.

false

BLS_OBSERVATIONS_MIRROR_ENABLED

Serve observations from a local SQLite mirror instead of the live API (requires a one-time bootstrap — see below).

false

BLS_DATASET_TTL_SECONDS

Per-dataframe TTL for canvas-registered tables, in seconds.

86400 (24 h)

BLS_DATAFRAME_DROP_ENABLED

Expose bls_dataframe_drop. TTL handles cleanup by default.

false

CANVAS_PROVIDER_TYPE

Set to duckdb to enable DataCanvas tabular spillover for large result sets.

none

MCP_TRANSPORT_TYPE

Transport: stdio or http.

stdio

MCP_HTTP_PORT

HTTP server port.

3010

MCP_SESSION_MODE

Session mode. This server uses stateless; valid schema values are auto, stateful, and stateless. Schema-default auto resolves to stateful.

stateless

MCP_AUTH_MODE

Auth mode: none, jwt, or oauth.

none

MCP_LOG_LEVEL

Log level (RFC 5424).

info

LOGS_DIR

Directory for log files (Node.js only).

<project-root>/logs

OTEL_ENABLED

Enable OpenTelemetry instrumentation.

false

OTEL_EXPORTER_OTLP_ENDPOINT

Base OTLP URL for traces and metrics; signal-specific endpoints override it.

—

OTEL_EXPORTER_OTLP_LOGS_ENDPOINT

Opt-in OTLP log endpoint. The base URL never enables log export.

—

LOG_TOOL_FAILURE_PAYLOADS

Log failed-call arguments and results, redacted by key name; secrets in free-form values remain.

false

LOG_TOOL_FAILURE_PAYLOAD_MAX_BYTES

UTF-8 byte cap per logged failure payload.

16384

See .env.example for the full list of optional overrides.

Observation mirror (optional)

For high-volume workloads, an opt-in local mirror serves bls_get_series / bls_get_latest from an embedded SQLite store instead of the BLS API — eliminating the 500/day quota cap. It is off by default. To enable:

  1. Set BLS_OBSERVATIONS_MIRROR_ENABLED=true (and review the BLS_OBSERVATIONS_MIRROR_* vars in .env.example).

  2. Run the one-time bootstrap out-of-band — it downloads the full LABSTAT observation set and can take a while:

    node dist/services/bls-observations/subprocess.js --init

The mirror harvests every survey in the catalog's survey list — OE included, whatever BLS_CATALOG_INCLUDE_OES says — so it covers CM and CI too (about 55 MB of cm.data.* / ci.data.* files). An incremental refresh reads only files published after the mirror's last one, so a mirror bootstrapped before CM and CI joined picks each file up at its next BLS publication; until then those series come from the live API when fallback is on. Until the bootstrap completes, requests fall back to the live API (unless BLS_OBSERVATIONS_MIRROR_FALLBACK_LIVE=false). When a live fallback fails — quota exhausted, BLS unreachable — the series the mirror served are still returned, and enrichment.notice names each unserved SeriesID with the failure's reason and recovery. Mirror rows carry no BLS calculations: with calculations: true, a mirror-served series comes back without calculation fields, enrichment.calculationsApplied is false, and the notice names it. On HTTP transport, an incremental refresh runs on the BLS_OBSERVATIONS_MIRROR_REFRESH_CRON schedule. In containers, mount a persistent volume at BLS_OBSERVATIONS_MIRROR_PATH.

Upgrading an existing mirror. A mirror bootstrapped before sentinel rows were stored is missing the periods BLS publishes with its - missing-value marker. Opening such a mirror clears its sync checkpoint once, so the next refresh re-reads every LABSTAT file and fills them in — no operator action beyond letting that refresh run, and it takes as long as a full read. The mirror keeps serving throughout.

Running the server

Local development

  • Build and run:

    # One-time build
    bun run rebuild
    
    # Run the built server
    bun run start:stdio
    # or
    bun run start:http
  • Run checks and tests:

    bun run devcheck   # Lint, format, typecheck, security
    bun run test       # Vitest test suite
    bun run lint:mcp   # Validate MCP definitions against spec

Docker

docker build -t bls-labor-mcp-server .
docker run --rm -e BLS_API_KEY=your-key -e MCP_TRANSPORT_TYPE=http -p 3010:3010 bls-labor-mcp-server

The Dockerfile defaults to HTTP transport, stateless session mode, and logs to /var/log/bls-labor-mcp-server. OpenTelemetry peer dependencies are installed by default — build with --build-arg OTEL_ENABLED=false to omit them.

Project structure

Directory

Purpose

src/index.ts

createApp() entry point — registers tools and initializes services.

src/config

Server-specific environment variable parsing and validation with Zod.

src/mcp-server/tools

Tool definitions (*.tool.ts).

src/services/bls-api

BLS API v2 service — batch fetch, latest-value GET, surveys metadata.

src/services/bls-catalog

LABSTAT flat-file catalog — offline series index and search.

src/services/bls-observations

Optional LABSTAT observation mirror — embedded SQLite store, ingester, and refresh subprocess.

src/services/bls-periods

Annual-average period semantics (M13/Q05/S03) shared by the API and mirror paths.

src/services/canvas-bridge

DataCanvas bridge — dataframe registration, SQL gate, lifecycle management.

docs/design.md

Full tool surface specification, service architecture, and error contracts.

tests/

Unit and integration tests mirroring src/.

Development guide

See CLAUDE.md for development guidelines and architectural rules. The short version:

  • Handlers throw, framework catches — no try/catch in tool logic

  • Use ctx.log for request-scoped logging, ctx.state for tenant-scoped storage

  • bls_search_series is the anchor tool — design workflows to call it before the API tools

  • Wrap BLS API calls: validate raw → normalize to domain type → return output schema; never fabricate missing fields

Contributing

Issues are welcome. Run checks and tests before submitting:

bun run devcheck
bun run test

License

Apache-2.0 — see LICENSE for details.

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    D
    maintenance
    Enables access to Bureau of Labor Statistics (BLS) economic data including Consumer Price Index, employment statistics, and other labor market indicators. Supports fetching data series, listing available datasets, and retrieving metadata through natural language queries.
    4
    MIT
  • A
    license
    A
    quality
    C
    maintenance
    Enables users to query U.S. labor statistics, including employment, CPI, and wages, directly from the Bureau of Labor Statistics Public Data API. It provides tools to retrieve real-time economic time series data, browse popular series, and access survey metadata through natural language.
    6
    110 npm
    1
    MIT
  • F
    license
    Not graded
    quality
    D
    maintenance
    Economic data MCP server that connects FRED, BLS, BEA, IMF, World Bank, and ECB to any MCP-compatible client, with built-in methodology rules to guide LLMs in selecting appropriate economic indicators.
    -
  • A
    license
    A
    quality
    B
    maintenance
    MCP server providing official US economic data including CPI inflation, famous prices, national debt, and Treasury interest rates. Enables AI agents to query historical and latest economic statistics via natural language.
    8
    27 npm
    MIT