Spaces:
Sleeping
Sleeping
File size: 12,338 Bytes
ebd74d9 7a0fde5 ebd74d9 7a0fde5 ebd74d9 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 | #!/usr/bin/env python3
"""
Finnhub MCP Server - Standard MCP Protocol (JSON-RPC over SSE)
This is a standalone MCP server that implements the standard Model Context Protocol.
It provides three tools for accessing Finnhub financial data:
- get_quote: Real-time stock quotes
- get_market_news: Latest market news by category
- get_company_news: Company-specific news
Usage:
python mcp_server.py
Environment Variables:
FINNHUB_API_KEY: Your Finnhub API key (required)
"""
import os
import requests
from typing import Optional, Dict, Any
from datetime import datetime, timedelta
from mcp.server.fastmcp import FastMCP
# Initialize FastMCP server with standard MCP protocol
mcp = FastMCP("finnhub-market-info")
def get_api_key() -> Optional[str]:
"""Get the Finnhub API key from environment"""
return os.getenv("FINNHUB_API_KEY")
def make_finnhub_request(endpoint: str, params: Dict[str, Any]) -> Dict[str, Any]:
"""
Make a request to Finnhub API
Args:
endpoint: API endpoint path
params: Query parameters
Returns:
API response as dictionary
"""
api_key = get_api_key()
if not api_key:
return {"error": "API key not configured. Please set FINNHUB_API_KEY environment variable."}
params["token"] = api_key
base_url = "https://finnhub.io/api/v1"
url = f"{base_url}/{endpoint}"
try:
response = requests.get(url, params=params, timeout=10)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
return {"error": f"API request failed: {str(e)}"}
@mcp.tool()
def get_quote(symbol: str) -> dict:
"""
Get real-time quote data for US stocks. Use this tool when you need current stock price
information and market performance metrics for any US-listed stock.
When to use:
- User asks "What's the current price of [stock]?"
- Need real-time stock quote data
- User mentions "stock price", "current value", "how is [stock] trading?"
- Want to check latest market price and daily changes
Examples:
- "What's Apple's stock price?" β get_quote(symbol="AAPL")
- "How is Tesla trading today?" β get_quote(symbol="TSLA")
- "Show me Microsoft's current quote" β get_quote(symbol="MSFT")
Args:
symbol: Stock ticker symbol (e.g., 'AAPL', 'MSFT', 'TSLA', 'GOOGL')
Returns:
dict: Real-time quote data containing:
- symbol: Stock ticker symbol
- current_price: Current trading price (c)
- change: Price change in dollars (d)
- percent_change: Price change in percentage (dp)
- high: Today's high price (h)
- low: Today's low price (l)
- open: Opening price (o)
- previous_close: Previous trading day's closing price (pc)
- timestamp: Quote timestamp
"""
result = make_finnhub_request("quote", {"symbol": symbol.upper()})
if "error" in result:
return {
"error": result["error"],
"symbol": symbol.upper()
}
# Return structured data
return {
"symbol": symbol.upper(),
"current_price": result.get('c'),
"change": result.get('d'),
"percent_change": result.get('dp'),
"high": result.get('h'),
"low": result.get('l'),
"open": result.get('o'),
"previous_close": result.get('pc'),
"timestamp": datetime.fromtimestamp(result.get('t', 0)).strftime('%Y-%m-%d %H:%M:%S') if result.get('t') else None
}
@mcp.tool()
def get_market_news(category: str = "general", min_id: int = 0) -> dict:
"""
Get latest market news across different categories. Use this tool when you need current market
news, trends, and developments in general markets, forex, cryptocurrency, or mergers.
When to use:
- User asks "What's the latest market news?"
- Need current financial news and market updates
- User mentions "news", "market trends", "what's happening in the market?"
- Want to get news for specific categories (forex, crypto, M&A)
Categories explained:
- general: General market news, stocks, economy, major companies
- forex: Foreign exchange and currency market news
- crypto: Cryptocurrency and blockchain news
- merger: Mergers & acquisitions, corporate deals
Examples:
- "What's the latest market news?" β get_market_news(category="general")
- "Show me crypto news" β get_market_news(category="crypto")
- "Any forex updates?" β get_market_news(category="forex")
- "Recent merger news" β get_market_news(category="merger")
Args:
category: News category - "general", "forex", "crypto", or "merger" (default: "general")
min_id: Minimum news ID for pagination (default: 0, use 0 to get latest news)
Returns:
dict: Market news data containing:
- category: News category requested
- total_articles: Total number of articles returned
- articles: List of news articles (max 10), each with:
* id: Article ID
* headline: News headline
* summary: Brief summary of the article
* source: News source
* url: Link to full article
* published: Publication timestamp
* image: Article image URL (if available)
"""
params = {"category": category}
if min_id > 0:
params["minId"] = min_id
result = make_finnhub_request("news", params)
if isinstance(result, dict) and "error" in result:
return {
"error": result["error"],
"category": category
}
if not result or len(result) == 0:
return {
"category": category,
"total_articles": 0,
"articles": [],
"message": "No news articles found for this category"
}
# Format the news articles
articles = []
for article in result[:10]: # Limit to 10 articles
articles.append({
"id": article.get('id'),
"headline": article.get('headline', 'No headline'),
"summary": article.get('summary', ''),
"source": article.get('source', 'Unknown'),
"url": article.get('url'),
"published": datetime.fromtimestamp(article.get('datetime', 0)).strftime('%Y-%m-%d %H:%M:%S') if article.get('datetime') else None,
"image": article.get('image')
})
return {
"category": category,
"total_articles": len(articles),
"articles": articles
}
@mcp.tool()
def get_company_news(symbol: str, from_date: Optional[str] = None, to_date: Optional[str] = None) -> dict:
"""
Get latest news for a specific company by stock symbol. This endpoint provides company-specific
news, press releases, and announcements. Only available for North American companies.
When to use:
- User asks about a specific company's news (e.g., "Apple news", "Tesla updates")
- Need company-specific announcements or press releases
- User mentions "[company name] news", "recent [company] developments"
- Want to filter news by date range
Date range tips:
- Default: Last 7 days if no dates specified
- Can go back up to several years
- Use YYYY-MM-DD format (e.g., "2024-01-01")
Examples:
- "What's the latest Apple news?" β get_company_news(symbol="AAPL")
- "Tesla news from last month" β get_company_news(symbol="TSLA", from_date="2024-10-01", to_date="2024-10-31")
- "Microsoft announcements this week" β get_company_news(symbol="MSFT")
- "Show me Amazon news from January 2024" β get_company_news(symbol="AMZN", from_date="2024-01-01", to_date="2024-01-31")
Args:
symbol: Company stock ticker symbol (e.g., 'AAPL', 'MSFT', 'TSLA', 'GOOGL')
Must be a North American (US/Canada) listed company
from_date: Start date in YYYY-MM-DD format (default: 7 days ago)
to_date: End date in YYYY-MM-DD format (default: today)
Returns:
dict: Company news data containing:
- symbol: Company stock symbol
- from_date: Start date of news range
- to_date: End date of news range
- total_articles: Total number of articles returned
- articles: List of news articles (max 10), each with:
* headline: News headline
* summary: Brief summary
* source: News source
* url: Link to full article
* published: Publication timestamp
* related: Related stock symbols (if any)
"""
# Set default date range if not provided
if not to_date:
to_date = datetime.now().strftime('%Y-%m-%d')
if not from_date:
from_date = (datetime.now() - timedelta(days=7)).strftime('%Y-%m-%d')
params = {
"symbol": symbol.upper(),
"from": from_date,
"to": to_date
}
result = make_finnhub_request("company-news", params)
if isinstance(result, dict) and "error" in result:
return {
"error": result["error"],
"symbol": symbol.upper(),
"from_date": from_date,
"to_date": to_date
}
if not result or len(result) == 0:
# Provide helpful suggestions based on the symbol
suggestion = "Try expanding the date range or check if the symbol is correct."
if symbol.upper() in ["BABA", "BIDU", "JD", "PDD", "NIO"]:
suggestion = "Note: Chinese ADRs may have limited news coverage. Try US companies like AAPL, MSFT, TSLA, or GOOGL for better results."
return {
"symbol": symbol.upper(),
"from_date": from_date,
"to_date": to_date,
"total_articles": 0,
"articles": [],
"message": f"No news found for {symbol.upper()} between {from_date} and {to_date}.",
"suggestion": suggestion,
"note": "Company news is only available for North American companies. Some companies may have limited news coverage during certain periods."
}
# Format the news articles
articles = []
for article in result[:10]: # Limit to 10 articles
articles.append({
"headline": article.get('headline', 'No headline'),
"summary": article.get('summary', ''),
"source": article.get('source', 'Unknown'),
"url": article.get('url'),
"published": datetime.fromtimestamp(article.get('datetime', 0)).strftime('%Y-%m-%d %H:%M:%S') if article.get('datetime') else None,
"related": article.get('related', '')
})
return {
"symbol": symbol.upper(),
"from_date": from_date,
"to_date": to_date,
"total_articles": len(articles),
"articles": articles
}
if __name__ == "__main__":
# Check for API key
api_key = get_api_key()
if not api_key:
print("β Error: FINNHUB_API_KEY environment variable is not set")
print("Please set your Finnhub API key:")
print(" export FINNHUB_API_KEY='your_api_key_here'")
exit(1)
print("β
API Key loaded from environment variable")
print("βΆοΈ Starting Finnhub MCP Server (JSON-RPC over SSE)...")
print("π‘ Server name: finnhub-market-info")
print("π§ Protocol: Model Context Protocol (MCP)")
print("π Transport: SSE (Server-Sent Events)")
print("")
print("Available tools:")
print(" β’ get_quote(symbol)")
print(" β’ get_market_news(category, min_id)")
print(" β’ get_company_news(symbol, from_date, to_date)")
print("")
# Run the MCP server with SSE transport
mcp.run(transport="sse")
|