#
tokens: 4619/50000 8/8 files
lines: on (toggle) GitHub
raw markdown copy reset
# 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 | ];
```