#
tokens: 3389/50000 8/8 files
lines: off (toggle) GitHub
raw markdown copy
# Directory Structure

```
├── .env.example
├── .gitignore
├── package-lock.json
├── package.json
├── readme.md
├── src
│   ├── index.ts
│   ├── tools.ts
│   └── types.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------

```
FRED_API_KEY=YOUR_API_KEY
```

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
node_modules/
dist/
.env
*.log
```

--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------

```markdown
Here's the README formatted in proper markdown:

# FRED MCP Server

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.

## Prerequisites

- Node.js (v16 or higher)
- FRED API Key (obtain from [FRED API](https://fred.stlouisfed.org/docs/api/api_key.html))

## Installation

1. Clone the repository:
   ```bash
   git clone https://github.com/kablewy/fred-mcp-server
   cd fred-mcp-server
   ```

2. Install dependencies:
   ```bash
   npm install
   ```

3. Copy the `.env.example` file to `.env` and add your FRED API key:
   ```
   FRED_API_KEY=your_api_key_here
   ```

## Usage

### Development

Run the server in development mode:
```bash
npm run dev
```

### Production

1. Build the project:
   ```bash
   npm run build
   ```

2. Start the server:
   ```bash
   npm start
   ```

## Available Tools

The server provides the following FRED API tools:

### Series Search
Search for economic data series using various parameters.

### Series Observations
Retrieve observations for a specific economic data series with options for:
- Date range filtering
- Frequency adjustment
- Aggregation methods
- Sorting and pagination

## Development

### Project Structure
```
fred-mcp-server/
├── src/
│   ├── index.ts      # Server entry point
│   ├── tools.ts      # Tool implementations
│   └── types.ts      # TypeScript interfaces
├── package.json
├── tsconfig.json
└── .env
```

### Testing

Run the test suite:
```bash
npm test
```

## License

[Your chosen license]

## Contributing

[Your contribution guidelines]

## Acknowledgments

- Built with [Model Context Protocol SDK](https://github.com/modelcontextprotocol/sdk)
- Data provided by [Federal Reserve Economic Data (FRED)](https://fred.stlouisfed.org/)

```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "lib": ["ES2022", "DOM"],
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "**/*.test.ts"]
}
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "fred-mcp-server",
  "version": "0.1.0",
  "description": "MCP server for accessing FRED (Federal Reserve Economic Data) API",
  "main": "dist/index.js",
  "type": "module",
  "scripts": {
    "build": "tsc",
    "start": "node dist/index.js",
    "dev": "ts-node-esm src/index.ts",
    "test": "jest"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.0.3",
    "@types/node": "^20.10.5",
    "dotenv": "^16.4.7",
    "typescript": "^5.3.3"
  },
  "devDependencies": {
    "@types/jest": "^29.5.11",
    "jest": "^29.7.0",
    "ts-jest": "^29.1.1",
    "ts-node": "^10.9.2"
  }
}

```

--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------

```typescript
export interface FredParams {
  tool: string;
  arguments: any;
}

export interface FredSeriesSearchResponse {
  realtime_start: string;
  realtime_end: string;
  order_by: string;
  sort_order: string;
  count: number;
  offset: number;
  limit: number;
  seriess: Array<{
    id: string;
    realtime_start: string;
    realtime_end: string;
    title: string;
    observation_start: string;
    observation_end: string;
    frequency: string;
    frequency_short: string;
    units: string;
    units_short: string;
    seasonal_adjustment: string;
    seasonal_adjustment_short: string;
    last_updated: string;
    popularity: number;
    notes: string;
  }>;
}

export interface FredSeriesObservation {
  realtime_start: string;
  realtime_end: string;
  date: string;
  value: string;
}

export interface FredSeriesResponse {
  realtime_start: string;
  realtime_end: string;
  observation_start: string;
  observation_end: string;
  units: string;
  output_type: number;
  file_type: string;
  order_by: string;
  sort_order: string;
  count: number;
  offset: number;
  limit: number;
  observations: FredSeriesObservation[];
}
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  ListToolsRequestSchema,
  CallToolRequestSchema
} from "@modelcontextprotocol/sdk/types.js";
import dotenv from "dotenv";
import { tools, Tool } from "./tools.js";

dotenv.config();

if (!process.env.FRED_API_KEY) {
  throw new Error("FRED_API_KEY environment variable is required");
}

class FredServer {
  private server: Server;

  constructor() {
    this.server = new Server({
      name: "fred-mcp-server",
      version: "0.1.0"
    }, {
      capabilities: {
        tools: {}
      }
    });

    this.setupHandlers();
    this.setupErrorHandling();
  }

  private setupErrorHandling(): void {
    this.server.onerror = (error) => {
      console.error("[MCP Error]", error);
    };

    process.on('SIGINT', async () => {
      await this.server.close();
      process.exit(0);
    });
  }

  private setupHandlers(): void {
    this.server.setRequestHandler(
      ListToolsRequestSchema,
      async () => ({
        tools: tools.map(({ name, description, inputSchema }) => ({
          name,
          description,
          inputSchema
        }))
      })
    );

    this.server.setRequestHandler(
      CallToolRequestSchema,
      async (request) => {
        const tool = tools.find(t => t.name === request.params.name) as Tool;
        
        if (!tool) {
          return {
            content: [{
              type: "text",
              text: `Unknown tool: ${request.params.name}`
            }],
            isError: true
          };
        }

        try {
          const result = await tool.handler(request.params.arguments || {});
          
          return {
            content: [{
              type: "text",
              text: JSON.stringify(result, null, 2)
            }]
          };
        } catch (error) {
          return {
            content: [{
              type: "text",
              text: `FRED API error: ${error instanceof Error ? error.message : String(error)}`
            }],
            isError: true
          };
        }
      }
    );
  }

  async run(): Promise<void> {
    const transport = new StdioServerTransport();
    await this.server.connect(transport);
    console.error("FRED MCP server running on stdio");
  }
}

const server = new FredServer();
server.run().catch(console.error);
```

--------------------------------------------------------------------------------
/src/tools.ts:
--------------------------------------------------------------------------------

```typescript
type ToolHandler<T> = (args: T) => Promise<unknown>;

export interface Tool<T = any> {
  name: string;
  description: string;
  inputSchema: {
    type: string;
    properties: Record<string, unknown>;
    required?: string[];
  };
  handler: ToolHandler<T>;
}

interface SearchSeriesArgs {
  searchText: string;
  limit?: number;
  orderBy?: 'searchrank' | 'series_id' | 'title' | 'units' | 'frequency' | 'seasonal_adjustment' | 'realtime_start' | 'realtime_end' | 'last_updated' | 'observation_start' | 'observation_end' | 'popularity';
  sortOrder?: 'asc' | 'desc';
  filterVariable?: string;
  filterValue?: string;
  tagNames?: string[];
  excludeTagNames?: string[];
}

interface GetSeriesArgs {
  seriesId: string;
  startDate?: string;
  endDate?: string;
  sortOrder?: 'asc' | 'desc';
  limit?: number;
  offset?: number;
  frequency?: 'd' | 'w' | 'bw' | 'm' | 'q' | 'sa' | 'a';
  aggregationMethod?: 'avg' | 'sum' | 'eop';
  outputType?: 1 | 2 | 3 | 4;
  vintage_dates?: string[];
}

const BASE_URL = 'https://api.stlouisfed.org/fred';

export const tools: Tool[] = [
  {
    name: 'search',
    description: 'Search for FRED data series with advanced filtering options',
    inputSchema: {
      type: 'object',
      properties: {
        searchText: { type: 'string', description: 'Search text for FRED series' },
        limit: { type: 'number', description: 'Maximum number of results to return (default: 1000)' },
        orderBy: { 
          type: 'string', 
          enum: ['searchrank', 'series_id', 'title', 'units', 'frequency', 'seasonal_adjustment', 
                'realtime_start', 'realtime_end', 'last_updated', 'observation_start', 
                'observation_end', 'popularity'],
          description: 'Order results by this property'
        },
        sortOrder: { 
          type: 'string', 
          enum: ['asc', 'desc'], 
          description: 'Sort order (default: asc)'
        },
        filterVariable: { type: 'string', description: 'Variable to filter results by' },
        filterValue: { type: 'string', description: 'Value of filter variable' },
        tagNames: { 
          type: 'array', 
          items: { type: 'string' }, 
          description: 'Series tags to include'
        },
        excludeTagNames: { 
          type: 'array', 
          items: { type: 'string' }, 
          description: 'Series tags to exclude'
        }
      },
      required: ['searchText']
    },
    handler: async (args: SearchSeriesArgs) => {
      const url = new URL(`${BASE_URL}/series/search`);
      url.searchParams.append('api_key', process.env.FRED_API_KEY!);
      url.searchParams.append('search_text', args.searchText);
      url.searchParams.append('file_type', 'json');

      if (args.limit) url.searchParams.append('limit', args.limit.toString());
      if (args.orderBy) url.searchParams.append('order_by', args.orderBy);
      if (args.sortOrder) url.searchParams.append('sort_order', args.sortOrder);
      if (args.filterVariable) url.searchParams.append('filter_variable', args.filterVariable);
      if (args.filterValue) url.searchParams.append('filter_value', args.filterValue);
      if (args.tagNames) url.searchParams.append('tag_names', args.tagNames.join(';'));
      if (args.excludeTagNames) url.searchParams.append('exclude_tag_names', args.excludeTagNames.join(';'));

      const response = await fetch(url.toString());
      
      if (!response.ok) {
        throw new Error(`FRED API error: ${response.statusText}`);
      }

      const data = await response.json();
      return data.seriess;
    }
  },
  {
    name: 'series',
    description: 'Get observations for a specific FRED data series with advanced options',
    inputSchema: {
      type: 'object',
      properties: {
        seriesId: { type: 'string', description: 'FRED series ID' },
        startDate: { type: 'string', description: 'Start date in YYYY-MM-DD format' },
        endDate: { type: 'string', description: 'End date in YYYY-MM-DD format' },
        sortOrder: { 
          type: 'string', 
          enum: ['asc', 'desc'], 
          description: 'Sort order (default: asc)'
        },
        limit: { type: 'number', description: 'Maximum number of results to return' },
        offset: { type: 'number', description: 'Number of results to skip' },
        frequency: { 
          type: 'string', 
          enum: ['d', 'w', 'bw', 'm', 'q', 'sa', 'a'],
          description: 'Frequency of observations (d=daily, w=weekly, bw=biweekly, m=monthly, q=quarterly, sa=semiannual, a=annual)'
        },
        aggregationMethod: { 
          type: 'string', 
          enum: ['avg', 'sum', 'eop'],
          description: 'Aggregation method for frequency conversion (avg=average, sum=sum, eop=end of period)'
        },
        outputType: {
          type: 'number',
          enum: [1, 2, 3, 4],
          description: '1=observations by real-time period, 2=observations by vintage date, 3=vintage dates, 4=initial release plus current value'
        },
        vintageDates: {
          type: 'array',
          items: { type: 'string' },
          description: 'Vintage dates in YYYY-MM-DD format'
        }
      },
      required: ['seriesId']
    },
    handler: async (args: GetSeriesArgs) => {
      const url = new URL(`${BASE_URL}/series/observations`);
      url.searchParams.append('api_key', process.env.FRED_API_KEY!);
      url.searchParams.append('series_id', args.seriesId);
      url.searchParams.append('file_type', 'json');
      
      if (args.startDate) url.searchParams.append('observation_start', args.startDate);
      if (args.endDate) url.searchParams.append('observation_end', args.endDate);
      if (args.sortOrder) url.searchParams.append('sort_order', args.sortOrder);
      if (args.limit) url.searchParams.append('limit', args.limit.toString());
      if (args.offset) url.searchParams.append('offset', args.offset.toString());
      if (args.frequency) url.searchParams.append('frequency', args.frequency);
      if (args.aggregationMethod) url.searchParams.append('aggregation_method', args.aggregationMethod);
      if (args.outputType) url.searchParams.append('output_type', args.outputType.toString());
      if (args.vintage_dates) url.searchParams.append('vintage_dates', args.vintage_dates.join(','));

      const response = await fetch(url.toString());
      
      if (!response.ok) {
        throw new Error(`FRED API error: ${response.statusText}`);
      }

      const data = await response.json();
      return data.observations;
    }
  }
];
```