@pipeworx/us-econ-calendar

Connect: https://pipeworx.io/mcp — every tool in the catalog, including @pipeworx/us-econ-calendar’s. Install: one-click buttons

Connect to just the @pipeworx/us-econ-calendar pack

https://gateway.pipeworx.io/us-econ-calendar/mcp — only @pipeworx/us-econ-calendar’s own tools, nothing else in the catalog.

No MCP client? Skip the connection: POST https://gateway.pipeworx.io/v1/tools/search_packs {"query":"..."} to find a tool below, GET /v1/tools/<name> for its schema, POST the same URL with arguments for the data — see For AI agents.

Tools: 3

US macro-economic release SCHEDULE — when CPI, PPI, the Employment Situation (jobs report), JOLTS, FOMC meetings, weekly DOL jobless-claims reports and Treasury auctions happen, in Eastern Time. This pack answers when, never what the number was — for the released values, use bls or fred.

Tools

  • econ_calendar_upcoming({hours?, days?, types?}) — every tracked release falling in the next N hours/days, sorted by datetime.
  • econ_calendar_next({release, from?}) — the next occurrence of one release type (cpi, ppi, employment_situation, jolts, fomc, claims, treasury_auction).
  • econ_calendar_recent({days?}) — releases in the last N days (“what came out this week”).

Auth

Keyless. No platform key, no BYO key.

Data sources

Important gotcha: bls.gov refuses the clients we SERVE from — but not Node

www.bls.gov sits behind Akamai bot-management. What it refuses is the client FINGERPRINT, not our address and not our user-agent. Measured 2026-09-13 from one laptop on one residential IP, within the same minute:

ClientResult
curl, plain UA403 Access Denied (AkamaiGHost)
curl, full Chrome header set (UA, sec-ch-ua-*, all four Sec-Fetch-*)403
curl https://www.bls.gov/robots.txt403
a deployed Cloudflare Worker fetch() (measured in #1914)403
node fetch() (undici) with the same Chrome headers200, 55,572 bytes, the real schedule table

Headers and IP were held constant across the curl and Node rows, so the difference is the TLS/HTTP stack. Two consequences, and they point opposite ways:

  • At runtime the pack still cannot fetch bls.gov. The Worker is one of the refused clients, so the CPI / PPI / Employment-Situation / JOLTS tables in src/index.ts remain a checked-in copy with a coverage horizon. Nothing about the sections below changes.
  • At refresh time we can. scripts/refresh-econ-calendar.mjs runs on a dev machine, reads all four schedule pages with Node’s fetch, and rewrites the tables. The annual refresh is a command, not a browser-and-retype chore.

Re-measure rather than trust this table; bot rules move. node scripts/refresh-econ-calendar.mjs --check is the probe — it exits non-zero on drift and prints the HTTP status when bls.gov refuses it.

(Separately: data.bls.gov’s general pages are blocked too, while its /timeseries/<series> data-table path is not — but that path carries series data, never schedule information.)

Coverage is reported on every response — and it expires

The BLS legs are DATA WITH AN EXPIRY DATE, so every response carries a schedule_coverage array saying, per release, how far this pack reaches and on what basis:

  • cpi / ppi / employment_situation / jolts — basis: "published calendar": BLS issues these dates about a year ahead, so they have a horizon. Carries covered_through (the last date this pack answers for) and source_vintage (when the dates were last brought current).
  • fomc / treasury_auction — basis: "live upstream". No expiry.
  • claims — basis: "computed cadence". Answers for any future date; only the federal-holiday overrides are hardcoded, and they currently cover 2026.

When a query reaches past covered_through:

  • econ_calendar_upcoming / _recent add a coverage_warning array naming the release, the cutoff and the fact that our data ran out — so an empty events list is never read as “nothing is scheduled”.
  • econ_calendar_next returns found: false with reason: "schedule_coverage_exhausted", covered_through, and a hint that says in words that BLS has very likely published past that date and we have not refreshed.

pnpm check:econ-calendar-coverage is the other half: a CI gate (and pre-push gate) that warns from 120 days of coverage out and fails the build at 60, so the refresh below gets done while there is still slack. Under 60 days it passes (with a warning on every run) only while SCHEDULE_VINTAGE is under 14 days old — i.e. somebody ran the refresh against bls.gov in the last two weeks and bls.gov genuinely had nothing newer. Under 14 days of coverage it fails whatever the vintage says. Do not silence it — run the refresh.

Refreshing the BLS schedule (automated weekly — you probably have nothing to do)

.github/workflows/econ-calendar-refresh.yml does this every Monday and commits what it finds, including the SCHEDULE_VINTAGE stamp. Nobody has to remember it, and you do not need to run anything before pushing.

That workflow exists because the thing this section used to insist on was false. “bls.gov only answers a laptop” was inferred from a deployed Cloudflare Worker getting 403 and curl getting 403, and it turned the refresh into a fortnightly human errand. Measured 2026-10-14 from a GitHub Actions ubuntu-latest runner (workflow run 38497568684): all four schedule pages answered HTTP 200 with the real release-list table, from Node’s fetch(). The refusal tracks the client stack, not the IP class — a Worker and curl are refused from anywhere, Node’s fetch() is served from a laptop, a mini and an Azure runner alike. So it is ordinary CI work (fleet #2680).

By hand, if you are debugging the pipeline or want it stamped now:

node scripts/refresh-econ-calendar.mjs --write   # merge any new rows, stamp the vintage + horizon
node scripts/check-econ-calendar-coverage.mjs    # confirm the gate is green
# then commit mcps/us-econ-calendar/src/index.ts (tables, vintage and horizon together)

--write stamps SCHEDULE_VINTAGE to today even when every table already matches bls.gov — the vintage means “compared against bls.gov on this date”, not “rows last changed on this date”.

What the gate asks now, and why it stopped asking the other thing

The old hard floor is gone (fleet #2680). It failed at 14 days of remaining coverage “whatever the vintage says”, and when BLS has published nothing further, nothing a pusher does makes that green. It was due to block every lane’s push from 2026-11-18 over JOLTS, whose 2027 dates do not exist on bls.gov, in BLS’s .ics, in FRED release 192 or in OMB’s PFEI schedule. The same shape had already cost two days on 2026-10-03 (docs/checks-that-cannot-fail.md).

What replaced it: the pack records SCHEDULE_UPSTREAM_HORIZON — how far bls.gov itself had published, per release, as of the vintage — so the gate can ask the question that has an action behind it.

Gate verdictMeansWho can fix it
behind-upstream FAILthe last shipped row is earlier than bls.gov’s ownyou — refresh --write adds the rows
stale-vintage FAILnobody compared us to bls.gov in >60 daysyou, and it means the weekly workflow has stopped
forged-vintage FAILthe stamp is dated in the futurewhoever hand-edited it; only --write may write it
unparseable FAILa table, the vintage or the horizon cannot be readyou — fix the parser in the same commit
at-upstream-horizon pass, loudlyshort on coverage, but level with bls.govnobody — upstream has not published
okreaches past 60 days—

Short coverage at parity is still reported on every run, with the date the pack stops answering for that release, and the pack says it to callers too (schedule_coverage.at_upstream_horizon / coverage_warning). It is just not a red push gate, because a red gate nobody can clear blocks everyone and teaches --no-verify. node scripts/check-econ-calendar-coverage.mjs --selftest runs all six verdicts against fixtures and asserts each one, red paths included.

--write merges, it does not replace, in BOTH directions. bls.gov wins for every reference month it lists; every row for a month it does not list is kept. Both directions is the fix from #2680 — the merge used to keep only rows past upstream’s horizon, and bls.gov’s page is a ~14-month rolling window, so elapsed months drop off it. Measured 2026-10-14 against the shipped pack, one --write would have deleted 11 CPI, 11 PPI, 11 Employment Situation and 12 JOLTS elapsed rows — taking JOLTS from 13 rows to 1 — and econ_calendar_recent would have gone quietly empty while every coverage check stayed green, because they all read the LAST date.

Row-level provenance. A row bls.gov did not list when it was written carries src: 'omb-pfei' and reports OMB’s PFEI schedule as its source, not BLS. Before #2680 the source string was a per-table constant, so 36 OMB-sourced 2027 rows told callers they came from bls.gov. bls.gov has since published those rows itself and agrees row-for-row, so none is OMB-only today and the markers are cleared — tests/econ-calendar-row-provenance.test.ts keeps the mechanism honest for next autumn.

1. Preferred: scripts/refresh-econ-calendar.mjs. It fetches all four schedule pages from bls.gov with Node’s fetch (which Akamai lets through — see the table above), parses the Reference Month / Release Date / Release Time rows, and diffs them against the tables in src/index.ts:

node scripts/refresh-econ-calendar.mjs           # dry run: fetch + diff, exit 1 on drift, writes nothing
node scripts/refresh-econ-calendar.mjs --write   # merge drifted tables + stamp SCHEDULE_VINTAGE
node scripts/check-econ-calendar-coverage.mjs    # confirm the gate is green

Commit the pack change and the stamped vintage together. Verified 2026-09-13: --check returned 52 rows across the four releases and matched the checked-in tables exactly, which is also an independent re-verification that what we ship is what BLS published.

It refuses rather than returns empty — a block page, renamed table headers, or a table that parses to zero rows all throw. --selftest proves those three directions plus a real parse, with no network.

2. Fallback: FRED’s release-date API. If Akamai’s rules move and Node stops clearing them, the St. Louis Fed mirrors each BLS release’s scheduled future dates, keyless-to-us through our own fred pack:

scripts/pwcall.sh call fred_release_dates '{"release_id":10,"limit":24,"include_release_dates_with_no_data":true}'

Release ids: CPI 10 · PPI 46 · Employment Situation 50 · JOLTS 192. Verified 2026-09-13 — all four returned exactly the dates in our tables, through the same mid-December cutoff, which is BLS’s own published horizon. FRED does NOT give you two things the tables need: the release time (stable: 08:30 ET for CPI, PPI and the Employment Situation, 10:00 ET for JOLTS) and the reference period (CPI/PPI/empsit describe the prior month; JOLTS lags two).

3. When bls.gov AND FRED both stop at the same date, that’s not drift — BLS genuinely hasn’t published next year’s calendar yet. Hit 2026-10-03: the 60-day gate went red (jolts had 59 days left), but refresh-econ-calendar.mjs reported zero drift — bls.gov’s own per-release pages, the FRED release-dates endpoint, and BLS’s own master .ics calendar (https://www.bls.gov/schedule/news_release/bls.ics) all independently stopped at the exact same row. That’s expected: under OMB Statistical Policy Directive No. 3, agencies submit next year’s schedule to OIRA and it is not required to post until December, so a gap where “the pack’s refresh has zero drift against three independent BLS-adjacent sources, and the gate is still red” is real, not a parser bug — don’t spend time re-probing bls.gov harder.

The escape hatch that saved it this time: OMB’s PFEI schedule, a different official annual calendar that can land earlier than BLS’s own page. OMB publishes a consolidated “Schedule of Release Dates for Principal Federal Economic Indicators” (PFEI) covering CPI, PPI, Employment Situation (and Real Earnings, ECI, Productivity & Costs, import/export prices — but not JOLTS, which OMB’s directive does not track) at https://www.whitehouse.gov/omb/information-resources/guidance/us-principal-federal-economic-indicators/. Its next-year PDF (FY<year+1>-PFEI.pdf) posted 2026-10-01, two days before bls.gov’s own per-release pages had moved — each release’s table gives the release day-of-month for every month of the next calendar year; cross the day with “data are for previous month” (true for CPI/PPI/Employment Situation) to get the reference month, same shape as the existing ScheduleRow entries. Because it isn’t JOLTS-covered, that leg has no early-release lever: when only jolts is the gate’s failing leg and this PDF doesn’t help, that is a real, unresolvable-today external gap, not a refresh you haven’t tried hard enough. Run refresh --write anyway: the stamped vintage is exactly what lets the gate pass on that gap (above), and the gate re-arms after 14 days. Do NOT guess a JOLTS date from its recent cadence, which has been irregular since the 2025 government-shutdown lapse and is not safe to extrapolate. Only if the table falls under 14 days of coverage with still nothing published anywhere is it a real outage of that leg — then escalate it rather than editing the gate.

Does the egress relay clear bls.gov? The question is moot — do not wire it. The relay (supabase/functions/egress-proxy, the non-CF hop this pack already uses for its TreasuryDirect leg) exists for upstreams that block Cloudflare egress addresses. bls.gov is not that class: the block follows the client fingerprint, not the IP, which is why curl is refused from a residential address and Node is served from the same one. It was never probed through the relay and now does not need to be — the refresh runs on a dev machine where plain Node already works, and allow-listing a host to route a build-time chore through a production relay buys nothing. If someone ever wants the pack to fetch bls.gov at runtime, that is a different question and the relay is an untested guess at it; the honest starting point is that Deno’s stack has not been measured against Akamai here.

Who does it: whoever the 60-day gate stops. It fails on push with the release name, the cutoff and this procedure, so the person holding a commit is the person who fixes it. --write stamps SCHEDULE_VINTAGE for you. Never hand-edit it: the gate trusts a fresh stamp to mean bls.gov was really checked, so a hand-bumped vintage is a forged verification.

Treasury and the Fed calendar have no such problem — both are fetched live (Treasury directly; FOMC via fed-comms) and need no manual refresh.

Tools

  • econ_calendar_upcoming — Upcoming US macro release SCHEDULE — which of CPI, PPI, the Employment Situation (jobs report), JOLTS, an FOMC meeting/rate decision, the weekly DOL jobless-claims report, and Treasury auctions fall i
  • econ_calendar_next — When the NEXT occurrence of a specific US macro release is — next CPI, next PPI, next jobs report, next JOLTS, next FOMC meeting, next weekly jobless-claims report, or next Treasury auction — with its
  • econ_calendar_recent — What US macro releases happened in the last N days — ‘what was released this week’, ‘has CPI come out yet this month’. Lists CPI/PPI/Employment Situation/JOLTS/FOMC/claims/Treasury-auction events whos

Tools

  • econ_calendar_next — When the NEXT occurrence of a specific US macro release is — next CPI, next PPI, next jobs report, next JOLTS, next FOMC meeting, next weekly jobless-claims report, or next Treasury auction — with its
  • econ_calendar_recent — What US macro releases happened in the last N days — 'what was released this week', 'has CPI come out yet this month'. Lists CPI/PPI/Employment Situation/JOLTS/FOMC/claims/Treasury-auction events whos
  • econ_calendar_upcoming — Upcoming US macro release SCHEDULE — which of CPI, PPI, the Employment Situation (jobs report), JOLTS, an FOMC meeting/rate decision, the weekly DOL jobless-claims report, and Treasury auctions fall i

Regenerated from source · build October 28, 2026