@pipeworx/mcp-fred 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -3
- package/package.json +3 -3
- package/server.json +1 -1
- package/src/index.ts +182 -0
- package/src/server.ts +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The St. Louis Fed's data warehouse: 800,000+ economic time series spanning interest rates, inflation, employment, GDP, money supply, exchange rates, and metro-level indicators. The most authoritative, continuously updated source for US macro and monetary data — used by economists, policymakers, and journalists.
|
|
4
4
|
|
|
5
|
-
Part of [Pipeworx](https://pipeworx.io) — an MCP gateway connecting AI agents to
|
|
5
|
+
Part of [Pipeworx](https://pipeworx.io) — an MCP gateway connecting AI agents to 1704+ live data sources. This is an independent, unofficial integration — not affiliated with, endorsed by, or published by the upstream provider.
|
|
6
6
|
|
|
7
7
|
## Why this matters for AI agents
|
|
8
8
|
|
|
@@ -55,6 +55,45 @@ pipeworx://fred/series/{series_id}/observations
|
|
|
55
55
|
|
|
56
56
|
Other agents (and `resources/read`) can resolve these to the current value of the series.
|
|
57
57
|
|
|
58
|
+
## ALFRED vintages: "what did this number say on date X"
|
|
59
|
+
|
|
60
|
+
FRED revises most series after first publication (GDP, payrolls, retail sales…).
|
|
61
|
+
`fred_get_series` defaults to the latest revised values — pass one of these to
|
|
62
|
+
ask for an earlier vintage instead:
|
|
63
|
+
|
|
64
|
+
- `as_of: "2020-03-15"` — the data exactly as it stood on that date. Shorthand
|
|
65
|
+
for `realtime_start = realtime_end = as_of`.
|
|
66
|
+
- `realtime_start` / `realtime_end` — an explicit publication window, for the
|
|
67
|
+
rarer case of a custom range rather than a single date.
|
|
68
|
+
|
|
69
|
+
Omitting all three is the default and is unchanged: no `vintage` field, same
|
|
70
|
+
values as before this parameter existed. Passing any of them adds a `vintage`
|
|
71
|
+
object to the response (`{as_of, realtime_start, realtime_end}`) so you can
|
|
72
|
+
tell which vintage you're looking at.
|
|
73
|
+
|
|
74
|
+
```js
|
|
75
|
+
fred_get_series({ series_id: "GDPC1", as_of: "2024-01-15", limit: 1 })
|
|
76
|
+
// -> the GDP print as FRED had it published on 2024-01-15, before later revisions
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Two more tools cover the revision history directly:
|
|
80
|
+
|
|
81
|
+
- **`fred_vintage_dates({ series_id })`** — every date FRED published a new
|
|
82
|
+
vintage of the series (initial release or a later revision).
|
|
83
|
+
- **`fred_revisions({ series_id, observation_date })`** — how ONE observation's
|
|
84
|
+
value changed across every vintage, from `first_release` to `latest`. Use
|
|
85
|
+
this on revision-prone series (GDP `GDPC1`, payrolls `PAYEMS`) to see the
|
|
86
|
+
originally reported number versus today's.
|
|
87
|
+
|
|
88
|
+
```js
|
|
89
|
+
fred_revisions({ series_id: "GDPC1", observation_date: "2023-10-01" })
|
|
90
|
+
// -> revisions: [{ vintage_date: "2023-10-26", value: "22490.692" }, ...]
|
|
91
|
+
// one entry per ALFRED vintage, oldest first; first_release is the
|
|
92
|
+
// earliest, latest the most recent. Consecutive vintages can repeat the
|
|
93
|
+
// same value — compare first_release.value to latest.value to see
|
|
94
|
+
// whether this observation was ever actually revised.
|
|
95
|
+
```
|
|
96
|
+
|
|
58
97
|
## Reading a `fred_get_series` response
|
|
59
98
|
|
|
60
99
|
| Field | What it is |
|
|
@@ -75,7 +114,7 @@ already mean by them.
|
|
|
75
114
|
|
|
76
115
|
## Common pitfalls
|
|
77
116
|
|
|
78
|
-
- **Vintages**: FRED preserves historical "vintages" (data as it was reported at time T). Default tool calls get the latest revised series. Pass `
|
|
117
|
+
- **Vintages**: FRED preserves historical "vintages" (data as it was reported at time T). Default tool calls get the latest revised series. Pass `as_of` (or `realtime_start`/`realtime_end`) to `fred_get_series` for as-of queries, or use `fred_revisions` to see the full history of one observation.
|
|
79
118
|
- **Frequency aggregation**: `frequency` parameter coerces to a different cadence (e.g., daily → monthly average). Default is the series' native frequency.
|
|
80
119
|
- **Units transformation**: `units` parameter computes derived series at request time (`pch` for percent change, `pca` for compound annual rate, etc.). Don't compute these client-side; let FRED do it.
|
|
81
120
|
- **Series renamed**: occasionally the Fed deprecates a series and creates a successor. Old IDs return errors. `fred_search` is the recovery path.
|
|
@@ -124,7 +163,7 @@ directly, instead of just this one's:
|
|
|
124
163
|
}
|
|
125
164
|
```
|
|
126
165
|
|
|
127
|
-
Both URLs reach the same gateway and the same
|
|
166
|
+
Both URLs reach the same gateway and the same 1704+ data sources. The
|
|
128
167
|
only difference is which pack's tools are listed **directly**; `ask_pipeworx`
|
|
129
168
|
reaches all of them from either one.
|
|
130
169
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pipeworx/mcp-fred",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "FRED MCP — Federal Reserve Economic Data (St. Louis Fed)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"@cloudflare/workers-types": "^4.20260405.1"
|
|
27
27
|
},
|
|
28
28
|
"pipeworx": {
|
|
29
|
-
"sourceHash": "v1-
|
|
30
|
-
"sourceCommit": "
|
|
29
|
+
"sourceHash": "v1-fa111ff967e8af148bb0034cc421e106d808627f312e3b60c59862685d3c2904",
|
|
30
|
+
"sourceCommit": "eb436cf60f06c9b79701a15cd1670609580544a5"
|
|
31
31
|
}
|
|
32
32
|
}
|
package/server.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "io.github.pipeworx-io/fred",
|
|
4
4
|
"title": "Fred",
|
|
5
5
|
"description": "FRED MCP — Federal Reserve Economic Data (St. Louis Fed)",
|
|
6
|
-
"version": "0.1.
|
|
6
|
+
"version": "0.1.3",
|
|
7
7
|
"websiteUrl": "https://pipeworx.io/packs/fred",
|
|
8
8
|
"repository": {
|
|
9
9
|
"url": "https://github.com/pipeworx-io/mcp-fred",
|
package/src/index.ts
CHANGED
|
@@ -678,10 +678,15 @@ function collapse(s: string): string {
|
|
|
678
678
|
*
|
|
679
679
|
* Tools:
|
|
680
680
|
* - fred_get_series: get observations for a data series (e.g., MORTGAGE30US, HOUST, CSUSHPISA)
|
|
681
|
+
* accepts `as_of` (or explicit realtime_start/realtime_end) for ALFRED "as published on
|
|
682
|
+
* date X" vintage queries; default (no vintage args) behavior is unchanged.
|
|
681
683
|
* - fred_search: search for series by keyword
|
|
682
684
|
* - fred_series_info: get metadata about a series
|
|
683
685
|
* - fred_category: browse FRED categories
|
|
684
686
|
* - fred_releases: get latest data releases
|
|
687
|
+
* - fred_release_dates: release calendar (past + scheduled)
|
|
688
|
+
* - fred_vintage_dates: every ALFRED vintage (revision) date for a series
|
|
689
|
+
* - fred_revisions: how one observation's value changed across vintages (first release -> latest)
|
|
685
690
|
*/
|
|
686
691
|
|
|
687
692
|
|
|
@@ -765,11 +770,45 @@ const tools: McpToolExport['tools'] = [
|
|
|
765
770
|
// a caller who wants a series in chronological order can ask for it
|
|
766
771
|
// instead of reversing a truncated tail (fleet #718).
|
|
767
772
|
sort_order: { type: 'string', description: 'Observation order: desc = newest first (default), asc = oldest first.', enum: ['asc', 'desc'] },
|
|
773
|
+
as_of: { type: 'string', description: 'ALFRED vintage query: return the data as it was PUBLISHED on this date (YYYY-MM-DD), before any later revisions. Shorthand for realtime_start=realtime_end=as_of. Omit for the default: today\'s latest-revised values (unchanged from before this parameter existed).' },
|
|
774
|
+
realtime_start: { type: 'string', description: 'ALFRED vintage query: start of the realtime (publication) window, YYYY-MM-DD. Use with realtime_end for a custom vintage range; use `as_of` for the common single-date case. Omit for the default latest-revised behavior.' },
|
|
775
|
+
realtime_end: { type: 'string', description: 'ALFRED vintage query: end of the realtime (publication) window, YYYY-MM-DD. See realtime_start.' },
|
|
768
776
|
_apiKey: { type: 'string', description: 'FRED API key' },
|
|
769
777
|
},
|
|
770
778
|
required: ['series_id', '_apiKey'],
|
|
771
779
|
},
|
|
772
780
|
},
|
|
781
|
+
{
|
|
782
|
+
name: 'fred_vintage_dates',
|
|
783
|
+
description:
|
|
784
|
+
'ALFRED revision history: every date FRED published a new vintage (initial release or a later revision) of a series. Use before fred_revisions to see which dates have a distinct vintage, or pass one of these dates as `as_of`/`realtime_start`+`realtime_end` to fred_get_series to get the data as it stood on that date.',
|
|
785
|
+
summary: 'The ALFRED vintage (revision) dates for one FRED series, from the St. Louis Fed.',
|
|
786
|
+
inputSchema: {
|
|
787
|
+
type: 'object' as const,
|
|
788
|
+
properties: {
|
|
789
|
+
series_id: { type: 'string', description: 'FRED series ID (e.g., "GDPC1", "PAYEMS")' },
|
|
790
|
+
limit: { type: 'number', description: 'Max vintage dates to return (1-10000, default 100)' },
|
|
791
|
+
sort_order: { type: 'string', description: 'asc = oldest vintage first, desc = newest vintage first (default desc)', enum: ['asc', 'desc'] },
|
|
792
|
+
_apiKey: { type: 'string', description: 'FRED API key' },
|
|
793
|
+
},
|
|
794
|
+
required: ['series_id', '_apiKey'],
|
|
795
|
+
},
|
|
796
|
+
},
|
|
797
|
+
{
|
|
798
|
+
name: 'fred_revisions',
|
|
799
|
+
description:
|
|
800
|
+
'How ONE observation (one date) in a FRED series was revised over time, from first release through every later vintage to the latest. Use on series that get revised after initial publication (e.g. GDP "GDPC1", payrolls "PAYEMS") to see the originally reported value versus the current one. Returns every ALFRED vintage on record for that observation, oldest first — consecutive vintages can repeat the same value, so compare `first_release.value` to `latest.value` (or scan `revisions`) to see whether it was ever actually revised.',
|
|
801
|
+
summary: 'The revision history of one observation in a FRED series, across every ALFRED vintage.',
|
|
802
|
+
inputSchema: {
|
|
803
|
+
type: 'object' as const,
|
|
804
|
+
properties: {
|
|
805
|
+
series_id: { type: 'string', description: 'FRED series ID (e.g., "GDPC1", "PAYEMS")' },
|
|
806
|
+
observation_date: { type: 'string', description: 'The observation date to trace revisions for, YYYY-MM-DD (must match an actual observation date in the series — e.g. the first day of the quarter for quarterly series).' },
|
|
807
|
+
_apiKey: { type: 'string', description: 'FRED API key' },
|
|
808
|
+
},
|
|
809
|
+
required: ['series_id', 'observation_date', '_apiKey'],
|
|
810
|
+
},
|
|
811
|
+
},
|
|
773
812
|
{
|
|
774
813
|
name: 'fred_search',
|
|
775
814
|
description:
|
|
@@ -866,6 +905,10 @@ async function callTool(name: string, args: Record<string, unknown>): Promise<un
|
|
|
866
905
|
return getReleases(key, args);
|
|
867
906
|
case 'fred_release_dates':
|
|
868
907
|
return getReleaseDates(key, args);
|
|
908
|
+
case 'fred_vintage_dates':
|
|
909
|
+
return getVintageDates(key, args);
|
|
910
|
+
case 'fred_revisions':
|
|
911
|
+
return getRevisions(key, args);
|
|
869
912
|
default:
|
|
870
913
|
throw new Error(`Unknown tool: ${name}`);
|
|
871
914
|
}
|
|
@@ -894,8 +937,23 @@ function normalizeFredFrequency(v: unknown): string | null {
|
|
|
894
937
|
|
|
895
938
|
// ── Tool implementations ────────────────────────────────────────────────
|
|
896
939
|
|
|
940
|
+
// ALFRED vintage args. `as_of` is shorthand for realtime_start=realtime_end=as_of;
|
|
941
|
+
// explicit realtime_start/realtime_end (either or both) take precedence over
|
|
942
|
+
// `as_of` when present, so a caller can set a custom window. Absent all three,
|
|
943
|
+
// this returns {} and callers of getSeries set nothing extra on the request —
|
|
944
|
+
// which is the point: default behavior must stay byte-for-byte unchanged.
|
|
945
|
+
function resolveVintageArgs(args: Record<string, unknown>): { realtimeStart?: string; realtimeEnd?: string; asOf?: string } {
|
|
946
|
+
const realtimeStart = args.realtime_start as string | undefined;
|
|
947
|
+
const realtimeEnd = args.realtime_end as string | undefined;
|
|
948
|
+
const asOf = args.as_of as string | undefined;
|
|
949
|
+
if (realtimeStart || realtimeEnd) return { realtimeStart, realtimeEnd };
|
|
950
|
+
if (asOf) return { realtimeStart: asOf, realtimeEnd: asOf, asOf };
|
|
951
|
+
return {};
|
|
952
|
+
}
|
|
953
|
+
|
|
897
954
|
async function getSeries(key: string, args: Record<string, unknown>) {
|
|
898
955
|
const seriesId = args.series_id as string;
|
|
956
|
+
const { realtimeStart, realtimeEnd, asOf } = resolveVintageArgs(args);
|
|
899
957
|
const params = new URLSearchParams({
|
|
900
958
|
series_id: seriesId,
|
|
901
959
|
api_key: key,
|
|
@@ -908,6 +966,8 @@ async function getSeries(key: string, args: Record<string, unknown>) {
|
|
|
908
966
|
if (f) params.set('frequency', f); // unknown → drop, return native frequency (don't 400)
|
|
909
967
|
}
|
|
910
968
|
if (args.units) params.set('units', args.units as string);
|
|
969
|
+
if (realtimeStart) params.set('realtime_start', realtimeStart);
|
|
970
|
+
if (realtimeEnd) params.set('realtime_end', realtimeEnd);
|
|
911
971
|
// FRED's observations API defaults to sort_order=asc + ALL observations, so a
|
|
912
972
|
// bare fred_get_series(UNRATE) returned the full series from 1948 OLDEST-first
|
|
913
973
|
// and ignored the documented `limit: 20` — agents read a decades-old value,
|
|
@@ -917,6 +977,8 @@ async function getSeries(key: string, args: Record<string, unknown>) {
|
|
|
917
977
|
params.set('limit', String(Math.min(100000, Math.max(1, (args.limit as number) ?? 20))));
|
|
918
978
|
|
|
919
979
|
const infoParams = new URLSearchParams({ series_id: seriesId, api_key: key, file_type: 'json' });
|
|
980
|
+
if (realtimeStart) infoParams.set('realtime_start', realtimeStart);
|
|
981
|
+
if (realtimeEnd) infoParams.set('realtime_end', realtimeEnd);
|
|
920
982
|
const [data, infoRaw] = await Promise.all([
|
|
921
983
|
fredFetch(`${BASE}/series/observations?${params}`) as Promise<{
|
|
922
984
|
realtime_start: string;
|
|
@@ -953,6 +1015,8 @@ async function getSeries(key: string, args: Record<string, unknown>) {
|
|
|
953
1015
|
if (f) p2.set('frequency', f);
|
|
954
1016
|
}
|
|
955
1017
|
if (args.units) p2.set('units', args.units as string);
|
|
1018
|
+
if (realtimeStart) p2.set('realtime_start', realtimeStart);
|
|
1019
|
+
if (realtimeEnd) p2.set('realtime_end', realtimeEnd);
|
|
956
1020
|
const retry = (await fredFetch(`${BASE}/series/observations?${p2}`)) as typeof data;
|
|
957
1021
|
if (retry.observations.length > 0) {
|
|
958
1022
|
obs = retry;
|
|
@@ -1013,6 +1077,18 @@ async function getSeries(key: string, args: Record<string, unknown>) {
|
|
|
1013
1077
|
...(infoRaw?.observation_end ? { observation_end: infoRaw.observation_end } : {}),
|
|
1014
1078
|
...(args.observation_start ? { requested_start: args.observation_start as string } : {}),
|
|
1015
1079
|
...(args.observation_end ? { requested_end: args.observation_end as string } : {}),
|
|
1080
|
+
// Only present when the caller asked for a vintage — absence here (and
|
|
1081
|
+
// everywhere above) is what keeps the no-args response byte-for-byte
|
|
1082
|
+
// identical to before this field existed.
|
|
1083
|
+
...(realtimeStart || realtimeEnd
|
|
1084
|
+
? {
|
|
1085
|
+
vintage: {
|
|
1086
|
+
...(asOf ? { as_of: asOf } : {}),
|
|
1087
|
+
...(realtimeStart ? { realtime_start: realtimeStart } : {}),
|
|
1088
|
+
...(realtimeEnd ? { realtime_end: realtimeEnd } : {}),
|
|
1089
|
+
},
|
|
1090
|
+
}
|
|
1091
|
+
: {}),
|
|
1016
1092
|
observations: rows,
|
|
1017
1093
|
};
|
|
1018
1094
|
}
|
|
@@ -1207,4 +1283,110 @@ async function getReleaseDates(key: string, args: Record<string, unknown>) {
|
|
|
1207
1283
|
};
|
|
1208
1284
|
}
|
|
1209
1285
|
|
|
1286
|
+
async function getVintageDates(key: string, args: Record<string, unknown>) {
|
|
1287
|
+
const seriesId = args.series_id as string;
|
|
1288
|
+
const cap = Math.min(10000, Math.max(1, (args.limit as number) ?? 100));
|
|
1289
|
+
const sortOrder = (args.sort_order as string) === 'asc' ? 'asc' : 'desc';
|
|
1290
|
+
const params = new URLSearchParams({
|
|
1291
|
+
series_id: seriesId,
|
|
1292
|
+
api_key: key,
|
|
1293
|
+
file_type: 'json',
|
|
1294
|
+
sort_order: sortOrder,
|
|
1295
|
+
limit: '10000', // FRED's own cap; we slice to the caller's `limit` ourselves so `truncated` is honest
|
|
1296
|
+
});
|
|
1297
|
+
|
|
1298
|
+
const data = (await fredFetch(`${BASE}/series/vintagedates?${params}`)) as {
|
|
1299
|
+
count: number;
|
|
1300
|
+
vintage_dates: string[];
|
|
1301
|
+
};
|
|
1302
|
+
|
|
1303
|
+
const all = data.vintage_dates ?? [];
|
|
1304
|
+
const returned = all.slice(0, cap);
|
|
1305
|
+
const truncated = returned.length < all.length;
|
|
1306
|
+
return {
|
|
1307
|
+
series_id: seriesId,
|
|
1308
|
+
total_vintages: typeof data.count === 'number' ? data.count : all.length,
|
|
1309
|
+
returned: returned.length,
|
|
1310
|
+
truncated,
|
|
1311
|
+
...(truncated
|
|
1312
|
+
? { note: `Showing ${returned.length} of ${all.length} vintage dates (${sortOrder === 'desc' ? 'newest first' : 'oldest first'}); raise \`limit\` for more.` }
|
|
1313
|
+
: {}),
|
|
1314
|
+
vintage_dates: returned,
|
|
1315
|
+
};
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
// FRED's output_type=2 ("Observations by Vintage Date, All Observations")
|
|
1319
|
+
// does NOT return one row per vintage — verified against the live response
|
|
1320
|
+
// (2026-10-07, after shipping a first version that assumed it did and got
|
|
1321
|
+
// `{}` back for every vintage). It returns ONE ROW for the requested
|
|
1322
|
+
// observation_date, with the date in `date` and one EXTRA COLUMN PER VINTAGE,
|
|
1323
|
+
// named `<SERIES_ID>_<YYYYMMDD>` — e.g. `{ date: "2023-07-01",
|
|
1324
|
+
// GDPC1_20231026: "22490.692", GDPC1_20231129: "22475.212", ... }`. A missing
|
|
1325
|
+
// vintage column's value is FRED's usual "." null marker, not absence.
|
|
1326
|
+
const VINTAGE_COLUMN = /^.+_(\d{8})$/;
|
|
1327
|
+
|
|
1328
|
+
function parseVintageRow(row: Record<string, string>): { vintage_date: string; value: string | null }[] {
|
|
1329
|
+
const out: { vintage_date: string; value: string | null }[] = [];
|
|
1330
|
+
for (const [col, raw] of Object.entries(row)) {
|
|
1331
|
+
const m = col.match(VINTAGE_COLUMN);
|
|
1332
|
+
if (!m) continue; // `date` itself, or any non-vintage field FRED adds
|
|
1333
|
+
const ymd = m[1];
|
|
1334
|
+
const vintageDate = `${ymd.slice(0, 4)}-${ymd.slice(4, 6)}-${ymd.slice(6, 8)}`;
|
|
1335
|
+
out.push({ vintage_date: vintageDate, value: raw === '.' ? null : raw });
|
|
1336
|
+
}
|
|
1337
|
+
out.sort((a, b) => a.vintage_date.localeCompare(b.vintage_date));
|
|
1338
|
+
return out;
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
async function getRevisions(key: string, args: Record<string, unknown>) {
|
|
1342
|
+
const seriesId = args.series_id as string;
|
|
1343
|
+
const observationDate = args.observation_date as string;
|
|
1344
|
+
// Pinning observation_start=observation_end=observationDate and spanning the
|
|
1345
|
+
// full realtime range (FRED's documented earliest/latest sentinels) returns
|
|
1346
|
+
// the whole revision history of one observation in a single call — no need
|
|
1347
|
+
// to walk fred_vintage_dates and refetch per date.
|
|
1348
|
+
//
|
|
1349
|
+
// realtime_end must be FRED's documented max sentinel (9999-12-31), NOT
|
|
1350
|
+
// `new Date()`-derived "today" — FRED 400s "Variable realtime_end can not be
|
|
1351
|
+
// after today's date … unless it's equal to the real-time max date 9999-12-31"
|
|
1352
|
+
// whenever the CALLER's UTC day has already rolled past FRED's Central-time
|
|
1353
|
+
// day (reproduced live post-deploy on 2026-10-07: the gateway's UTC day was
|
|
1354
|
+
// 10-07 while FRED still considered it 10-06). The sentinel means "no upper
|
|
1355
|
+
// bound" to FRED, so it also means we never need to recompute it again.
|
|
1356
|
+
const params = new URLSearchParams({
|
|
1357
|
+
series_id: seriesId,
|
|
1358
|
+
api_key: key,
|
|
1359
|
+
file_type: 'json',
|
|
1360
|
+
observation_start: observationDate,
|
|
1361
|
+
observation_end: observationDate,
|
|
1362
|
+
realtime_start: '1776-07-04', // FRED's documented sentinel for "earliest possible"
|
|
1363
|
+
realtime_end: '9999-12-31', // FRED's documented sentinel for "latest possible" — never "today"
|
|
1364
|
+
output_type: '2',
|
|
1365
|
+
});
|
|
1366
|
+
|
|
1367
|
+
const data = (await fredFetch(`${BASE}/series/observations?${params}`)) as {
|
|
1368
|
+
observations: Record<string, string>[];
|
|
1369
|
+
};
|
|
1370
|
+
|
|
1371
|
+
const rows = data.observations ?? [];
|
|
1372
|
+
const row = rows.find((o) => o.date === observationDate) ?? rows[0];
|
|
1373
|
+
const revisions = row ? parseVintageRow(row) : [];
|
|
1374
|
+
|
|
1375
|
+
return {
|
|
1376
|
+
series_id: seriesId,
|
|
1377
|
+
observation_date: observationDate,
|
|
1378
|
+
// The number of ALFRED VINTAGES on record for this observation — NOT how
|
|
1379
|
+
// many times the value actually changed. Consecutive vintages can (and
|
|
1380
|
+
// often do) repeat the same value; compare first_release.value to
|
|
1381
|
+
// latest.value, or scan `revisions`, to see whether it was ever revised.
|
|
1382
|
+
revisions_count: revisions.length,
|
|
1383
|
+
...(revisions.length === 0
|
|
1384
|
+
? { note: `No observation found for ${observationDate} on ${seriesId} — confirm the date matches an actual observation (e.g. the first day of the period for quarterly/monthly series), or call fred_get_series first.` }
|
|
1385
|
+
: {}),
|
|
1386
|
+
first_release: revisions[0] ?? null,
|
|
1387
|
+
latest: revisions[revisions.length - 1] ?? null,
|
|
1388
|
+
revisions,
|
|
1389
|
+
};
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1210
1392
|
export default { tools, callTool, meter: { credits: 5 } } satisfies McpToolExport;
|
package/src/server.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprot
|
|
|
9
9
|
import pack from './index.js';
|
|
10
10
|
|
|
11
11
|
const server = new Server(
|
|
12
|
-
{ name: '@pipeworx/mcp-fred', version: '0.1.
|
|
12
|
+
{ name: '@pipeworx/mcp-fred', version: '0.1.3' },
|
|
13
13
|
{ capabilities: { tools: {} } },
|
|
14
14
|
);
|
|
15
15
|
|