Skip to main content
Glama

Finance MCP Server

by dino65-dev
yfinance_mcp_server.py16.1 kB
import os import json import logging from datetime import datetime from typing import Any, Sequence, Dict, List, Optional, Union from collections.abc import Sequence as SequenceABC import yfinance as yf from mcp.server import Server from mcp.types import ( Resource, Tool, TextContent, ImageContent, EmbeddedResource, LoggingLevel ) from pydantic import AnyUrl # Configure logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger("yfinance-mcp-server") # Default settings DEFAULT_SYMBOLS = ["AAPL", "MSFT", "GOOG", "AMZN", "META"] app = Server("yfinance-mcp-server") async def fetch_stock_info(symbol: str) -> dict[str, Any]: """Fetch current stock information.""" try: stock = yf.Ticker(symbol) info = stock.info return info except Exception as e: logger.error(f"Error fetching stock info for {symbol}: {str(e)}") raise RuntimeError(f"Failed to fetch stock info: {str(e)}") @app.list_resources() async def list_resources() -> list[Resource]: """List available financial resources.""" resources = [] # Add default stock resources for symbol in DEFAULT_SYMBOLS: uri = AnyUrl(f"finance://{symbol}/info") resources.append( Resource( uri=uri, name=f"Current stock information for {symbol}", mimeType="application/json", description=f"Real-time market data for {symbol}" ) ) return resources @app.read_resource() async def read_resource(uri: AnyUrl) -> str: """Read current stock information.""" uri_str = str(uri) if uri_str.startswith("finance://") and uri_str.endswith("/info"): parts = uri_str.split("/") if len(parts) >= 3: symbol = parts[-2] try: stock_data = await fetch_stock_info(symbol) return json.dumps(stock_data, indent=2) except Exception as e: logger.error(f"Error reading resource {uri}: {str(e)}") raise RuntimeError(f"Failed to read resource: {str(e)}") raise ValueError(f"Unknown resource: {uri}") @app.list_tools() async def list_tools() -> list[Tool]: """List available financial tools.""" return [ Tool( name="get_stock_price", description="Get the current stock price for a given symbol", inputSchema={ "type": "object", "properties": { "symbol": { "type": "string", "description": "Stock symbol (e.g., AAPL, MSFT, GOOG)" } }, "required": ["symbol"] } ), Tool( name="get_historical_data", description="Get historical stock data for a symbol", inputSchema={ "type": "object", "properties": { "symbol": { "type": "string", "description": "Stock symbol (e.g., AAPL, MSFT, GOOG)" }, "period": { "type": "string", "description": "Time period (1d, 5d, 1mo, 3mo, 6mo, 1y, 2y, 5y, 10y, ytd, max)", "enum": ["1d", "5d", "1mo", "3mo", "6mo", "1y", "2y", "5y", "10y", "ytd", "max"], "default": "1mo" } }, "required": ["symbol"] } ), Tool( name="get_stock_metric", description="""Get a specific metric for a stock using yfinance field names. Common requests and their exact field names: Stock Price & Trading Info: - Current/Stock Price: currentPrice - Opening Price: open - Day's High: dayHigh - Day's Low: dayLow - Previous Close: previousClose - 52 Week High: fiftyTwoWeekHigh - 52 Week Low: fiftyTwoWeekLow - 50 Day Average: fiftyDayAverage - 200 Day Average: twoHundredDayAverage - Trading Volume: volume - Average Volume: averageVolume - Average Daily Volume (10 day): averageDailyVolume10Day - Market Cap/Capitalization: marketCap - Beta: beta - Bid Price: bid - Ask Price: ask - Bid Size: bidSize - Ask Size: askSize Company Information: - Company Name: longName - Short Name: shortName - Business Description/About/Summary: longBusinessSummary - Industry: industry - Sector: sector - Website: website - Number of Employees: fullTimeEmployees - Country: country - State: state - City: city - Address: address1 Financial Metrics: - PE Ratio: trailingPE - Forward PE: forwardPE - Price to Book: priceToBook - Price to Sales: priceToSalesTrailing12Months - Enterprise Value: enterpriseValue - Enterprise to EBITDA: enterpriseToEbitda - Enterprise to Revenue: enterpriseToRevenue - Book Value: bookValue Earnings & Revenue: - Revenue/Total Revenue: totalRevenue - Revenue Growth: revenueGrowth - Revenue Per Share: revenuePerShare - EBITDA: ebitda - EBITDA Margins: ebitdaMargins - Net Income: netIncomeToCommon - Earnings Growth: earningsGrowth - Quarterly Earnings Growth: earningsQuarterlyGrowth - Forward EPS: forwardEps - Trailing EPS: trailingEps Margins & Returns: - Profit Margin: profitMargins - Operating Margin: operatingMargins - Gross Margins: grossMargins - Return on Equity/ROE: returnOnEquity - Return on Assets/ROA: returnOnAssets Dividends: - Dividend Yield: dividendYield - Dividend Rate: dividendRate - Dividend Date: lastDividendDate - Ex-Dividend Date: exDividendDate - Payout Ratio: payoutRatio Balance Sheet: - Total Cash: totalCash - Cash Per Share: totalCashPerShare - Total Debt: totalDebt - Debt to Equity: debtToEquity - Current Ratio: currentRatio - Quick Ratio: quickRatio Ownership: - Institutional Ownership: heldPercentInstitutions - Insider Ownership: heldPercentInsiders - Float Shares: floatShares - Shares Outstanding: sharesOutstanding - Short Ratio: shortRatio Analyst Coverage: - Analyst Recommendation: recommendationKey - Number of Analysts: numberOfAnalystOpinions - Price Target Mean: targetMeanPrice - Price Target High: targetHighPrice - Price Target Low: targetLowPrice - Price Target Median: targetMedianPrice Risk Metrics: - Overall Risk: overallRisk - Audit Risk: auditRisk - Board Risk: boardRisk - Compensation Risk: compensationRisk Other: - Currency: currency - Exchange: exchange - Year Change/52 Week Change: 52WeekChange - S&P 500 Year Change: SandP52WeekChange""", inputSchema={ "type": "object", "properties": { "symbol": { "type": "string", "description": "Stock symbol (e.g., AAPL, MSFT, GOOG)" }, "metric": { "type": "string", "description": "The metric to retrieve, use camelCase" } }, "required": ["symbol", "metric"] } ), Tool( name="compare_stocks", description="Compare multiple stocks by a specific metric", inputSchema={ "type": "object", "properties": { "symbols": { "type": "array", "items": { "type": "string" }, "description": "List of stock symbols to compare" }, "metric": { "type": "string", "description": "Metric to compare (e.g., currentPrice, marketCap)" } }, "required": ["symbols", "metric"] } ), Tool( name="search_stocks", description="Search for stocks by company name or keyword", inputSchema={ "type": "object", "properties": { "query": { "type": "string", "description": "Search query (company name or keyword)" }, "limit": { "type": "integer", "description": "Maximum number of results to return", "default": 5 } }, "required": ["query"] } ) ] @app.call_tool() async def call_tool(name: str, arguments: Any) -> Sequence[TextContent | ImageContent | EmbeddedResource]: """Handle tool calls.""" logger.info(f"Tool called: {name} with arguments: {arguments}") try: if name == "get_stock_price": symbol = arguments["symbol"] stock = yf.Ticker(symbol) # Try to get the most recent price history = stock.history(period="1d") if not history.empty: price = history['Close'].iloc[-1] else: # Fallback to info info = stock.info price = info.get("currentPrice", info.get("regularMarketPrice", None)) if price is None: return [TextContent(type="text", text=f"Could not retrieve price for {symbol}")] return [TextContent( type="text", text=f"The current price of {symbol} is ${price:.2f}" )] elif name == "get_stock_metric": symbol = arguments["symbol"] metric = arguments["metric"] stock_data = await fetch_stock_info(symbol) if metric in stock_data: # Format different metrics appropriately value = stock_data[metric] formatted_value = value # Format percentages if metric in ["dividendYield", "profitMargins", "operatingMargins", "grossMargins"]: if value is not None: formatted_value = f"{value * 100:.2f}%" # Format currency values elif metric in ["currentPrice", "open", "dayHigh", "dayLow", "targetMeanPrice"]: if value is not None: formatted_value = f"${value:.2f}" # Format large numbers elif metric in ["marketCap", "totalRevenue", "volume"]: if value is not None: formatted_value = f"{value:,}" return [TextContent( type="text", text=f"{symbol} {metric}: {formatted_value}" )] else: return [TextContent( type="text", text=f"Metric '{metric}' not found for {symbol}" )] elif name == "get_historical_data": symbol = arguments["symbol"] period = arguments.get("period", "1mo") stock = yf.Ticker(symbol) history = stock.history(period=period) if history.empty: return [TextContent( type="text", text=f"No historical data available for {symbol} with period {period}" )] data = [] for date, row in history.iterrows(): data.append({ "date": date.strftime("%Y-%m-%d"), "open": row["Open"], "high": row["High"], "low": row["Low"], "close": row["Close"], "volume": row["Volume"] }) return [TextContent( type="text", text=json.dumps(data, indent=2) )] elif name == "compare_stocks": symbols = arguments["symbols"] metric = arguments["metric"] results = {} for symbol in symbols: try: stock_data = await fetch_stock_info(symbol) if metric in stock_data: results[symbol] = stock_data[metric] else: results[symbol] = f"Metric '{metric}' not found" except Exception as e: results[symbol] = f"Error: {str(e)}" return [TextContent( type="text", text=json.dumps(results, indent=2) )] elif name == "search_stocks": query = arguments["query"] limit = arguments.get("limit", 5) # Use yfinance search functionality from yfinance.search import Search search_results = Search(query=query, max_results=limit) results = {} if search_results.quotes: results["quotes"] = [] for quote in search_results.quotes[:limit]: results["quotes"].append({ "symbol": quote.get("symbol"), "name": quote.get("shortname", quote.get("longname")), "exchange": quote.get("exchange"), "price": quote.get("regularMarketPrice") }) if search_results.news: results["news"] = [] for news in search_results.news[:limit]: results["news"].append({ "title": news.get("title"), "publisher": news.get("publisher"), "link": news.get("link") }) return [TextContent( type="text", text=json.dumps(results, indent=2) )] else: return [TextContent( type="text", text=f"Unknown tool: {name}" )] except Exception as e: logger.error(f"Error in call_tool: {str(e)}") return [TextContent( type="text", text=f"Error: {str(e)}" )] async def main(): """Run the server.""" from mcp.server.stdio import stdio_server logger.info("Starting yfinance MCP server...") async with stdio_server() as (read_stream, write_stream): await app.run( read_stream, write_stream, app.create_initialization_options() ) if __name__ == "__main__": import asyncio asyncio.run(main())

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/dino65-dev/Finance_mcp-server'

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