Skip to main content
Glama

MCP Financial Datasets Server

financial_datasets_server.py18.4 kB
""" MCP SERVER IMPLEMENTATION FROM FINANCIAL DATASETS API (Modified from https://github.com/financial-datasets/mcp-server) """ import json import os import httpx import logging from datetime import datetime from fastmcp import FastMCP from dotenv import load_dotenv # Configure logging logger = logging.getLogger(__name__) # 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.""" # Load environment variables from .env file load_dotenv() 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( exclude_args=["cutoff_date"] ) async def get_income_statements( ticker: str, cutoff_date: str = datetime.now().strftime("%Y-%m-%d"), period: str = "annual", limit: int = 4, ) -> str: """Get historical income statements for a company. Args: ticker: Stock ticker symbol (e.g. AAPL for Apple, MSFT for Microsoft, GOOGL for Google) period: Time period - "annual" for yearly data, "quarterly" for Q1/Q2/Q3/Q4, "ttm" for trailing twelve months limit: Number of historical periods to return (default: 4, e.g. last 4 years or quarters) Returns: JSON array of income statements with fields like revenue, net_income, operating_income, earnings_per_share, etc. """ # Fetch data from the API url = f"{FINANCIAL_DATASETS_API_BASE}/financials/income-statements/?ticker={ticker}&period={period}&limit={limit}&report_period_lte={cutoff_date}" 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( exclude_args=["cutoff_date"] ) async def get_balance_sheets( ticker: str, cutoff_date: str = datetime.now().strftime("%Y-%m-%d"), period: str = "annual", limit: int = 4, ) -> str: """Get historical balance sheets for a company. Args: ticker: Stock ticker symbol (e.g. AAPL for Apple, MSFT for Microsoft, GOOGL for Google) period: Time period - "annual" for yearly data, "quarterly" for Q1/Q2/Q3/Q4, "ttm" for trailing twelve months limit: Number of historical periods to return (default: 4, e.g. last 4 years or quarters) Returns: JSON array of balance sheets with fields like total_assets, cash_and_equivalents, total_debt, shareholders_equity, etc. """ # Fetch data from the API url = f"{FINANCIAL_DATASETS_API_BASE}/financials/balance-sheets/?ticker={ticker}&period={period}&limit={limit}&report_period_lte={cutoff_date}" 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( exclude_args=["cutoff_date"] ) async def get_cash_flow_statements( ticker: str, cutoff_date: str = datetime.now().strftime("%Y-%m-%d"), period: str = "annual", limit: int = 4, ) -> str: """Get historical cash flow statements for a company. Args: ticker: Stock ticker symbol (e.g. AAPL for Apple, MSFT for Microsoft, GOOGL for Google) period: Time period - "annual" for yearly data, "quarterly" for Q1/Q2/Q3/Q4, "ttm" for trailing twelve months limit: Number of historical periods to return (default: 4, e.g. last 4 years or quarters) Returns: JSON array of cash flow statements with fields like net_cash_flow_from_operations, capital_expenditure, free_cash_flow, etc. """ # Fetch data from the API url = f"{FINANCIAL_DATASETS_API_BASE}/financials/cash-flow-statements/?ticker={ticker}&period={period}&limit={limit}&report_period_lte={cutoff_date}" 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) # DISABLED FOR BACKTESTING: This tool returns the current/latest stock price, which would always # leak future information relative to any historical cutoff_date. There is no way to make this # endpoint compliant for backtesting purposes. # # ALTERNATIVE: Use get_historical_stock_prices with start_date=cutoff_date and end_date=cutoff_date # to get the price at a specific historical date. # # @mcp.tool( # exclude_args=["cutoff_date"] # ) # 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( exclude_args=["cutoff_date"] ) async def get_historical_stock_prices( ticker: str, start_date: str, end_date: str, cutoff_date: str = datetime.now().strftime("%Y-%m-%d"), interval: str = "day", interval_multiplier: int = 1, ) -> str: """Get historical stock price data including open, close, high, low, and volume for a date range. Returns OHLCV (Open, High, Low, Close, Volume) price data at specified time intervals. Use this to analyze price trends, calculate returns, identify support/resistance levels, or build technical indicators. Perfect for backtesting strategies or understanding price movements over time. Args: ticker: Stock ticker symbol (e.g. AAPL for Apple, TSLA for Tesla, NVDA for Nvidia) start_date: Start date in YYYY-MM-DD format (e.g. 2020-01-01) end_date: End date in YYYY-MM-DD format (e.g. 2020-12-31) interval: Time interval - "minute", "hour", "day", "week", or "month" interval_multiplier: Multiply the interval (e.g. 5 with "minute" = every 5 minutes, 2 with "day" = every 2 days) Returns: JSON array of price records with open, close, high, low, volume, and timestamp fields. """ # Clamp end_date to cutoff_date if it exceeds it if end_date > cutoff_date: end_date = cutoff_date # 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( exclude_args=["cutoff_date"] ) async def get_company_news(ticker: str, cutoff_date: str = datetime.now().strftime("%Y-%m-%d")) -> str: """Get recent news articles about a specific company from major financial news sources. Returns up to 20 recent news articles with titles, URLs, publication dates, authors, and sentiment. Use this to understand recent events, announcements, product launches, earnings reports, or market sentiment that might affect the company's stock price or business performance. Args: ticker: Stock ticker symbol (e.g. AAPL for Apple, TSLA for Tesla, NVDA for Nvidia) Returns: JSON array of news articles with title, url, date, author, source, and sentiment fields. """ # Fetch data from the API url = f"{FINANCIAL_DATASETS_API_BASE}/news/?limit=20&end_date={cutoff_date}&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." return json.dumps(news, indent=2) @mcp.tool( exclude_args=["cutoff_date"] ) async def get_available_crypto_tickers( cutoff_date: str = datetime.now().strftime("%Y-%m-%d") ) -> str: """Get a list of all available cryptocurrency tickers that can be queried for price data. Returns ticker symbols for major cryptocurrencies like BTC-USD (Bitcoin), ETH-USD (Ethereum), and many others. Use this to discover what crypto assets are available before requesting their price history or current data. Returns: JSON array of available cryptocurrency ticker symbols (e.g. ["BTC-USD", "ETH-USD", "SOL-USD"]). """ # 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( exclude_args=["cutoff_date"] ) async def get_crypto_prices( ticker: str, start_date: str, end_date: str, cutoff_date: str = datetime.now().strftime("%Y-%m-%d"), interval: str = "day", interval_multiplier: int = 1, ) -> str: """Get historical cryptocurrency price data including open, close, high, low, and volume. Returns OHLCV (Open, High, Low, Close, Volume) price data for cryptocurrencies at specified time intervals. Use this to analyze crypto price trends, volatility, trading volumes, or build technical analysis. Covers major cryptocurrencies like Bitcoin, Ethereum, and many altcoins. Args: ticker: Cryptocurrency ticker symbol (e.g. BTC-USD for Bitcoin, ETH-USD for Ethereum, SOL-USD for Solana) start_date: Start date in YYYY-MM-DD format (e.g. 2020-01-01) end_date: End date in YYYY-MM-DD format (e.g. 2020-12-31) interval: Time interval - "minute", "hour", "day", "week", or "month" interval_multiplier: Multiply the interval (e.g. 5 with "minute" = every 5 minutes, 2 with "day" = every 2 days) Returns: JSON array of price records with open, close, high, low, volume, and timestamp fields. """ # Clamp end_date to cutoff_date if it exceeds it if end_date > cutoff_date: end_date = cutoff_date # 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( exclude_args=["cutoff_date"] ) async def get_historical_crypto_prices( ticker: str, start_date: str, end_date: str, cutoff_date: str = datetime.now().strftime("%Y-%m-%d"), interval: str = "day", interval_multiplier: int = 1, ) -> str: """Get historical cryptocurrency price data including open, close, high, low, and volume. Returns OHLCV (Open, High, Low, Close, Volume) price data for cryptocurrencies at specified time intervals. Identical to get_crypto_prices - use either one. Covers major cryptocurrencies sourced from exchanges like Coinbase, Kraken, and Bitfinex. Use get_available_crypto_tickers to see all available cryptocurrency tickers before querying. Args: ticker: Cryptocurrency ticker symbol (e.g. BTC-USD for Bitcoin, ETH-USD for Ethereum, SOL-USD for Solana) start_date: Start date in YYYY-MM-DD format (e.g. 2020-01-01) end_date: End date in YYYY-MM-DD format (e.g. 2020-12-31) interval: Time interval - "minute", "hour", "day", "week", or "month" interval_multiplier: Multiply the interval (e.g. 5 with "minute" = every 5 minutes, 2 with "day" = every 2 days) Returns: JSON array of price records with open, close, high, low, volume, and timestamp fields. """ # Clamp end_date to cutoff_date if it exceeds it if end_date > cutoff_date: end_date = cutoff_date # 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) # DISABLED FOR BACKTESTING: This tool returns the current/latest crypto price, which would always # leak future information relative to any historical cutoff_date. There is no way to make this # endpoint compliant for backtesting purposes. # # ALTERNATIVE: Use get_historical_crypto_prices or get_crypto_prices with start_date=cutoff_date # and end_date=cutoff_date to get the price at a specific historical date. # # @mcp.tool( # exclude_args=["cutoff_date"] # ) # 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) # DISABLED FOR BACKTESTING: This tool cannot guarantee backtesting validity because the API # only returns 'report_date' (the fiscal period end date), not the 'filing_date' (when the # filing was actually made public with the SEC). # # ISSUE: A 10-K for fiscal year ending Dec 31, 2023 (report_date=2023-12-31) might not be # filed until March 2024. If cutoff_date=2024-02-01, we would incorrectly include a filing # that wasn't yet public, leaking future information. # # Without access to the actual filing date (when made public), we cannot reliably filter # SEC filings for backtesting purposes. The report_date is not sufficient. # # POTENTIAL SOLUTION: If the API supports filing_date filtering, this could be re-enabled. # The filing_date would need to be <= cutoff_date to ensure the filing was publicly available. # # @mcp.tool( # exclude_args=["cutoff_date"] # ) # async def get_sec_filings( # ticker: str, # cutoff_date: str, # limit: int = 10, # filing_type: str | None = None, # ) -> str: # """Get all SEC filings for a company. # # Args: # ticker: Ticker symbol of the company (e.g. AAPL, GOOGL) # cutoff_date: Cutoff date in YYYY-MM-DD format. Only returns filings made public before this date. # limit: Number of SEC filings to return (default: 10) # filing_type: Type of SEC filing (e.g. 10-K, 10-Q, 8-K) # """ # # Fetch data from the API # url = f"{FINANCIAL_DATASETS_API_BASE}/filings/?ticker={ticker}&limit={limit}" # if filing_type: # url += f"&filing_type={filing_type}" # # # Call the API # data = await make_request(url) # # # Extract the SEC filings # filings = data.get("filings", []) # # # Check if SEC filings are found # if not filings: # return f"Unable to fetch SEC filings or no SEC filings found." # # # Stringify the SEC filings # return json.dumps(filings, 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")

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/chestnutforty/mcp-financial-datasets'

If you have feedback or need assistance with the MCP directory API, please join our Discord server