UNPKG

@trygordian/mcp-newyorkfed

Version:

Model Context Protocol server for New York Fed Markets Data APIs

685 lines 27.7 kB
#!/usr/bin/env node import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js"; // Base URL for NY Fed API const BASE_URL = "https://markets.newyorkfed.org"; // Supported formats const SUPPORTED_FORMATS = ["json", "xml", "csv", "pdf", "xlsx"]; // Tool definitions const REFERENCE_RATES_ALL_TOOL = { name: "get_reference_rates_all", description: "Get all reference rates (SOFR, EFFR, OBFR, etc.) for the latest available date or search with filters", inputSchema: { type: "object", properties: { operation: { type: "string", enum: ["latest", "search"], description: "Get latest rates or search with filters", default: "latest" }, format: { type: "string", enum: SUPPORTED_FORMATS, description: "Response format", default: "json" }, startDate: { type: "string", pattern: "^\\d{4}-\\d{2}-\\d{2}$", description: "Start date for search (YYYY-MM-DD format, only for search operation)" }, endDate: { type: "string", pattern: "^\\d{4}-\\d{2}-\\d{2}$", description: "End date for search (YYYY-MM-DD format, only for search operation)" } }, required: ["operation", "format"] } }; const REFERENCE_RATES_SECURED_TOOL = { name: "get_secured_rates", description: "Get secured reference rates (SOFR, TGCR, BGCR) with options for latest data or historical search", inputSchema: { type: "object", properties: { operation: { type: "string", enum: ["latest", "last", "search"], description: "Type of operation to perform" }, rateType: { type: "string", enum: ["sofr", "tgcr", "bgcr"], description: "Type of secured rate (required for last/search operations)" }, number: { type: "number", minimum: 1, maximum: 1000, description: "Number of records to retrieve (only for 'last' operation)" }, format: { type: "string", enum: SUPPORTED_FORMATS, description: "Response format", default: "json" }, startDate: { type: "string", pattern: "^\\d{4}-\\d{2}-\\d{2}$", description: "Start date for search (YYYY-MM-DD format, only for search operation)" }, endDate: { type: "string", pattern: "^\\d{4}-\\d{2}-\\d{2}$", description: "End date for search (YYYY-MM-DD format, only for search operation)" } }, required: ["operation", "format"] } }; const REFERENCE_RATES_UNSECURED_TOOL = { name: "get_unsecured_rates", description: "Get unsecured reference rates (EFFR, OBFR) with options for latest data or historical search", inputSchema: { type: "object", properties: { operation: { type: "string", enum: ["latest", "last", "search"], description: "Type of operation to perform" }, rateType: { type: "string", enum: ["effr", "obfr"], description: "Type of unsecured rate (required for last/search operations)" }, number: { type: "number", minimum: 1, maximum: 1000, description: "Number of records to retrieve (only for 'last' operation)" }, format: { type: "string", enum: SUPPORTED_FORMATS, description: "Response format", default: "json" }, startDate: { type: "string", pattern: "^\\d{4}-\\d{2}-\\d{2}$", description: "Start date for search (YYYY-MM-DD format, only for search operation)" }, endDate: { type: "string", pattern: "^\\d{4}-\\d{2}-\\d{2}$", description: "End date for search (YYYY-MM-DD format, only for search operation)" } }, required: ["operation", "format"] } }; const SOMA_HOLDINGS_TOOL = { name: "get_soma_holdings", description: "Get System Open Market Account (SOMA) holdings data for Treasury and Agency securities", inputSchema: { type: "object", properties: { operation: { type: "string", enum: ["summary", "latest_date", "list_dates", "treasury_by_date", "agency_by_date", "treasury_by_cusip", "agency_by_cusip"], description: "Type of SOMA data to retrieve" }, format: { type: "string", enum: SUPPORTED_FORMATS, description: "Response format", default: "json" }, date: { type: "string", pattern: "^\\d{4}-\\d{2}-\\d{2}$", description: "As-of date for holdings (YYYY-MM-DD format, required for *_by_date operations)" }, cusip: { type: "string", description: "CUSIP identifier (required for *_by_cusip operations)" }, holdingType: { type: "string", enum: ["bills", "notesbonds", "frn", "tips", "agency debts", "mbs", "cmbs"], description: "Type of holding (optional for *_by_date operations)" } }, required: ["operation", "format"] } }; const TREASURY_OPERATIONS_TOOL = { name: "get_treasury_operations", description: "Get Treasury securities operations including purchases, sales, and prices paid", inputSchema: { type: "object", properties: { operation: { type: "string", enum: ["purchases", "sales"], description: "Type of Treasury operation" }, timeframe: { type: "string", enum: ["latest", "lastTwoWeeks", "last"], description: "Timeframe for operations" }, status: { type: "string", enum: ["all", "completed"], description: "Operation status filter (required for latest timeframe)" }, include: { type: "string", enum: ["details", "summary"], description: "Level of detail to include" }, number: { type: "number", minimum: 1, maximum: 1000, description: "Number of operations to retrieve (only for 'last' timeframe)" }, format: { type: "string", enum: SUPPORTED_FORMATS, description: "Response format", default: "json" }, startDate: { type: "string", pattern: "^\\d{4}-\\d{2}-\\d{2}$", description: "Start date for search (YYYY-MM-DD format)" }, endDate: { type: "string", pattern: "^\\d{4}-\\d{2}-\\d{2}$", description: "End date for search (YYYY-MM-DD format)" } }, required: ["operation", "timeframe", "include", "format"] } }; const REPO_OPERATIONS_TOOL = { name: "get_repo_operations", description: "Get repo and reverse repo operations data", inputSchema: { type: "object", properties: { operationType: { type: "string", enum: ["repo", "reverserepo"], description: "Type of repo operation" }, method: { type: "string", enum: ["all", "single-tranche", "multiple-price"], description: "Operation method (not required for search operations)" }, timeframe: { type: "string", enum: ["latest", "lastTwoWeeks", "last", "search"], description: "Timeframe for operations" }, status: { type: "string", enum: ["all", "completed"], description: "Operation status (required for latest timeframe)" }, number: { type: "number", minimum: 1, maximum: 1000, description: "Number of operations to retrieve (only for 'last' timeframe)" }, format: { type: "string", enum: SUPPORTED_FORMATS, description: "Response format", default: "json" }, startDate: { type: "string", pattern: "^\\d{4}-\\d{2}-\\d{2}$", description: "Start date for search (YYYY-MM-DD format)" }, endDate: { type: "string", pattern: "^\\d{4}-\\d{2}-\\d{2}$", description: "End date for search (YYYY-MM-DD format)" } }, required: ["operationType", "timeframe", "format"] } }; const PRIMARY_DEALER_STATS_TOOL = { name: "get_primary_dealer_statistics", description: "Get Primary Dealer Statistics survey data", inputSchema: { type: "object", properties: { operation: { type: "string", enum: ["latest_by_series", "all_surveys", "list_timeseries", "list_dates", "list_series_breaks", "by_date", "by_timeseries", "by_series_and_timeseries"], description: "Type of dealer statistics operation" }, seriesBreak: { type: "string", description: "Series break identifier (required for latest_by_series and by_series_and_timeseries)" }, date: { type: "string", pattern: "^\\d{4}-\\d{2}-\\d{2}$", description: "As-of date (YYYY-MM-DD format, required for by_date operation)" }, timeseries: { type: "string", description: "Time series/key ID (required for by_timeseries and by_series_and_timeseries)" }, format: { type: "string", enum: SUPPORTED_FORMATS, description: "Response format", default: "json" } }, required: ["operation", "format"] } }; const MARKET_SHARE_TOOL = { name: "get_market_share", description: "Get Primary Dealer market share data (quarterly and year-to-date)", inputSchema: { type: "object", properties: { period: { type: "string", enum: ["quarterly", "ytd"], description: "Market share period" }, format: { type: "string", enum: SUPPORTED_FORMATS, description: "Response format", default: "json" } }, required: ["period", "format"] } }; // Server implementation const server = new Server({ name: "newyorkfed-mcp-server", version: "1.0.0", }, { capabilities: { tools: {}, }, }); // Utility function to build API URLs function buildApiUrl(path, queryParams) { const url = new URL(`${BASE_URL}${path}`); if (queryParams) { Object.entries(queryParams).forEach(([key, value]) => { if (value) url.searchParams.set(key, value); }); } return url.toString(); } // Utility function to make API requests async function makeApiRequest(url) { try { const response = await fetch(url); if (!response.ok) { throw new Error(`API request failed: ${response.status} ${response.statusText}`); } const contentType = response.headers.get('content-type') || ''; if (contentType.includes('application/json')) { const data = await response.json(); return JSON.stringify(data, null, 2); } else { return await response.text(); } } catch (error) { throw new Error(`Failed to fetch data: ${error instanceof Error ? error.message : String(error)}`); } } // Tool handlers server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [ REFERENCE_RATES_ALL_TOOL, REFERENCE_RATES_SECURED_TOOL, REFERENCE_RATES_UNSECURED_TOOL, SOMA_HOLDINGS_TOOL, TREASURY_OPERATIONS_TOOL, REPO_OPERATIONS_TOOL, PRIMARY_DEALER_STATS_TOOL, MARKET_SHARE_TOOL, ], })); server.setRequestHandler(CallToolRequestSchema, async (request) => { try { const { name, arguments: args } = request.params; if (!args) { throw new Error("No arguments provided"); } switch (name) { case "get_reference_rates_all": { const { operation, format, startDate, endDate } = args; let path; const queryParams = {}; if (operation === "latest") { path = `/api/rates/all/latest.${format}`; } else if (operation === "search") { path = `/api/rates/all/search.${format}`; if (startDate) queryParams.startDate = startDate; if (endDate) queryParams.endDate = endDate; } else { throw new Error("Invalid operation. Must be 'latest' or 'search'"); } const url = buildApiUrl(path, queryParams); const result = await makeApiRequest(url); return { content: [{ type: "text", text: result }], isError: false, }; } case "get_secured_rates": { const { operation, rateType, number, format, startDate, endDate } = args; let path; const queryParams = {}; if (operation === "latest") { path = `/api/rates/secured/all/latest.${format}`; } else if (operation === "last") { if (!rateType || !number) { throw new Error("rateType and number are required for 'last' operation"); } path = `/api/rates/secured/${rateType}/last/${number}.${format}`; } else if (operation === "search") { if (!rateType) { throw new Error("rateType is required for 'search' operation"); } path = `/api/rates/secured/${rateType}/search.${format}`; if (startDate) queryParams.startDate = startDate; if (endDate) queryParams.endDate = endDate; } else { throw new Error("Invalid operation. Must be 'latest', 'last', or 'search'"); } const url = buildApiUrl(path, queryParams); const result = await makeApiRequest(url); return { content: [{ type: "text", text: result }], isError: false, }; } case "get_unsecured_rates": { const { operation, rateType, number, format, startDate, endDate } = args; let path; const queryParams = {}; if (operation === "latest") { path = `/api/rates/unsecured/all/latest.${format}`; } else if (operation === "last") { if (!rateType || !number) { throw new Error("rateType and number are required for 'last' operation"); } path = `/api/rates/unsecured/${rateType}/last/${number}.${format}`; } else if (operation === "search") { if (!rateType) { throw new Error("rateType is required for 'search' operation"); } path = `/api/rates/unsecured/${rateType}/search.${format}`; if (startDate) queryParams.startDate = startDate; if (endDate) queryParams.endDate = endDate; } else { throw new Error("Invalid operation. Must be 'latest', 'last', or 'search'"); } const url = buildApiUrl(path, queryParams); const result = await makeApiRequest(url); return { content: [{ type: "text", text: result }], isError: false, }; } case "get_soma_holdings": { const { operation, format, date, cusip, holdingType } = args; let path; switch (operation) { case "summary": path = `/api/soma/summary.${format}`; break; case "latest_date": path = `/api/soma/asofdates/latest.${format}`; break; case "list_dates": path = `/api/soma/asofdates/list.${format}`; break; case "treasury_by_date": if (!date) throw new Error("date is required for treasury_by_date operation"); if (holdingType) { path = `/api/soma/tsy/get/${holdingType}/asof/${date}.${format}`; } else { path = `/api/soma/tsy/get/asof/${date}.${format}`; } break; case "agency_by_date": if (!date) throw new Error("date is required for agency_by_date operation"); if (holdingType) { path = `/api/soma/agency/get/${holdingType}/asof/${date}.${format}`; } else { path = `/api/soma/agency/get/asof/${date}.${format}`; } break; case "treasury_by_cusip": if (!cusip) throw new Error("cusip is required for treasury_by_cusip operation"); path = `/api/soma/tsy/get/cusip/${cusip}.${format}`; break; case "agency_by_cusip": if (!cusip) throw new Error("cusip is required for agency_by_cusip operation"); path = `/api/soma/agency/get/cusip/${cusip}.${format}`; break; default: throw new Error("Invalid operation"); } const url = buildApiUrl(path); const result = await makeApiRequest(url); return { content: [{ type: "text", text: result }], isError: false, }; } case "get_treasury_operations": { const { operation, timeframe, status, include, number, format, startDate, endDate } = args; let path; const queryParams = {}; if (timeframe === "latest") { if (!status) throw new Error("status is required for latest timeframe"); path = `/api/tsy/${operation}/${status}/${include}/latest.${format}`; } else if (timeframe === "lastTwoWeeks") { path = `/api/tsy/${operation}/results/${include}/lastTwoWeeks.${format}`; } else if (timeframe === "last") { if (!number) throw new Error("number is required for last timeframe"); path = `/api/tsy/${operation}/results/${include}/last/${number}.${format}`; } else if (timeframe === "search") { path = `/api/tsy/${operation}/results/${include}/search.${format}`; if (startDate) queryParams.startDate = startDate; if (endDate) queryParams.endDate = endDate; } else { throw new Error("Invalid timeframe"); } const url = buildApiUrl(path, queryParams); const result = await makeApiRequest(url); return { content: [{ type: "text", text: result }], isError: false, }; } case "get_repo_operations": { const { operationType, method, timeframe, status, number, format, startDate, endDate } = args; let path; const queryParams = {}; if (timeframe === "search") { if (operationType === "reverserepo") { path = `/api/rp/reverserepo/propositions/search.${format}`; } else { path = `/api/rp/results/search.${format}`; } if (startDate) queryParams.startDate = startDate; if (endDate) queryParams.endDate = endDate; } else if (timeframe === "latest") { if (!method || !status) throw new Error("method and status are required for latest timeframe"); path = `/api/rp/${operationType}/${method}/${status}/latest.${format}`; } else if (timeframe === "lastTwoWeeks") { if (!method) throw new Error("method is required for lastTwoWeeks timeframe"); path = `/api/rp/${operationType}/${method}/results/lastTwoWeeks.${format}`; } else if (timeframe === "last") { if (!method || !number) throw new Error("method and number are required for last timeframe"); path = `/api/rp/${operationType}/${method}/results/last/${number}.${format}`; } else { throw new Error("Invalid timeframe"); } const url = buildApiUrl(path, queryParams); const result = await makeApiRequest(url); return { content: [{ type: "text", text: result }], isError: false, }; } case "get_primary_dealer_statistics": { const { operation, seriesBreak, date, timeseries, format } = args; let path; switch (operation) { case "latest_by_series": if (!seriesBreak) throw new Error("seriesBreak is required for latest_by_series operation"); path = `/api/pd/latest/${seriesBreak}.${format}`; break; case "all_surveys": if (format !== "csv") throw new Error("all_surveys operation only supports CSV format"); path = `/api/pd/get/all/timeseries.csv`; break; case "list_timeseries": path = `/api/pd/list/timeseries.${format}`; break; case "list_dates": path = `/api/pd/list/asof.${format}`; break; case "list_series_breaks": path = `/api/pd/list/seriesbreaks.${format}`; break; case "by_date": if (!date) throw new Error("date is required for by_date operation"); path = `/api/pd/get/asof/${date}.${format}`; break; case "by_timeseries": if (!timeseries) throw new Error("timeseries is required for by_timeseries operation"); path = `/api/pd/get/${timeseries}.${format}`; break; case "by_series_and_timeseries": if (!seriesBreak || !timeseries) { throw new Error("seriesBreak and timeseries are required for by_series_and_timeseries operation"); } path = `/api/pd/get/${seriesBreak}/timeseries/${timeseries}.${format}`; break; default: throw new Error("Invalid operation"); } const url = buildApiUrl(path); const result = await makeApiRequest(url); return { content: [{ type: "text", text: result }], isError: false, }; } case "get_market_share": { const { period, format } = args; const path = `/api/marketshare/${period}/latest.${format}`; const url = buildApiUrl(path); const result = await makeApiRequest(url); return { content: [{ type: "text", text: result }], isError: false, }; } default: return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true, }; } } catch (error) { return { content: [ { type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}`, }, ], isError: true, }; } }); async function runServer() { const transport = new StdioServerTransport(); await server.connect(transport); console.error("New York Fed MCP Server running on stdio"); } runServer().catch((error) => { console.error("Fatal error running server:", error); process.exit(1); }); //# sourceMappingURL=index.js.map