@cyanheads/bls-labor-mcp-server
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@@cyanheads/bls-labor-mcp-serverget the latest unemployment rate"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Public Hosted Server: https://bls-labor.caseyjhand.com/mcp
Tools
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 |
| List BLS survey programs (CPI, CPS, CES, JOLTS, PPI, OEWS, …) with codes, descriptions, and calculation-support flags. |
| Search the BLS series catalog by natural language, survey, area, or keywords to resolve cryptic SeriesIDs. |
| Fetch time-series data for 1–50 BLS series by SeriesID, with optional year range and period-over-period calculations. |
| Return the single most recent observation for one or more BLS series. |
| List canvas dataframes registered by |
| Run a SELECT against canvas dataframes registered by |
| Drop a canvas dataframe by name. Opt-in via |
bls_list_surveys
List available BLS survey programs and their metadata.
Covers all major BLS programs: CPS, CES, CPI, PPI, JOLTS, LAUS, OEWS, ECEC, and others
Optional
categoryfilter (prices,employment,wages,productivity,injuries,time_use)Returns survey codes, descriptions, and calculation-support flags (
allowsNetChange,allowsPercentChange,hasAnnualAverages)Backed by the live BLS surveys API with monthly caching; does not consume daily API quota
bls_search_series
The entry point for most BLS workflows. Resolves human concepts to BLS SeriesIDs.
BLS identifiers like
LNS14000000andCES0000000001encode survey + area + item + seasonal flag in opaque positional codes — this tool decodes themFree-text and keyword search against the full BLS series catalog
Filter by survey code, geographic area (state name, MSA, or FIPS), and seasonal adjustment flag
Returns decoded series components (survey, area, item, seasonal flag) alongside the plain-language name
Operates entirely offline against LABSTAT flat files bundled at startup — no API quota consumed
Use before
bls_get_seriesorbls_get_latestwhen you have a concept but not a SeriesID
bls_get_series
Fetch historical time-series data for one or more BLS series.
Batch fetch up to 50 series per request (counts as one of the 500 daily API queries)
Optional
start_year/end_yearwindow (BLS caps history at 20 years per request)Optional
calculations: truefor BLS-server-side net change and percent change — a survey returns whichever it supports (CPI/PPI return percent change only); checkbls_list_surveysfor per-survey supportOptional
annual_average: trueadds each year's annual-average row (periodM13,Q05orS03) — the mean of that year's real periods, not an additional one. Off by default, so observations are safe to sum or average as returned;enrichment.annualAverageRowsreports how many were addedReturns observations with series metadata (title, area, item, seasonality)
Spills to a DataCanvas dataframe when the observation count exceeds the inline context budget — response includes a
dataset.namehandle for SQL viabls_dataframe_query. RequiresCANVAS_PROVIDER_TYPE=duckdb.
bls_get_latest
Get the current value for one or more BLS series.
Issues one GET per SeriesID (no batch-latest endpoint exists in BLS v2) — each counts as one of the 500 daily API queries
Recommended limit: ≤10 series per call; accepts up to 50
For "current value" across many series,
bls_get_serieswith a narrow year window is more quota-efficient (one API query regardless of series count)Partial success reporting — failed series are returned in a separate
failed[]array alongside successful results
bls_dataframe_describe
Inspect canvas dataframes registered by bls_get_series.
Lists all active dataframes for the current tenant: table name, source tool, query params, row count, column schema, TTL
Optionally describe a single dataframe by name
Lazy-sweeps expired entries before responding
Use before writing SQL to confirm column names
bls_dataframe_query
Run SQL against canvas dataframes registered by bls_get_series.
Read-only: writes, DDL, DROP, COPY, PRAGMA, ATTACH, and external-file table functions are rejected
Supports JOINs, aggregates, window functions, and CTEs
Optional
register_aspersists the query result as a new named dataframe with a fresh TTL — useful for chaining analyses without re-consuming BLS API quotaInline row cap: 1,000 rows by default (max 10,000); full results live on-canvas when
register_asis setZero BLS API quota consumed
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
Must be explicitly enabled via
BLS_DATAFRAME_DROP_ENABLED=true(TTL handles cleanup by default)
Related MCP server: mcp-fred
Features
Built on @cyanheads/mcp-ts-core:
Declarative tool definitions — single file per tool, framework handles registration and validation
Unified error handling across all tools
Pluggable auth (
none,jwt,oauth)Swappable storage backends:
in-memory,filesystem,Supabase,Cloudflare KV/R2/D1Structured logging with optional OpenTelemetry tracing
STDIO and Streamable HTTP transports
BLS-specific:
BLS API v2 integration with retry/backoff and daily quota tracking
Offline series catalog search against LABSTAT flat files — zero API quota for discovery
Typed error contracts for BLS-specific failure modes: quota exhaustion, locked database, calculations not supported
Period-over-period calculations via BLS server-side flag (consistent with BLS published numbers)
DataCanvas spillover (DuckDB) for large multi-series result sets — SQL access without re-querying the API
Optional local observation mirror — sync LABSTAT bulk data into an embedded SQLite store to serve
bls_get_series/bls_get_latestwithout the 500/day API cap (opt-in, off by default)On-disk SQLite catalog index — the series catalog is parsed into an FTS5 SQLite store, queried on demand (not held in memory) and persisted across restarts; the OES/OEWS wage survey (~6M series) is opt-in via
BLS_CATALOG_INCLUDE_OES
Getting started
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_HTTP_PORT=3010 BLS_API_KEY=... bun run start:http
# Server listens at http://localhost:3010/mcpPrerequisites
Bun v1.3.2 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
Clone the repository:
git clone https://github.com/cyanheads/bls-labor-mcp-server.gitNavigate into the directory:
cd bls-mcp-serverInstall dependencies:
bun installConfigure environment:
cp .env.example .env
# edit .env and set BLS_API_KEYConfiguration
All configuration is validated at startup via Zod schemas in src/config/server-config.ts.
Variable | Description | Default |
| BLS v2 API key. Optional — 25 req/day without, 500 req/day with. Register free at bls.gov/developers. | — |
| BLS API v2 base URL. |
|
| LABSTAT flat-file base URL. Override to point at a local mirror. |
|
| 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. |
|
| Catalog freshness window in hours — re-harvest once the index is older. |
|
| Include the OES/OEWS wage survey (~6M series / ~1.2 GB; multi-minute first harvest). Off by default — OES series stay fetchable by ID. |
|
| Serve observations from a local SQLite mirror instead of the live API (requires a one-time bootstrap — see below). |
|
| Per-dataframe TTL for canvas-registered tables, in seconds. |
|
| Expose |
|
| Set to |
|
| Transport: |
|
| HTTP server port. |
|
| Auth mode: |
|
| Log level (RFC 5424). |
|
| Directory for log files (Node.js only). |
|
| Enable OpenTelemetry instrumentation. |
|
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:
Set
BLS_OBSERVATIONS_MIRROR_ENABLED=true(and review theBLS_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.
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:httpRun 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-serverThe 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 |
|
|
| Server-specific environment variable parsing and validation with Zod. |
| Tool definitions ( |
| BLS API v2 service — batch fetch, latest-value GET, surveys metadata. |
| LABSTAT flat-file catalog — offline series index and search. |
| Optional LABSTAT observation mirror — embedded SQLite store, ingester, and refresh subprocess. |
| DataCanvas bridge — dataframe registration, SQL gate, lifecycle management. |
| Full tool surface specification, service architecture, and error contracts. |
| Unit and integration tests mirroring |
Development guide
See CLAUDE.md for development guidelines and architectural rules. The short version:
Handlers throw, framework catches — no
try/catchin tool logicUse
ctx.logfor request-scoped logging,ctx.statefor tenant-scoped storagebls_search_seriesis the anchor tool — design workflows to call it before the API toolsWrap BLS API calls: validate raw → normalize to domain type → return output schema; never fabricate missing fields
Contributing
Issues and pull requests are welcome. Run checks and tests before submitting:
bun run devcheck
bun run testLicense
Apache-2.0 — see LICENSE for details.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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/cyanheads/bls-labor-mcp-server'
If you have feedback or need assistance with the MCP directory API, please join our Discord server