Wraps the Bureau of Labor Statistics v2 API with a focus on resolving cryptic SeriesIDs before you fetch data. You get natural language search against the full LABSTAT catalog to turn "unemployment rate" into LNS14000000, then pull time series for CPI, CES, JOLTS, wages, and 20+ other BLS surveys. Handles batching (50 series per request), year windows, and server-side calculations when the survey supports them. Results that exceed the context budget spill to DataCanvas dataframes, which you can query with SQL, join across surveys, or persist for later analysis. The search runs offline against bundled flat files, so it doesn't burn your 500-query daily quota. Reach for this when you need authoritative US labor data without decoding positional survey codes by hand.
Fetch US Bureau of Labor Statistics data — CPI, unemployment, wages, JOLTS, and more via MCP. STDIO or Streamable HTTP.
Public Hosted Server: https://bls-labor.caseyjhand.com/mcp
Seven tools in two groups — four for BLS data access (survey discovery, SeriesID resolution, history, current values) and three for optional DataCanvas SQL analysis of large result sets:
| 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 — provenance, TTL, row count, column schema. Requires CANVAS_PROVIDER_TYPE=duckdb. |
bls_dataframe_query | Run a SELECT against canvas dataframes registered by bls_get_series. Supports JOINs, aggregates, window functions, CTEs. Requires CANVAS_PROVIDER_TYPE=duckdb. |
bls_dataframe_drop | Drop a canvas dataframe by name. Opt-in via BLS_DATAFRAME_DROP_ENABLED=true; TTL handles cleanup by default. Requires CANVAS_PROVIDER_TYPE=duckdb. |
bls_list_surveysList available BLS survey programs and their metadata.
category filter (prices, employment, wages, productivity, injuries, time_use)allowsNetChange, allowsPercentChange, hasAnnualAverages)bls_search_seriesThe entry point for most BLS workflows. Resolves human concepts to BLS SeriesIDs.
LNS14000000 and CES0000000001 encode survey + area + item + seasonal flag in opaque positional codes — this tool decodes thembls_get_series or bls_get_latest when you have a concept but not a SeriesIDbls_get_seriesFetch historical time-series data for one or more BLS series.
start_year / end_year window (BLS caps history at 20 years per request)calculations: true for BLS-server-side net change and percent change — a survey returns whichever it supports (CPI/PPI return percent change only); check bls_list_surveys for per-survey supportdataset.name handle for SQL via bls_dataframe_query. Requires CANVAS_PROVIDER_TYPE=duckdb.bls_get_latestGet the current value for one or more BLS series.
bls_get_series with a narrow year window is more quota-efficient (one API query regardless of series count)failed[] array alongside successful resultsbls_dataframe_describeInspect canvas dataframes registered by bls_get_series.
bls_dataframe_queryRun SQL against canvas dataframes registered by bls_get_series.
register_as persists the query result as a new named dataframe with a fresh TTL — useful for chaining analyses without re-consuming BLS API quotaregister_as is setbls_dataframe_dropDrop a canvas dataframe by name. Idempotent — returns dropped: false when nothing matched.
BLS_DATAFRAME_DROP_ENABLED=true (TTL handles cleanup by default)Built on @cyanheads/mcp-ts-core:
none, jwt, oauth)in-memory, filesystem, Supabase, Cloudflare KV/R2/D1BLS-specific:
bls_get_series / bls_get_latest without the 500/day API cap (opt-in, off by default)BLS_CATALOG_INCLUDE_OESAdd 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_HTTP_PORT=3010 BLS_API_KEY=... bun run start:http
# Server listens at http://localhost:3010/mcp
git clone https://github.com/cyanheads/bls-labor-mcp-server.git
cd bls-mcp-server
bun install
cp .env.example .env
# edit .env and set BLS_API_KEY
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. | 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_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 |
See .env.example for the full list of optional overrides.
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:
Set BLS_OBSERVATIONS_MIRROR_ENABLED=true (and review the BLS_OBSERVATIONS_MIRROR_* vars in .env.example).
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
Until the bootstrap completes, requests fall back to the live API (unless BLS_OBSERVATIONS_MIRROR_FALLBACK_LIVE=false). 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.
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 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.
| 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/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/. |
See CLAUDE.md for development guidelines and architectural rules. The short version:
try/catch in tool logicctx.log for request-scoped logging, ctx.state for tenant-scoped storagebls_search_series is the anchor tool — design workflows to call it before the API toolsIssues and pull requests are welcome. Run checks and tests before submitting:
bun run devcheck
bun run test
Apache-2.0 — see LICENSE for details.
BLS_API_KEYOptional — works without one (25 req/day). With key: 500 req/day + calculations and annual averages. Register free at bls.gov/developers.
MCP_LOG_LEVELdefault: infoSets the minimum log level for output (e.g., 'debug', 'info', 'warn').
MCP_HTTP_HOSTdefault: 127.0.0.1The hostname for the HTTP server.
MCP_HTTP_PORTdefault: 3010The port to run the HTTP server on.
MCP_HTTP_ENDPOINT_PATHdefault: /mcpThe endpoint path for the MCP server.
MCP_AUTH_MODEdefault: noneAuthentication mode to use: 'none', 'jwt', or 'oauth'.
com.mcparmory/google-search
io.github.pipeworx-io/brave-search
marcopesani/mcp-server-serper
brave/brave-search-mcp-server
com.mcparmory/google-search-console
acamolese/google-search-console-mcp