# Directory Structure
```
├── .env.example
├── .gitignore
├── package-lock.json
├── package.json
├── readme.md
├── src
│ ├── index.ts
│ ├── tools.ts
│ └── types.ts
└── tsconfig.json
```
# Files
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
```
1 | FRED_API_KEY=YOUR_API_KEY
```
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
1 | node_modules/
2 | dist/
3 | .env
4 | *.log
```
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
```markdown
1 | Here's the README formatted in proper markdown:
2 |
3 | # FRED MCP Server
4 |
5 | A Model Context Protocol (MCP) server implementation for accessing the Federal Reserve Economic Data (FRED) API. This server provides tools to search and retrieve economic data series from FRED.
6 |
7 | ## Prerequisites
8 |
9 | - Node.js (v16 or higher)
10 | - FRED API Key (obtain from [FRED API](https://fred.stlouisfed.org/docs/api/api_key.html))
11 |
12 | ## Installation
13 |
14 | 1. Clone the repository:
15 | ```bash
16 | git clone https://github.com/kablewy/fred-mcp-server
17 | cd fred-mcp-server
18 | ```
19 |
20 | 2. Install dependencies:
21 | ```bash
22 | npm install
23 | ```
24 |
25 | 3. Copy the `.env.example` file to `.env` and add your FRED API key:
26 | ```
27 | FRED_API_KEY=your_api_key_here
28 | ```
29 |
30 | ## Usage
31 |
32 | ### Development
33 |
34 | Run the server in development mode:
35 | ```bash
36 | npm run dev
37 | ```
38 |
39 | ### Production
40 |
41 | 1. Build the project:
42 | ```bash
43 | npm run build
44 | ```
45 |
46 | 2. Start the server:
47 | ```bash
48 | npm start
49 | ```
50 |
51 | ## Available Tools
52 |
53 | The server provides the following FRED API tools:
54 |
55 | ### Series Search
56 | Search for economic data series using various parameters.
57 |
58 | ### Series Observations
59 | Retrieve observations for a specific economic data series with options for:
60 | - Date range filtering
61 | - Frequency adjustment
62 | - Aggregation methods
63 | - Sorting and pagination
64 |
65 | ## Development
66 |
67 | ### Project Structure
68 | ```
69 | fred-mcp-server/
70 | ├── src/
71 | │ ├── index.ts # Server entry point
72 | │ ├── tools.ts # Tool implementations
73 | │ └── types.ts # TypeScript interfaces
74 | ├── package.json
75 | ├── tsconfig.json
76 | └── .env
77 | ```
78 |
79 | ### Testing
80 |
81 | Run the test suite:
82 | ```bash
83 | npm test
84 | ```
85 |
86 | ## License
87 |
88 | [Your chosen license]
89 |
90 | ## Contributing
91 |
92 | [Your contribution guidelines]
93 |
94 | ## Acknowledgments
95 |
96 | - Built with [Model Context Protocol SDK](https://github.com/modelcontextprotocol/sdk)
97 | - Data provided by [Federal Reserve Economic Data (FRED)](https://fred.stlouisfed.org/)
98 |
```
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "compilerOptions": {
3 | "target": "ES2022",
4 | "module": "NodeNext",
5 | "moduleResolution": "NodeNext",
6 | "lib": ["ES2022", "DOM"],
7 | "outDir": "./dist",
8 | "rootDir": "./src",
9 | "strict": true,
10 | "esModuleInterop": true,
11 | "skipLibCheck": true,
12 | "forceConsistentCasingInFileNames": true
13 | },
14 | "include": ["src/**/*"],
15 | "exclude": ["node_modules", "**/*.test.ts"]
16 | }
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "name": "fred-mcp-server",
3 | "version": "0.1.0",
4 | "description": "MCP server for accessing FRED (Federal Reserve Economic Data) API",
5 | "main": "dist/index.js",
6 | "type": "module",
7 | "scripts": {
8 | "build": "tsc",
9 | "start": "node dist/index.js",
10 | "dev": "ts-node-esm src/index.ts",
11 | "test": "jest"
12 | },
13 | "dependencies": {
14 | "@modelcontextprotocol/sdk": "^1.0.3",
15 | "@types/node": "^20.10.5",
16 | "dotenv": "^16.4.7",
17 | "typescript": "^5.3.3"
18 | },
19 | "devDependencies": {
20 | "@types/jest": "^29.5.11",
21 | "jest": "^29.7.0",
22 | "ts-jest": "^29.1.1",
23 | "ts-node": "^10.9.2"
24 | }
25 | }
26 |
```
--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------
```typescript
1 | export interface FredParams {
2 | tool: string;
3 | arguments: any;
4 | }
5 |
6 | export interface FredSeriesSearchResponse {
7 | realtime_start: string;
8 | realtime_end: string;
9 | order_by: string;
10 | sort_order: string;
11 | count: number;
12 | offset: number;
13 | limit: number;
14 | seriess: Array<{
15 | id: string;
16 | realtime_start: string;
17 | realtime_end: string;
18 | title: string;
19 | observation_start: string;
20 | observation_end: string;
21 | frequency: string;
22 | frequency_short: string;
23 | units: string;
24 | units_short: string;
25 | seasonal_adjustment: string;
26 | seasonal_adjustment_short: string;
27 | last_updated: string;
28 | popularity: number;
29 | notes: string;
30 | }>;
31 | }
32 |
33 | export interface FredSeriesObservation {
34 | realtime_start: string;
35 | realtime_end: string;
36 | date: string;
37 | value: string;
38 | }
39 |
40 | export interface FredSeriesResponse {
41 | realtime_start: string;
42 | realtime_end: string;
43 | observation_start: string;
44 | observation_end: string;
45 | units: string;
46 | output_type: number;
47 | file_type: string;
48 | order_by: string;
49 | sort_order: string;
50 | count: number;
51 | offset: number;
52 | limit: number;
53 | observations: FredSeriesObservation[];
54 | }
```
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
```typescript
1 | #!/usr/bin/env node
2 | import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4 | import {
5 | ListToolsRequestSchema,
6 | CallToolRequestSchema
7 | } from "@modelcontextprotocol/sdk/types.js";
8 | import dotenv from "dotenv";
9 | import { tools, Tool } from "./tools.js";
10 |
11 | dotenv.config();
12 |
13 | if (!process.env.FRED_API_KEY) {
14 | throw new Error("FRED_API_KEY environment variable is required");
15 | }
16 |
17 | class FredServer {
18 | private server: Server;
19 |
20 | constructor() {
21 | this.server = new Server({
22 | name: "fred-mcp-server",
23 | version: "0.1.0"
24 | }, {
25 | capabilities: {
26 | tools: {}
27 | }
28 | });
29 |
30 | this.setupHandlers();
31 | this.setupErrorHandling();
32 | }
33 |
34 | private setupErrorHandling(): void {
35 | this.server.onerror = (error) => {
36 | console.error("[MCP Error]", error);
37 | };
38 |
39 | process.on('SIGINT', async () => {
40 | await this.server.close();
41 | process.exit(0);
42 | });
43 | }
44 |
45 | private setupHandlers(): void {
46 | this.server.setRequestHandler(
47 | ListToolsRequestSchema,
48 | async () => ({
49 | tools: tools.map(({ name, description, inputSchema }) => ({
50 | name,
51 | description,
52 | inputSchema
53 | }))
54 | })
55 | );
56 |
57 | this.server.setRequestHandler(
58 | CallToolRequestSchema,
59 | async (request) => {
60 | const tool = tools.find(t => t.name === request.params.name) as Tool;
61 |
62 | if (!tool) {
63 | return {
64 | content: [{
65 | type: "text",
66 | text: `Unknown tool: ${request.params.name}`
67 | }],
68 | isError: true
69 | };
70 | }
71 |
72 | try {
73 | const result = await tool.handler(request.params.arguments || {});
74 |
75 | return {
76 | content: [{
77 | type: "text",
78 | text: JSON.stringify(result, null, 2)
79 | }]
80 | };
81 | } catch (error) {
82 | return {
83 | content: [{
84 | type: "text",
85 | text: `FRED API error: ${error instanceof Error ? error.message : String(error)}`
86 | }],
87 | isError: true
88 | };
89 | }
90 | }
91 | );
92 | }
93 |
94 | async run(): Promise<void> {
95 | const transport = new StdioServerTransport();
96 | await this.server.connect(transport);
97 | console.error("FRED MCP server running on stdio");
98 | }
99 | }
100 |
101 | const server = new FredServer();
102 | server.run().catch(console.error);
```
--------------------------------------------------------------------------------
/src/tools.ts:
--------------------------------------------------------------------------------
```typescript
1 | type ToolHandler<T> = (args: T) => Promise<unknown>;
2 |
3 | export interface Tool<T = any> {
4 | name: string;
5 | description: string;
6 | inputSchema: {
7 | type: string;
8 | properties: Record<string, unknown>;
9 | required?: string[];
10 | };
11 | handler: ToolHandler<T>;
12 | }
13 |
14 | interface SearchSeriesArgs {
15 | searchText: string;
16 | limit?: number;
17 | orderBy?: 'searchrank' | 'series_id' | 'title' | 'units' | 'frequency' | 'seasonal_adjustment' | 'realtime_start' | 'realtime_end' | 'last_updated' | 'observation_start' | 'observation_end' | 'popularity';
18 | sortOrder?: 'asc' | 'desc';
19 | filterVariable?: string;
20 | filterValue?: string;
21 | tagNames?: string[];
22 | excludeTagNames?: string[];
23 | }
24 |
25 | interface GetSeriesArgs {
26 | seriesId: string;
27 | startDate?: string;
28 | endDate?: string;
29 | sortOrder?: 'asc' | 'desc';
30 | limit?: number;
31 | offset?: number;
32 | frequency?: 'd' | 'w' | 'bw' | 'm' | 'q' | 'sa' | 'a';
33 | aggregationMethod?: 'avg' | 'sum' | 'eop';
34 | outputType?: 1 | 2 | 3 | 4;
35 | vintage_dates?: string[];
36 | }
37 |
38 | const BASE_URL = 'https://api.stlouisfed.org/fred';
39 |
40 | export const tools: Tool[] = [
41 | {
42 | name: 'search',
43 | description: 'Search for FRED data series with advanced filtering options',
44 | inputSchema: {
45 | type: 'object',
46 | properties: {
47 | searchText: { type: 'string', description: 'Search text for FRED series' },
48 | limit: { type: 'number', description: 'Maximum number of results to return (default: 1000)' },
49 | orderBy: {
50 | type: 'string',
51 | enum: ['searchrank', 'series_id', 'title', 'units', 'frequency', 'seasonal_adjustment',
52 | 'realtime_start', 'realtime_end', 'last_updated', 'observation_start',
53 | 'observation_end', 'popularity'],
54 | description: 'Order results by this property'
55 | },
56 | sortOrder: {
57 | type: 'string',
58 | enum: ['asc', 'desc'],
59 | description: 'Sort order (default: asc)'
60 | },
61 | filterVariable: { type: 'string', description: 'Variable to filter results by' },
62 | filterValue: { type: 'string', description: 'Value of filter variable' },
63 | tagNames: {
64 | type: 'array',
65 | items: { type: 'string' },
66 | description: 'Series tags to include'
67 | },
68 | excludeTagNames: {
69 | type: 'array',
70 | items: { type: 'string' },
71 | description: 'Series tags to exclude'
72 | }
73 | },
74 | required: ['searchText']
75 | },
76 | handler: async (args: SearchSeriesArgs) => {
77 | const url = new URL(`${BASE_URL}/series/search`);
78 | url.searchParams.append('api_key', process.env.FRED_API_KEY!);
79 | url.searchParams.append('search_text', args.searchText);
80 | url.searchParams.append('file_type', 'json');
81 |
82 | if (args.limit) url.searchParams.append('limit', args.limit.toString());
83 | if (args.orderBy) url.searchParams.append('order_by', args.orderBy);
84 | if (args.sortOrder) url.searchParams.append('sort_order', args.sortOrder);
85 | if (args.filterVariable) url.searchParams.append('filter_variable', args.filterVariable);
86 | if (args.filterValue) url.searchParams.append('filter_value', args.filterValue);
87 | if (args.tagNames) url.searchParams.append('tag_names', args.tagNames.join(';'));
88 | if (args.excludeTagNames) url.searchParams.append('exclude_tag_names', args.excludeTagNames.join(';'));
89 |
90 | const response = await fetch(url.toString());
91 |
92 | if (!response.ok) {
93 | throw new Error(`FRED API error: ${response.statusText}`);
94 | }
95 |
96 | const data = await response.json();
97 | return data.seriess;
98 | }
99 | },
100 | {
101 | name: 'series',
102 | description: 'Get observations for a specific FRED data series with advanced options',
103 | inputSchema: {
104 | type: 'object',
105 | properties: {
106 | seriesId: { type: 'string', description: 'FRED series ID' },
107 | startDate: { type: 'string', description: 'Start date in YYYY-MM-DD format' },
108 | endDate: { type: 'string', description: 'End date in YYYY-MM-DD format' },
109 | sortOrder: {
110 | type: 'string',
111 | enum: ['asc', 'desc'],
112 | description: 'Sort order (default: asc)'
113 | },
114 | limit: { type: 'number', description: 'Maximum number of results to return' },
115 | offset: { type: 'number', description: 'Number of results to skip' },
116 | frequency: {
117 | type: 'string',
118 | enum: ['d', 'w', 'bw', 'm', 'q', 'sa', 'a'],
119 | description: 'Frequency of observations (d=daily, w=weekly, bw=biweekly, m=monthly, q=quarterly, sa=semiannual, a=annual)'
120 | },
121 | aggregationMethod: {
122 | type: 'string',
123 | enum: ['avg', 'sum', 'eop'],
124 | description: 'Aggregation method for frequency conversion (avg=average, sum=sum, eop=end of period)'
125 | },
126 | outputType: {
127 | type: 'number',
128 | enum: [1, 2, 3, 4],
129 | description: '1=observations by real-time period, 2=observations by vintage date, 3=vintage dates, 4=initial release plus current value'
130 | },
131 | vintageDates: {
132 | type: 'array',
133 | items: { type: 'string' },
134 | description: 'Vintage dates in YYYY-MM-DD format'
135 | }
136 | },
137 | required: ['seriesId']
138 | },
139 | handler: async (args: GetSeriesArgs) => {
140 | const url = new URL(`${BASE_URL}/series/observations`);
141 | url.searchParams.append('api_key', process.env.FRED_API_KEY!);
142 | url.searchParams.append('series_id', args.seriesId);
143 | url.searchParams.append('file_type', 'json');
144 |
145 | if (args.startDate) url.searchParams.append('observation_start', args.startDate);
146 | if (args.endDate) url.searchParams.append('observation_end', args.endDate);
147 | if (args.sortOrder) url.searchParams.append('sort_order', args.sortOrder);
148 | if (args.limit) url.searchParams.append('limit', args.limit.toString());
149 | if (args.offset) url.searchParams.append('offset', args.offset.toString());
150 | if (args.frequency) url.searchParams.append('frequency', args.frequency);
151 | if (args.aggregationMethod) url.searchParams.append('aggregation_method', args.aggregationMethod);
152 | if (args.outputType) url.searchParams.append('output_type', args.outputType.toString());
153 | if (args.vintage_dates) url.searchParams.append('vintage_dates', args.vintage_dates.join(','));
154 |
155 | const response = await fetch(url.toString());
156 |
157 | if (!response.ok) {
158 | throw new Error(`FRED API error: ${response.statusText}`);
159 | }
160 |
161 | const data = await response.json();
162 | return data.observations;
163 | }
164 | }
165 | ];
```