Financial Datasets MCP Server

MIT License
139
  • Apple
import json import os import httpx import logging import sys from mcp.server.fastmcp import FastMCP # Configure logging to write to stderr logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", stream=sys.stderr, ) logger = logging.getLogger("financial-datasets-mcp") # Initialize FastMCP server mcp = FastMCP("financial-datasets") # Constants FINANCIAL_DATASETS_API_BASE = "https://api.financialdatasets.ai" # Helper function to make API requests async def make_request(url: str) -> dict[str, any] | None: """Make a request to the Financial Datasets API with proper error handling.""" headers = {} if api_key := os.environ.get("FINANCIAL_DATASETS_API_KEY"): headers["X-API-KEY"] = api_key async with httpx.AsyncClient() as client: try: response = await client.get(url, headers=headers, timeout=30.0) response.raise_for_status() return response.json() except Exception as e: return {"Error": str(e)} @mcp.tool() async def get_income_statements( ticker: str, period: str = "annual", limit: int = 4, ) -> str: """Get income statements for a company. Args: ticker: Ticker symbol of the company (e.g. AAPL, GOOGL) period: Period of the income statement (e.g. annual, quarterly, ttm) limit: Number of income statements to return (default: 4) """ # Fetch data from the API url = f"{FINANCIAL_DATASETS_API_BASE}/income-statements/{ticker}?period={period}&limit={limit}" data = await make_request(url) # Check if data is found if not data: return "Unable to fetch income statements or no income statements found." # Extract the income statements income_statements = data.get("income_statements", []) # Check if income statements are found if not income_statements: return "Unable to fetch income statements or no income statements found." # Stringify the income statements return json.dumps(income_statements, indent=2) @mcp.tool() async def get_balance_sheets( ticker: str, period: str = "annual", limit: int = 4, ) -> str: """Get balance sheets for a company. Args: ticker: Ticker symbol of the company (e.g. AAPL, GOOGL) period: Period of the balance sheet (e.g. annual, quarterly, ttm) limit: Number of balance sheets to return (default: 4) """ # Fetch data from the API url = f"{FINANCIAL_DATASETS_API_BASE}/balance-sheets/{ticker}?period={period}&limit={limit}" data = await make_request(url) # Check if data is found if not data: return "Unable to fetch balance sheets or no balance sheets found." # Extract the balance sheets balance_sheets = data.get("balance_sheets", []) # Check if balance sheets are found if not balance_sheets: return "Unable to fetch balance sheets or no balance sheets found." # Stringify the balance sheets return json.dumps(balance_sheets, indent=2) @mcp.tool() async def get_cash_flow_statements( ticker: str, period: str = "annual", limit: int = 4, ) -> str: """Get cash flow statements for a company. Args: ticker: Ticker symbol of the company (e.g. AAPL, GOOGL) period: Period of the cash flow statement (e.g. annual, quarterly, ttm) limit: Number of cash flow statements to return (default: 4) """ # Fetch data from the API url = f"{FINANCIAL_DATASETS_API_BASE}/cash-flow-statements/{ticker}?period={period}&limit={limit}" data = await make_request(url) # Check if data is found if not data: return "Unable to fetch cash flow statements or no cash flow statements found." # Extract the cash flow statements cash_flow_statements = data.get("cash_flow_statements", []) # Check if cash flow statements are found if not cash_flow_statements: return "Unable to fetch cash flow statements or no cash flow statements found." # Stringify the cash flow statements return json.dumps(cash_flow_statements, indent=2) @mcp.tool() async def get_current_stock_price(ticker: str) -> str: """Get the current / latest price of a company. Args: ticker: Ticker symbol of the company (e.g. AAPL, GOOGL) """ # Fetch data from the API url = f"{FINANCIAL_DATASETS_API_BASE}/prices/snapshot/?ticker={ticker}" data = await make_request(url) # Check if data is found if not data: return "Unable to fetch current price or no current price found." # Extract the current price snapshot = data.get("snapshot", {}) # Check if current price is found if not snapshot: return "Unable to fetch current price or no current price found." # Stringify the current price return json.dumps(snapshot, indent=2) @mcp.tool() async def get_historical_stock_prices( ticker: str, start_date: str, end_date: str, interval: str = "day", interval_multiplier: int = 1, ) -> str: """Gets historical stock prices for a company. Args: ticker: Ticker symbol of the company (e.g. AAPL, GOOGL) start_date: Start date of the price data (e.g. 2020-01-01) end_date: End date of the price data (e.g. 2020-12-31) interval: Interval of the price data (e.g. minute, hour, day, week, month) interval_multiplier: Multiplier of the interval (e.g. 1, 2, 3) """ # Fetch data from the API url = f"{FINANCIAL_DATASETS_API_BASE}/prices/?ticker={ticker}&interval={interval}&interval_multiplier={interval_multiplier}&start_date={start_date}&end_date={end_date}" data = await make_request(url) # Check if data is found if not data: return "Unable to fetch prices or no prices found." # Extract the prices prices = data.get("prices", []) # Check if prices are found if not prices: return "Unable to fetch prices or no prices found." # Stringify the prices return json.dumps(prices, indent=2) @mcp.tool() async def get_company_news(ticker: str) -> str: """Get news for a company. Args: ticker: Ticker symbol of the company (e.g. AAPL, GOOGL) """ # Fetch data from the API url = f"{FINANCIAL_DATASETS_API_BASE}/news/?ticker={ticker}" data = await make_request(url) # Check if data is found if not data: return "Unable to fetch news or no news found." # Extract the news news = data.get("news", []) # Check if news are found if not news: return "Unable to fetch news or no news found." @mcp.tool() async def get_available_crypto_tickers() -> str: """ Gets all available crypto tickers. """ # Fetch data from the API url = f"{FINANCIAL_DATASETS_API_BASE}/crypto/prices/tickers" data = await make_request(url) # Check if data is found if not data: return "Unable to fetch available crypto tickers or no available crypto tickers found." # Extract the available crypto tickers tickers = data.get("tickers", []) # Stringify the available crypto tickers return json.dumps(tickers, indent=2) @mcp.tool() async def get_crypto_prices( ticker: str, start_date: str, end_date: str, interval: str = "day", interval_multiplier: int = 1, ) -> str: """ Gets historical prices for a crypto currency. """ # Fetch data from the API url = f"{FINANCIAL_DATASETS_API_BASE}/crypto/prices/?ticker={ticker}&interval={interval}&interval_multiplier={interval_multiplier}&start_date={start_date}&end_date={end_date}" data = await make_request(url) # Check if data is found if not data: return "Unable to fetch prices or no prices found." # Extract the prices prices = data.get("prices", []) # Check if prices are found if not prices: return "Unable to fetch prices or no prices found." # Stringify the prices return json.dumps(prices, indent=2) @mcp.tool() async def get_historical_crypto_prices( ticker: str, start_date: str, end_date: str, interval: str = "day", interval_multiplier: int = 1, ) -> str: """Gets historical prices for a crypto currency. Args: ticker: Ticker symbol of the crypto currency (e.g. BTC-USD). The list of available crypto tickers can be retrieved via the get_available_crypto_tickers tool. start_date: Start date of the price data (e.g. 2020-01-01) end_date: End date of the price data (e.g. 2020-12-31) interval: Interval of the price data (e.g. minute, hour, day, week, month) interval_multiplier: Multiplier of the interval (e.g. 1, 2, 3) """ # Fetch data from the API url = f"{FINANCIAL_DATASETS_API_BASE}/crypto/prices/?ticker={ticker}&interval={interval}&interval_multiplier={interval_multiplier}&start_date={start_date}&end_date={end_date}" data = await make_request(url) # Check if data is found if not data: return "Unable to fetch prices or no prices found." # Extract the prices prices = data.get("prices", []) # Check if prices are found if not prices: return "Unable to fetch prices or no prices found." # Stringify the prices return json.dumps(prices, indent=2) @mcp.tool() async def get_current_crypto_price(ticker: str) -> str: """Get the current / latest price of a crypto currency. Args: ticker: Ticker symbol of the crypto currency (e.g. BTC-USD). The list of available crypto tickers can be retrieved via the get_available_crypto_tickers tool. """ # Fetch data from the API url = f"{FINANCIAL_DATASETS_API_BASE}/crypto/prices/snapshot/?ticker={ticker}" data = await make_request(url) # Check if data is found if not data: return "Unable to fetch current price or no current price found." # Extract the current price snapshot = data.get("snapshot", {}) # Check if current price is found if not snapshot: return "Unable to fetch current price or no current price found." # Stringify the current price return json.dumps(snapshot, indent=2) if __name__ == "__main__": # Log server startup logger.info("Starting Financial Datasets MCP Server...") # Initialize and run the server mcp.run(transport="stdio") # This line won't be reached during normal operation logger.info("Server stopped")