Skip to main content
Glama
harperbrian

Labor Market Intelligence

by harperbrian

Labor Market Intelligence — Remote MCP Server

A read-only remote MCP server exposing U.S. Bureau of Labor Statistics (BLS) and FRED (Federal Reserve Bank of St. Louis) data to Claude as a custom connector — for career and labor-market research: employment trends, unemployment, job openings, hires, quits, wages, occupational outlook, and industry comparisons.

Runs on Cloudflare Workers. Cost: $0/month on Cloudflare's free tier.

Built incrementally against a fully verified implementation plan — every BLS and FRED endpoint, series ID, and data-shape quirk referenced in this codebase was confirmed against the live APIs (not assumed from documentation alone) before being implemented.

Status

All 10 implementation checkpoints complete and live-verified against the deployed Cloudflare Worker. 89 unit tests, clean typecheck, all 16 tools confirmed working against real BLS/FRED data.

Connecting to Claude

  1. In Claude, go to Settings → Connectors → Add custom connector.

  2. Remote MCP server URL: https://<your-worker>.<your-subdomain>.workers.dev/mcp/<MCP_PATH_TOKEN> — treat this URL as a credential; the token is the only thing authorizing access.

  3. OAuth Client ID / Secret: leave both blank. This server is authless — the secret path is the credential, and Claude supports authless remote MCP servers natively.

  4. Transport: Streamable HTTP (SSE is the legacy fallback).

Tools (16)

Low-level source tools — thin, faithful passthrough to BLS/FRED

Tool

What it does

fred_search_series

Full-text search over FRED series

fred_get_series

Fetch observations for a known FRED series ID

fred_get_latest

Fetch only the most recent FRED observation

bls_get_series

Fetch up to 50 BLS series over up to a 20-year span; aspects=true unlocks Employment Projections' projected employment/openings/wage

bls_list_surveys

List all 70 BLS survey program abbreviations

bls_popular_series

List BLS's "most requested" series (not universal — returns nothing for EP/JOLTS/OR)

search_indicators

Search ~13 curated headline indicators (unemployment, JOLTS metrics, payrolls, etc.) for their BLS/FRED IDs

search_occupations

Search ~1,113 SOC occupation titles for their BLS Employment Projections series ID

Research tools — composed, higher-level analysis

Tool

What it does

analyze_labor_market_trend

Change and CAGR for one BLS/FRED series over a date range

compare_labor_market_series

Compare 2-10 BLS/FRED series (can mix sources) side by side

analyze_job_market_conditions

Snapshot: unemployment, payrolls, openings, hires, quits, layoffs — each with 1mo/12mo change

analyze_industry_employment

Long-run BLS Employment Projections outlook for a whole industry

get_occupation_outlook

Full BLS Employment Projections outlook for one occupation (employment, openings, median wage)

compare_occupations

Compare 2-20 occupations' outlook in a single batched BLS call

analyze_wage_trends

Trend/CAGR for an aggregate wage measure (default: average hourly earnings)

ping

Connectivity check; uses no BLS/FRED quota

Every tool is annotated readOnlyHint: true and enforced by an automated test (test/unit/server.test.ts) — no tool can mutate state, and none accepts a caller-supplied URL to fetch.

What each source actually provides

Verified against the live APIs, not assumed from documentation:

  • FRED provides broad macro context (GDP, rates, recession indicators), unit transforms, and a real full-text series search. It also republishes many BLS series (UNRATE, PAYEMS, JOLTS metrics) with a clean uniform cadence.

  • BLS is authoritative for anything occupational: Employment Projections (EP, outlook/openings/wage) and OEWS (OE, current wages) exist only on the BLS side — FRED does not carry the National Employment Matrix.

  • OEWS has no history via the API — every series returns exactly one reference year, confirmed by requesting a 10-year range and getting nine "No Data Available" messages plus one datapoint. Occupational wage trends are not retrievable from this API; use get_occupation_outlook for a current-year median wage snapshot instead.

  • Employment Projections is not a time series — one base year + one projection year (currently ~10 years out), updated at most twice a year.

  • Annual openings figures include replacement demand (workers exiting or transferring out), not just net employment growth — a common misreading.

  • No official BLS series-search API exists. search_indicators and search_occupations are backed by a catalog derived from BLS's own flat files and cross-validated during the build (see scripts/build-catalog.ts), not hand-typed or guessed.

Attribution

Every BLS-derived response carries the retrieval timestamp and the exact disclaimer required by BLS's Terms of Service: "BLS.gov cannot vouch for the data or analyses derived from these data after the data have been retrieved from BLS.gov." FRED-derived responses carry their own source attribution.

Anything this server computes (percent change, CAGR, month-over-month deltas) is returned under a separate computedByServer field, explicitly labeled as server-calculated — never presented as an official BLS or FRED statistic. Tool instructions direct Claude to preserve both when answering.

Security model

  • Read-only. No tool mutates state or accepts a caller-supplied URL.

  • Secret-path authentication. The endpoint is /mcp/<256-bit token>. Claude's connector UI accepts a URL but not custom headers, so the credential lives in the path. Comparison is constant-time over SHA-256 digests; every failure (wrong token, missing token, unknown route) returns an identical 404 — no oracle for guessing.

  • Inbound rate limiting. ~60 requests/minute per IP (cf-connecting-ip, set by Cloudflare and unspoofable by the client), checked before auth so a flood can't spend CPU on token comparison. Backed by Workers KV; fails open (allows) if KV isn't bound.

  • Outbound BLS budget guard. A circuit breaker defaulting to 450 of BLS's 500/day registered-key quota — exhausting it fails the call before any network request, protecting the real quota from a runaway loop.

  • Secrets never leave the server. BLS_API_KEY, FRED_API_KEY, and MCP_PATH_TOKEN live only as Workers secrets — never returned in a tool response, never logged. src/lib/logging.ts redacts known secret values and any api_key=/registrationkey= pattern from every log record; this is asserted by unit tests, not just intended.

    • Known limitation: Cloudflare's own platform request logs (and wrangler tail) record the full request URL, including the path token — this is outside application code's control. Don't share raw log output publicly; rotate the token (wrangler secret put MCP_PATH_TOKEN, then re-paste the new URL into Claude) if you ever do.

Caching

Workers KV, two-tier: a "fresh" entry per the TTL table below, plus a 35-day "stale backup" written alongside every success. If a live call fails or the BLS budget is exhausted, the stale backup is served instead of failing outright — flagged explicitly in the response's limitations field so Claude never presents stale data as current without saying so.

Data

TTL

Why

Employment Projections / OEWS

30 days

Updated at most twice a year

BLS surveys / popular-series lists

7 days

Near-static

FRED search

24 hours

Stable

Monthly series (CES/CPS/JOLTS)

6 hours

Monthly releases

fred_get_latest

1 hour

Freshness matters most here

Cache keys are derived only from the tool name and its arguments — never from environment or secrets — so no key material can leak into a cache key.

Project structure

src/
  index.ts               Worker entry: routing, auth, rate limiting
  server.ts               MCP server construction + tool registration
  env.ts                  Env typing + secret names
  errors.ts                Typed error hierarchy (network/timeout/429/5xx/BLS-200-with-error-body)
  sources/
    http.ts               Shared fetch: timeout, retry/backoff
    bls.ts                BLS v2 client
    fred.ts               FRED client
  catalog/
    occupations.json      1,113 SOC occupations -> EP series ID (build-generated, validated)
    industries.json        423 EP industries -> series ID (build-generated, validated)
    indicators.ts           ~13 curated headline indicators (individually live-verified)
    search.ts               Shared token-matching + relevance-ranking search
  tools/
    source/                Thin passthrough tools
    research/               Composed analysis tools
  lib/
    cache.ts               Workers KV two-tier cache
    ratelimit.ts             BLS daily budget guard + inbound per-IP limiter
    envelope.ts              Response envelope: citations, timestamps, disclaimers
    stats.ts                 Deterministic trend math
    logging.ts                Structured logs with secret redaction
scripts/
  build-catalog.ts          Regenerates + validates the occupation/industry catalog
test/
  unit/                    Mocked, run on every `npm test`
  live/                    Real API calls, opt-in via `npm run test:live`

Local development

npm install
cp .dev.vars.example .dev.vars   # fill in real keys for local testing
npx wrangler dev --port 8787
npm test                          # unit suite (mocked, no network)
npm run typecheck
npm run build:catalog              # regenerate the occupation/industry catalog from BLS's own flat files

To verify against real BLS/FRED data locally (never commits or logs the keys):

BLS_API_KEY=your_key FRED_API_KEY=your_key npm run test:live

Deployment

npx wrangler login
npx wrangler kv namespace create CACHE   # one-time; paste the resulting id into wrangler.toml
npx wrangler secret put BLS_API_KEY
npx wrangler secret put FRED_API_KEY
npx wrangler secret put MCP_PATH_TOKEN     # generate with: openssl rand -hex 32
npx wrangler deploy
curl https://<your-worker>.<your-subdomain>.workers.dev/health

Secrets and the KV binding persist across wrangler deploy — you only set them once, not on every deploy.

Known limitations

  • Employment Projections and OEWS are single-reference-year snapshots, not time series — every research tool explicitly detects and reports this (trend: null with an explanatory limitation) rather than fabricating a trend from one datapoint.

  • No verified crosswalk exists between BLS Employment Projections' industry codes and BLS's monthly CES industry employment series (different classification schemes) — analyze_industry_employment covers long-run outlook only; pair it with fred_search_series + analyze_labor_market_trend for current monthly industry employment.

  • SOC occupation codes change between Employment Projections vintages — comparing an occupation across catalog rebuilds separated by a vintage change is not reliable.

  • The inbound rate limiter is a best-effort fixed-window counter (a read-then-write race can under-count by a request or two under heavy concurrency) — an accepted tradeoff for a low-volume personal connector, not a precision guarantee.

-
license - not tested
Not graded
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

  • Fetch US Bureau of Labor Statistics data — CPI, unemployment, wages, JOLTS, and more via MCP.

  • Macro data for AI agents: GDP, inflation, unemployment and more (World Bank, US BLS). No keys.

  • SEC EDGAR, CFPB complaints, and BLS employment data. 4 tools.

View all MCP Connectors

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/harperbrian/labor-market-intelligence-mcp'

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