import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
const API_KEY = process.env.BEA_API_KEY || "";
const BASE_URL = "https://apps.bea.gov/api/data";
async function beaRequest(params: Record<string, string>): Promise<any> {
const searchParams = new URLSearchParams({
UserID: API_KEY,
ResultFormat: "JSON",
...params,
});
const url = `${BASE_URL}?${searchParams.toString()}`;
const response = await fetch(url);
if (!response.ok) {
throw new Error(`BEA API error: ${response.status}`);
}
const data = await response.json();
if (data.BEAAPI?.Error) {
throw new Error(`BEA API error: ${data.BEAAPI.Error.ErrorDetail?.Description || 'Unknown error'}`);
}
return data.BEAAPI?.Results;
}
const server = new Server(
{ name: "bea", version: "1.0.0" },
{ capabilities: { tools: {} } }
);
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: "list_datasets",
description: "List all available BEA datasets (NIPA, Regional, GDPbyIndustry, etc.)",
inputSchema: {
type: "object",
properties: {},
required: [],
},
},
{
name: "get_parameters",
description: "Get required parameters for a dataset",
inputSchema: {
type: "object",
properties: {
dataset: {
type: "string",
description: "Dataset name (e.g., 'Regional', 'NIPA', 'GDPbyIndustry')"
},
},
required: ["dataset"],
},
},
{
name: "get_parameter_values",
description: "Get valid values for a parameter (e.g., available tables, line codes, geo areas)",
inputSchema: {
type: "object",
properties: {
dataset: { type: "string", description: "Dataset name" },
parameter: { type: "string", description: "Parameter name (e.g., 'TableName', 'LineCode', 'GeoFips')" },
},
required: ["dataset", "parameter"],
},
},
{
name: "query",
description: "Fetch BEA data. Parameters vary by dataset. Common: Regional (GeoFips, LineCode, TableName, Year), NIPA (TableName, Frequency, Year)",
inputSchema: {
type: "object",
properties: {
dataset: { type: "string", description: "Dataset name (e.g., 'Regional', 'NIPA')" },
tableName: { type: "string", description: "Table name (use get_parameter_values to find valid tables)" },
frequency: { type: "string", description: "A (annual), Q (quarterly), M (monthly)" },
year: { type: "string", description: "Year(s) - single year, comma-separated, or 'ALL', 'LAST5', etc." },
geoFips: { type: "string", description: "Geographic FIPS code(s) - 'STATE', 'COUNTY', 'MSA', or specific codes" },
lineCode: { type: "string", description: "Line code for specific data series" },
industry: { type: "string", description: "Industry code (for GDP by Industry)" },
},
required: ["dataset"],
},
},
{
name: "get_regional_income",
description: "Shortcut: Get personal income data by state/county. Returns per capita income, total income, etc.",
inputSchema: {
type: "object",
properties: {
geoFips: {
type: "string",
description: "Geographic area: 'STATE' for all states, 'COUNTY' for all counties, or specific FIPS codes"
},
year: { type: "string", description: "Year(s) - e.g., '2022', '2020,2021,2022', 'LAST5'" },
lineCode: {
type: "string",
description: "1=Total personal income, 2=Per capita personal income, 3=Population"
},
},
required: ["geoFips", "year"],
},
},
{
name: "get_gdp_by_state",
description: "Shortcut: Get GDP data by state",
inputSchema: {
type: "object",
properties: {
geoFips: { type: "string", description: "'STATE' for all states or specific FIPS codes" },
year: { type: "string", description: "Year(s)" },
industry: { type: "string", description: "Industry code: 'ALL' for total, or specific NAICS" },
},
required: ["geoFips", "year"],
},
},
],
}));
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case "list_datasets": {
const result = await beaRequest({ method: "GETDATASETLIST" });
return {
content: [{ type: "text", text: JSON.stringify(result.Dataset, null, 2) }],
};
}
case "get_parameters": {
const { dataset } = args as any;
const result = await beaRequest({
method: "GETPARAMETERLIST",
DatasetName: dataset,
});
return {
content: [{ type: "text", text: JSON.stringify(result.Parameter, null, 2) }],
};
}
case "get_parameter_values": {
const { dataset, parameter } = args as any;
const result = await beaRequest({
method: "GETPARAMETERVALUES",
DatasetName: dataset,
ParameterName: parameter,
});
const values = result.ParamValue || [];
// Limit output for large result sets
const limitedValues = values.slice(0, 100);
const output = {
totalCount: values.length,
showing: limitedValues.length,
values: limitedValues,
};
return {
content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
};
}
case "query": {
const { dataset, tableName, frequency, year, geoFips, lineCode, industry } = args as any;
const params: Record<string, string> = {
method: "GETDATA",
DatasetName: dataset,
};
if (tableName) params.TableName = tableName;
if (frequency) params.Frequency = frequency;
if (year) params.Year = year;
if (geoFips) params.GeoFips = geoFips;
if (lineCode) params.LineCode = lineCode;
if (industry) params.Industry = industry;
const result = await beaRequest(params);
return {
content: [{ type: "text", text: JSON.stringify(result.Data || result, null, 2) }],
};
}
case "get_regional_income": {
const { geoFips, year, lineCode = "1" } = args as any;
const result = await beaRequest({
method: "GETDATA",
DatasetName: "Regional",
TableName: "SAINC1",
GeoFips: geoFips,
Year: year,
LineCode: lineCode,
});
return {
content: [{ type: "text", text: JSON.stringify(result.Data, null, 2) }],
};
}
case "get_gdp_by_state": {
const { geoFips, year, industry = "ALL" } = args as any;
const result = await beaRequest({
method: "GETDATA",
DatasetName: "Regional",
TableName: "SAGDP1",
GeoFips: geoFips,
Year: year,
LineCode: "1",
});
return {
content: [{ type: "text", text: JSON.stringify(result.Data, null, 2) }],
};
}
default:
throw new Error(`Unknown tool: ${name}`);
}
} catch (error: any) {
return {
content: [{ type: "text", text: `Error: ${error.message}` }],
isError: true,
};
}
});
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("BEA MCP server running");
}
main().catch(console.error);