Labor Market Intelligence
Enables Claude users to research U.S. labor-market trends through live BLS and FRED data, covering employment, unemployment, wages, job openings, occupational outlook, and industry comparisons.
README
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
- In Claude, go to Settings → Connectors → Add custom connector.
- 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. - 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.
- 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_outlookfor 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_indicatorsandsearch_occupationsare backed by a catalog derived from BLS's own flat files and cross-validated during the build (seescripts/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, andMCP_PATH_TOKENlive only as Workers secrets — never returned in a tool response, never logged.src/lib/logging.tsredacts known secret values and anyapi_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.
- Known limitation: Cloudflare's own platform request logs (and
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: nullwith 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_employmentcovers long-run outlook only; pair it withfred_search_series+analyze_labor_market_trendfor 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.
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。