Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "fred-mcp-server",
"version": "1.0.2",
"version": "1.1.0",
"description": "Federal Reserve Economic Data (FRED) MCP Server - Access all 800,000+ economic time series with search, browse, and data retrieval capabilities",
"main": "build/index.js",
"keywords": [
Expand Down Expand Up @@ -49,10 +49,12 @@
"license": "AGPL-3.0",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.11.2",
"express": "^4.21.2",
"zod": "^3.24.4"
},
"devDependencies": {
"@jest/globals": "^29.7.0",
"@types/express": "^4.17.25",
"@types/jest": "^29.5.14",
"@types/node": "^22.15.17",
"jest": "^29.7.0",
Expand Down
85 changes: 85 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

64 changes: 64 additions & 0 deletions src/http-server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import express from 'express';
import { randomUUID } from 'node:crypto';
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import { createServer } from './index.js';

const DEFAULT_PORT = 3000;
const DEFAULT_HOST = '0.0.0.0';
const ENV_PORT = 'PORT';
const ENV_HOST = 'HOST';

export interface HttpServerOptions {
port?: number;
host?: string;
}

export async function startHttpServer(options: HttpServerOptions = {}) {
const port = options.port ?? parseInt(process.env[ENV_PORT] ?? String(DEFAULT_PORT), 10);
const host = options.host ?? process.env[ENV_HOST] ?? DEFAULT_HOST;

const app = express();
app.use(express.json());

const transports = new Map<string, StreamableHTTPServerTransport>();

app.all('/mcp', async (req, res) => {
try {
const sessionId = req.headers['mcp-session-id'] as string | undefined;

if (sessionId && transports.has(sessionId)) {
await transports.get(sessionId)!.handleRequest(req, res, req.body);
} else if (!sessionId) {
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(),
onsessioninitialized: (id) => transports.set(id, transport)
});

transport.onclose = () => {
if (transport.sessionId) transports.delete(transport.sessionId);
};

const server = createServer();
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
} else {
res.status(404).json({ error: 'Session not found' });
}
} catch (error) {
console.error('Error in /mcp handler:', error);
if (!res.headersSent) {
res.status(500).json({ error: 'Internal server error' });
}
}
});

app.get('/health', (_req, res) => {
res.json({ status: 'ok', sessions: transports.size });
});

app.listen(port, host, () => {
console.error(`FRED MCP Server: http://${host}:${port}/mcp`);
});

return new Promise<void>(() => {});
}
64 changes: 52 additions & 12 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { registerFREDTools } from "./fred/tools.js";
import { readFileSync } from "fs";
import { fileURLToPath } from "url";
import { dirname, join } from "path";
import { startHttpServer } from "./http-server.js";

/**
* Create and configure a new FRED MCP server
Expand All @@ -18,7 +19,7 @@ export function createServer() {

/**
* Main FRED MCP Server
*
*
* Provides access to Federal Reserve Economic Data through the
* Model Context Protocol
*/
Expand All @@ -35,7 +36,7 @@ export function createServer() {
}

/**
* Connect and start the MCP server
* Connect and start the MCP server with stdio transport
*/
export async function startServer(server: McpServer, transport: StdioServerTransport) {
console.error("FRED MCP Server starting...");
Expand All @@ -49,24 +50,63 @@ export async function startServer(server: McpServer, transport: StdioServerTrans
console.error("Server shutting down...");
process.exit(0);
});

return true;
} catch (error) {
console.error("Failed to start server:", error);
return false;
}
}

/**
* Main entry point
*/
const TRANSPORT_STDIO = 'stdio' as const;
const TRANSPORT_STREAMABLE_HTTP = 'streamable-http' as const;

const CLI_FLAG_STREAMABLE_HTTP = '--streamable-http';
const CLI_FLAG_PORT = '--port=';
const CLI_FLAG_HOST = '--host=';

const ENV_TRANSPORT = 'FRED_MCP_TRANSPORT';
const ENV_PORT = 'PORT';
const ENV_HOST = 'HOST';

const DEFAULT_PORT = 3000;
const DEFAULT_HOST = '0.0.0.0';

type TransportType = typeof TRANSPORT_STDIO | typeof TRANSPORT_STREAMABLE_HTTP;

interface ServerConfig {
transport: TransportType;
port?: number;
host?: string;
}

function parseConfig(): ServerConfig {
const args = process.argv.slice(2);

const getArgValue = (prefix: string) =>
args.find(arg => arg.startsWith(prefix))?.split('=')[1];

const useStreamableHttp =
args.includes(CLI_FLAG_STREAMABLE_HTTP) ||
process.env[ENV_TRANSPORT] === TRANSPORT_STREAMABLE_HTTP;

return {
transport: useStreamableHttp ? TRANSPORT_STREAMABLE_HTTP : TRANSPORT_STDIO,
port: getArgValue(CLI_FLAG_PORT) ? parseInt(getArgValue(CLI_FLAG_PORT)!, 10) : undefined,
host: getArgValue(CLI_FLAG_HOST)
};
}

async function main() {
const server = createServer();
const transport = new StdioServerTransport();

const success = await startServer(server, transport);
if (!success) {
process.exit(1);
const config = parseConfig();

if (config.transport === TRANSPORT_STREAMABLE_HTTP) {
await startHttpServer({ port: config.port, host: config.host });
} else {
const server = createServer();
const transport = new StdioServerTransport();
const success = await startServer(server, transport);
if (!success) process.exit(1);
}
}

Expand Down