dist / toolsProvider.js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.toolsProvider = toolsProvider;
const sdk_1 = require("@lmstudio/sdk");
const zod_1 = require("zod");
const config_1 = require("./config");
const fredClient_1 = require("./fredClient");
function getKey(ctl) {
const g = ctl.getGlobalPluginConfig(config_1.globalConfigSchematics);
const key = g.get("fredApiKey")?.trim();
if (!key)
throw new Error("FRED API Key not set. Please set it in Plugin Settings > Global Config (fred-mcp-lmstudio). Get key at https://fred.stlouisfed.org/docs/api/api_key.html");
return key;
}
async function toolsProvider(ctl) {
const tools = [];
// ---------- fred_browse ----------
tools.push((0, sdk_1.tool)({
name: "fred_browse",
description: "Browse FRED catalog: categories, releases, sources, category_series, release_series. Access 800k+ series structure.",
parameters: {
browse_type: zod_1.z.enum(["categories", "releases", "sources", "category_series", "release_series"]).describe("Type of browsing"),
category_id: zod_1.z.number().int().optional().describe("Category ID (for subcategories or category_series)"),
release_id: zod_1.z.number().int().optional().describe("Release ID (for release_series)"),
limit: zod_1.z.number().int().min(1).max(1000).optional(),
offset: zod_1.z.number().int().min(0).optional(),
order_by: zod_1.z.string().optional().describe("Field to order by"),
sort_order: zod_1.z.enum(["asc", "desc"]).optional(),
},
implementation: async ({ browse_type, category_id, release_id, limit, offset, order_by, sort_order }) => {
try {
const apiKey = getKey(ctl);
const perChat = ctl.getPluginConfig(config_1.configSchematics);
const defLimit = perChat.get("defaultLimit");
const defSort = perChat.get("defaultSortOrder");
const base = (0, fredClient_1.clean)({ limit: limit ?? defLimit, offset, order_by, sort_order: sort_order ?? defSort });
let endpoint = "";
let params = { ...base };
switch (browse_type) {
case "categories":
endpoint = category_id !== undefined ? "category/children" : "category/children";
params.category_id = category_id ?? 0;
break;
case "releases":
endpoint = "releases";
break;
case "sources":
endpoint = "sources";
break;
case "category_series":
if (category_id === undefined)
return "Error: category_id required for category_series";
endpoint = "category/series";
params.category_id = category_id;
break;
case "release_series":
if (release_id === undefined)
return "Error: release_id required for release_series";
endpoint = "release/series";
params.release_id = release_id;
break;
}
const data = await (0, fredClient_1.fredFetch)(endpoint, params, apiKey);
return JSON.stringify(data, null, 2).slice(0, 12000);
}
catch (e) {
return `Error in fred_browse: ${e.message}`;
}
},
}));
// ---------- fred_search ----------
tools.push((0, sdk_1.tool)({
name: "fred_search",
description: "Search FRED series by keywords, tags, filters. Use to discover series IDs like GDP, UNRATE, CPIAUCSL.",
parameters: {
search_text: zod_1.z.string().optional().describe("Keywords in title/description"),
search_type: zod_1.z.enum(["full_text", "series_id"]).optional(),
tag_names: zod_1.z.string().optional().describe("Comma-separated tags to include, e.g. 'gdp,quarterly'"),
exclude_tag_names: zod_1.z.string().optional().describe("Comma-separated tags to exclude"),
limit: zod_1.z.number().int().min(1).max(1000).optional(),
offset: zod_1.z.number().int().min(0).optional(),
order_by: zod_1.z.string().optional().describe("popularity, last_updated, etc."),
sort_order: zod_1.z.enum(["asc", "desc"]).optional(),
filter_variable: zod_1.z.enum(["frequency", "units", "seasonal_adjustment"]).optional(),
filter_value: zod_1.z.string().optional(),
},
implementation: async (p) => {
try {
const apiKey = getKey(ctl);
const perChat = ctl.getPluginConfig(config_1.configSchematics);
const params = (0, fredClient_1.clean)({
search_text: p.search_text,
search_type: p.search_type,
tag_names: p.tag_names,
exclude_tag_names: p.exclude_tag_names,
limit: p.limit ?? perChat.get("defaultLimit"),
offset: p.offset,
order_by: p.order_by,
sort_order: p.sort_order ?? perChat.get("defaultSortOrder"),
filter_variable: p.filter_variable,
filter_value: p.filter_value,
});
const data = await (0, fredClient_1.fredFetch)("series/search", params, apiKey);
// concise summary for LLM
const series = data.seriess ?? data.series ?? [];
const summary = series.slice(0, 20).map((s) => ({
id: s.id,
title: s.title,
units: s.units,
frequency: s.frequency,
seasonal_adjustment: s.seasonal_adjustment_short,
last_updated: s.last_updated,
popularity: s.popularity,
}));
return JSON.stringify({ count: data.count ?? series.length, summary, raw: data }, null, 2).slice(0, 15000);
}
catch (e) {
return `Error in fred_search: ${e.message}`;
}
},
}));
// ---------- fred_get_series ----------
tools.push((0, sdk_1.tool)({
name: "fred_get_series",
description: "Retrieve observations for a FRED series ID (e.g., GDP, UNRATE, CPIAUCSL). Supports date range, transformations, aggregation.",
parameters: {
series_id: zod_1.z.string().describe("FRED series ID, e.g. GDP"),
observation_start: zod_1.z.string().optional().describe("YYYY-MM-DD start"),
observation_end: zod_1.z.string().optional().describe("YYYY-MM-DD end"),
limit: zod_1.z.number().int().min(1).max(100000).optional(),
offset: zod_1.z.number().int().min(0).optional(),
sort_order: zod_1.z.enum(["asc", "desc"]).optional(),
units: zod_1.z.enum(["lin", "chg", "ch1", "pch", "pc1", "pca", "cch", "cca", "log"]).optional().describe("Transformation"),
frequency: zod_1.z.enum(["d", "w", "bw", "m", "q", "sa", "a", "wef", "weth", "wew", "wetu", "wem", "wesu", "wesa", "bwew", "bwem"]).optional(),
aggregation_method: zod_1.z.enum(["avg", "sum", "eop"]).optional(),
},
implementation: async (p) => {
try {
const apiKey = getKey(ctl);
const perChat = ctl.getPluginConfig(config_1.configSchematics);
const obsParams = (0, fredClient_1.clean)({
series_id: p.series_id,
observation_start: p.observation_start,
observation_end: p.observation_end,
limit: p.limit ?? perChat.get("defaultLimit"),
offset: p.offset,
sort_order: p.sort_order ?? "asc",
units: p.units,
frequency: p.frequency,
aggregation_method: p.aggregation_method,
});
// fetch metadata + observations in parallel
const [meta, obs] = await Promise.all([
(0, fredClient_1.fredFetch)("series", { series_id: p.series_id }, apiKey).catch(() => ({})),
(0, fredClient_1.fredFetch)("series/observations", obsParams, apiKey),
]);
const observations = obs.observations ?? [];
const result = {
series: meta.seriess?.[0] ?? meta.series?.[0] ?? { id: p.series_id },
observations_count: observations.length,
observations: observations.slice(-200), // keep last 200 to avoid token blowup
full_range: {
start: observations[0]?.date,
end: observations[observations.length - 1]?.date,
},
};
return JSON.stringify(result, null, 2).slice(0, 20000);
}
catch (e) {
return `Error in fred_get_series: ${e.message}`;
}
},
}));
return tools;
}
dist / toolsProvider.js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.toolsProvider = toolsProvider;
const sdk_1 = require("@lmstudio/sdk");
const zod_1 = require("zod");
const config_1 = require("./config");
const fredClient_1 = require("./fredClient");
function getKey(ctl) {
const g = ctl.getGlobalPluginConfig(config_1.globalConfigSchematics);
const key = g.get("fredApiKey")?.trim();
if (!key)
throw new Error("FRED API Key not set. Please set it in Plugin Settings > Global Config (fred-mcp-lmstudio). Get key at https://fred.stlouisfed.org/docs/api/api_key.html");
return key;
}
async function toolsProvider(ctl) {
const tools = [];
// ---------- fred_browse ----------
tools.push((0, sdk_1.tool)({
name: "fred_browse",
description: "Browse FRED catalog: categories, releases, sources, category_series, release_series. Access 800k+ series structure.",
parameters: {
browse_type: zod_1.z.enum(["categories", "releases", "sources", "category_series", "release_series"]).describe("Type of browsing"),
category_id: zod_1.z.number().int().optional().describe("Category ID (for subcategories or category_series)"),
release_id: zod_1.z.number().int().optional().describe("Release ID (for release_series)"),
limit: zod_1.z.number().int().min(1).max(1000).optional(),
offset: zod_1.z.number().int().min(0).optional(),
order_by: zod_1.z.string().optional().describe("Field to order by"),
sort_order: zod_1.z.enum(["asc", "desc"]).optional(),
},
implementation: async ({ browse_type, category_id, release_id, limit, offset, order_by, sort_order }) => {
try {
const apiKey = getKey(ctl);
const perChat = ctl.getPluginConfig(config_1.configSchematics);
const defLimit = perChat.get("defaultLimit");
const defSort = perChat.get("defaultSortOrder");
const base = (0, fredClient_1.clean)({ limit: limit ?? defLimit, offset, order_by, sort_order: sort_order ?? defSort });
let endpoint = "";
let params = { ...base };
switch (browse_type) {
case "categories":
endpoint = category_id !== undefined ? "category/children" : "category/children";
params.category_id = category_id ?? 0;
break;
case "releases":
endpoint = "releases";
break;
case "sources":
endpoint = "sources";
break;
case "category_series":
if (category_id === undefined)
return "Error: category_id required for category_series";
endpoint = "category/series";
params.category_id = category_id;
break;
case "release_series":
if (release_id === undefined)
return "Error: release_id required for release_series";
endpoint = "release/series";
params.release_id = release_id;
break;
}
const data = await (0, fredClient_1.fredFetch)(endpoint, params, apiKey);
return JSON.stringify(data, null, 2).slice(0, 12000);
}
catch (e) {
return `Error in fred_browse: ${e.message}`;
}
},
}));
// ---------- fred_search ----------
tools.push((0, sdk_1.tool)({
name: "fred_search",
description: "Search FRED series by keywords, tags, filters. Use to discover series IDs like GDP, UNRATE, CPIAUCSL.",
parameters: {
search_text: zod_1.z.string().optional().describe("Keywords in title/description"),
search_type: zod_1.z.enum(["full_text", "series_id"]).optional(),
tag_names: zod_1.z.string().optional().describe("Comma-separated tags to include, e.g. 'gdp,quarterly'"),
exclude_tag_names: zod_1.z.string().optional().describe("Comma-separated tags to exclude"),
limit: zod_1.z.number().int().min(1).max(1000).optional(),
offset: zod_1.z.number().int().min(0).optional(),
order_by: zod_1.z.string().optional().describe("popularity, last_updated, etc."),
sort_order: zod_1.z.enum(["asc", "desc"]).optional(),
filter_variable: zod_1.z.enum(["frequency", "units", "seasonal_adjustment"]).optional(),
filter_value: zod_1.z.string().optional(),
},
implementation: async (p) => {
try {
const apiKey = getKey(ctl);
const perChat = ctl.getPluginConfig(config_1.configSchematics);
const params = (0, fredClient_1.clean)({
search_text: p.search_text,
search_type: p.search_type,
tag_names: p.tag_names,
exclude_tag_names: p.exclude_tag_names,
limit: p.limit ?? perChat.get("defaultLimit"),
offset: p.offset,
order_by: p.order_by,
sort_order: p.sort_order ?? perChat.get("defaultSortOrder"),
filter_variable: p.filter_variable,
filter_value: p.filter_value,
});
const data = await (0, fredClient_1.fredFetch)("series/search", params, apiKey);
// concise summary for LLM
const series = data.seriess ?? data.series ?? [];
const summary = series.slice(0, 20).map((s) => ({
id: s.id,
title: s.title,
units: s.units,
frequency: s.frequency,
seasonal_adjustment: s.seasonal_adjustment_short,
last_updated: s.last_updated,
popularity: s.popularity,
}));
return JSON.stringify({ count: data.count ?? series.length, summary, raw: data }, null, 2).slice(0, 15000);
}
catch (e) {
return `Error in fred_search: ${e.message}`;
}
},
}));
// ---------- fred_get_series ----------
tools.push((0, sdk_1.tool)({
name: "fred_get_series",
description: "Retrieve observations for a FRED series ID (e.g., GDP, UNRATE, CPIAUCSL). Supports date range, transformations, aggregation.",
parameters: {
series_id: zod_1.z.string().describe("FRED series ID, e.g. GDP"),
observation_start: zod_1.z.string().optional().describe("YYYY-MM-DD start"),
observation_end: zod_1.z.string().optional().describe("YYYY-MM-DD end"),
limit: zod_1.z.number().int().min(1).max(100000).optional(),
offset: zod_1.z.number().int().min(0).optional(),
sort_order: zod_1.z.enum(["asc", "desc"]).optional(),
units: zod_1.z.enum(["lin", "chg", "ch1", "pch", "pc1", "pca", "cch", "cca", "log"]).optional().describe("Transformation"),
frequency: zod_1.z.enum(["d", "w", "bw", "m", "q", "sa", "a", "wef", "weth", "wew", "wetu", "wem", "wesu", "wesa", "bwew", "bwem"]).optional(),
aggregation_method: zod_1.z.enum(["avg", "sum", "eop"]).optional(),
},
implementation: async (p) => {
try {
const apiKey = getKey(ctl);
const perChat = ctl.getPluginConfig(config_1.configSchematics);
const obsParams = (0, fredClient_1.clean)({
series_id: p.series_id,
observation_start: p.observation_start,
observation_end: p.observation_end,
limit: p.limit ?? perChat.get("defaultLimit"),
offset: p.offset,
sort_order: p.sort_order ?? "asc",
units: p.units,
frequency: p.frequency,
aggregation_method: p.aggregation_method,
});
// fetch metadata + observations in parallel
const [meta, obs] = await Promise.all([
(0, fredClient_1.fredFetch)("series", { series_id: p.series_id }, apiKey).catch(() => ({})),
(0, fredClient_1.fredFetch)("series/observations", obsParams, apiKey),
]);
const observations = obs.observations ?? [];
const result = {
series: meta.seriess?.[0] ?? meta.series?.[0] ?? { id: p.series_id },
observations_count: observations.length,
observations: observations.slice(-200), // keep last 200 to avoid token blowup
full_range: {
start: observations[0]?.date,
end: observations[observations.length - 1]?.date,
},
};
return JSON.stringify(result, null, 2).slice(0, 20000);
}
catch (e) {
return `Error in fred_get_series: ${e.message}`;
}
},
}));
return tools;
}