Spaces:
Runtime error
Runtime error
| """ | |
| MCP Server for SEC Financial Report Data Query | |
| Based on FastAPI framework | |
| """ | |
| from fastapi import FastAPI, HTTPException, Request | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from fastapi.responses import HTMLResponse, JSONResponse | |
| from fastapi.staticfiles import StaticFiles | |
| from fastapi.exceptions import RequestValidationError | |
| from pydantic import BaseModel, Field, ValidationError | |
| from typing import Optional, List, Dict, Any | |
| from edgar_client import EdgarDataClient | |
| from financial_analyzer import FinancialAnalyzer | |
| import uvicorn | |
| # Initialize FastAPI app | |
| app = FastAPI( | |
| title="SEC Financial Report MCP Server", | |
| description="MCP Server for querying SEC EDGAR financial data", | |
| version="1.0.0" | |
| ) | |
| # Configure CORS | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_credentials=True, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| # Initialize EDGAR client with user information | |
| edgar_client = EdgarDataClient( | |
| user_agent="Juntao Peng Financial Report Metrics App (jtyxabc@gmail.com)" | |
| ) | |
| # Initialize Financial Analyzer | |
| financial_analyzer = FinancialAnalyzer( | |
| user_agent="Juntao Peng Financial Report Metrics App (jtyxabc@gmail.com)" | |
| ) | |
| # Custom error handler for validation errors (422) | |
| async def validation_exception_handler(request: Request, exc: RequestValidationError): | |
| """ | |
| Custom handler for 422 Unprocessable Entity errors | |
| Provides user-friendly error messages | |
| """ | |
| errors = exc.errors() | |
| # Build friendly error message | |
| error_messages = [] | |
| for error in errors: | |
| field = " -> ".join(str(loc) for loc in error['loc']) | |
| msg = error['msg'] | |
| error_type = error['type'] | |
| if error_type == 'missing': | |
| error_messages.append(f"Missing required field: {field}") | |
| elif 'type_error' in error_type: | |
| error_messages.append(f"Invalid type for field '{field}': {msg}") | |
| else: | |
| error_messages.append(f"Validation error in '{field}': {msg}") | |
| return JSONResponse( | |
| status_code=422, | |
| content={ | |
| "error": "Request validation failed", | |
| "details": error_messages, | |
| "help": { | |
| "note": "Please ensure you send data in the request BODY (not URL parameters)", | |
| "example": { | |
| "cik": "1577552", | |
| "period": "2024" # if applicable | |
| }, | |
| "common_issues": [ | |
| "Don't put parameters in the URL query string", | |
| "Ensure CIK has no extra spaces", | |
| "Send Content-Type: application/json header", | |
| "Use POST method with JSON body" | |
| ] | |
| } | |
| } | |
| ) | |
| # Request/Response Models | |
| class UserAgentRequest(BaseModel): | |
| user_agent: str = Field( | |
| default="Financial Report Metrics App (your-email@example.com)", | |
| description="User agent string for identifying request source" | |
| ) | |
| class CompanySearchRequest(BaseModel): | |
| company_name: str = Field(..., description="Company name to search") | |
| class CompanyInfoRequest(BaseModel): | |
| cik: str = Field(..., description="Company CIK code") | |
| class Config: | |
| # Enable validation for strings | |
| str_strip_whitespace = True | |
| def __init__(self, **data): | |
| # Strip whitespace and clean CIK | |
| if 'cik' in data and data['cik']: | |
| data['cik'] = str(data['cik']).strip() | |
| super().__init__(**data) | |
| class CompanyFilingsRequest(BaseModel): | |
| cik: str = Field(..., description="Company CIK code") | |
| form_types: Optional[List[str]] = Field( | |
| None, | |
| description="List of form types (e.g., ['10-K', '10-Q']), None for all types" | |
| ) | |
| class Config: | |
| # Enable validation for strings | |
| str_strip_whitespace = True | |
| def __init__(self, **data): | |
| # Strip whitespace and clean CIK | |
| if 'cik' in data and data['cik']: | |
| data['cik'] = str(data['cik']).strip() | |
| super().__init__(**data) | |
| class CompanyFactsRequest(BaseModel): | |
| cik: str = Field(..., description="Company CIK code") | |
| class Config: | |
| # Enable validation for strings | |
| str_strip_whitespace = True | |
| def __init__(self, **data): | |
| # Strip whitespace and clean CIK | |
| if 'cik' in data and data['cik']: | |
| data['cik'] = str(data['cik']).strip() | |
| super().__init__(**data) | |
| class FinancialDataRequest(BaseModel): | |
| cik: str = Field(..., description="Company CIK code") | |
| period: str = Field( | |
| ..., | |
| description="Period in format 'YYYY' or 'YYYYQX' (e.g., '2025' or '2025Q3')" | |
| ) | |
| class Config: | |
| # Enable validation for strings | |
| str_strip_whitespace = True | |
| def __init__(self, **data): | |
| # Strip whitespace and clean inputs | |
| if 'cik' in data and data['cik']: | |
| data['cik'] = str(data['cik']).strip() | |
| if 'period' in data and data['period']: | |
| data['period'] = str(data['period']).strip() | |
| super().__init__(**data) | |
| class AdvancedSearchRequest(BaseModel): | |
| company_input: str = Field(..., description="Company name or CIK code") | |
| class ExtractMetricsRequest(BaseModel): | |
| cik: str = Field(..., description="Company CIK code") | |
| years: int = Field(default=3, description="Number of years to extract", ge=1, le=10) | |
| class Config: | |
| # Enable validation for strings | |
| str_strip_whitespace = True | |
| def __init__(self, **data): | |
| # Strip whitespace and clean CIK | |
| if 'cik' in data and data['cik']: | |
| data['cik'] = str(data['cik']).strip() | |
| super().__init__(**data) | |
| class LatestDataRequest(BaseModel): | |
| cik: str = Field(..., description="Company CIK code") | |
| class Config: | |
| # Enable validation for strings | |
| str_strip_whitespace = True | |
| def __init__(self, **data): | |
| # Strip whitespace and clean CIK | |
| if 'cik' in data and data['cik']: | |
| data['cik'] = str(data['cik']).strip() | |
| super().__init__(**data) | |
| class CompanySearchResponse(BaseModel): | |
| cik: Optional[str] = None | |
| name: Optional[str] = None | |
| ticker: Optional[str] = None | |
| error: Optional[str] = None | |
| class CompanyInfoResponse(BaseModel): | |
| cik: Optional[str] = None | |
| name: Optional[str] = None | |
| tickers: Optional[List[str]] = None | |
| sic: Optional[str] = None | |
| sic_description: Optional[str] = None | |
| error: Optional[str] = None | |
| class FilingItem(BaseModel): | |
| form_type: str | |
| filing_date: str | |
| accession_number: str | |
| primary_document: str | |
| class CompanyFilingsResponse(BaseModel): | |
| filings: List[FilingItem] = [] | |
| error: Optional[str] = None | |
| class CompanyFactsResponse(BaseModel): | |
| facts: Dict[str, Any] = {} | |
| error: Optional[str] = None | |
| class FinancialDataResponse(BaseModel): | |
| period: Optional[str] = None | |
| total_revenue: Optional[float] = None | |
| net_income: Optional[float] = None | |
| earnings_per_share: Optional[float] = None | |
| operating_expenses: Optional[float] = None | |
| operating_cash_flow: Optional[float] = None | |
| source_url: Optional[str] = None | |
| source_form: Optional[str] = None | |
| data_source: Optional[str] = None | |
| additional_details: Optional[Dict[str, Any]] = None | |
| error: Optional[str] = None | |
| class MetricsListResponse(BaseModel): | |
| metrics: List[Dict[str, Any]] = [] | |
| count: int = 0 | |
| error: Optional[str] = None | |
| # API Endpoints | |
| async def root(): | |
| """Root endpoint with beautiful HTML interface""" | |
| html_content = """ | |
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>SEC Financial Report MCP Server</title> | |
| <style> | |
| body { | |
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; | |
| line-height: 1.6; | |
| color: #333; | |
| max-width: 1200px; | |
| margin: 0 auto; | |
| padding: 20px; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| min-height: 100vh; | |
| } | |
| .container { | |
| background: white; | |
| border-radius: 12px; | |
| padding: 40px; | |
| box-shadow: 0 10px 30px rgba(0,0,0,0.1); | |
| } | |
| .header { | |
| text-align: center; | |
| margin-bottom: 40px; | |
| padding-bottom: 20px; | |
| border-bottom: 3px solid #667eea; | |
| } | |
| .header h1 { | |
| color: #667eea; | |
| margin: 0; | |
| font-size: 2.5em; | |
| font-weight: 700; | |
| } | |
| .status { | |
| display: inline-block; | |
| background: #10b981; | |
| color: white; | |
| padding: 8px 16px; | |
| border-radius: 20px; | |
| font-size: 0.9em; | |
| margin-top: 10px; | |
| } | |
| .quick-start { | |
| background: linear-gradient(135deg, #667eea, #764ba2); | |
| color: white; | |
| padding: 30px; | |
| border-radius: 10px; | |
| margin-bottom: 30px; | |
| } | |
| .quick-start h2 { | |
| margin-top: 0; | |
| font-size: 1.8em; | |
| } | |
| .steps { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); | |
| gap: 20px; | |
| margin-top: 20px; | |
| } | |
| .step { | |
| background: rgba(255,255,255,0.1); | |
| padding: 20px; | |
| border-radius: 8px; | |
| border: 1px solid rgba(255,255,255,0.2); | |
| } | |
| .step h3 { | |
| margin: 0 0 10px 0; | |
| font-size: 1.1em; | |
| } | |
| .code { | |
| background: rgba(0,0,0,0.1); | |
| padding: 10px; | |
| border-radius: 6px; | |
| font-family: 'Monaco', 'Consolas', monospace; | |
| font-size: 0.9em; | |
| overflow-x: auto; | |
| } | |
| .endpoints-grid { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 30px; | |
| margin-bottom: 30px; | |
| } | |
| .endpoint-section { | |
| border: 2px solid #e5e7eb; | |
| border-radius: 10px; | |
| padding: 25px; | |
| background: #f9fafb; | |
| } | |
| .endpoint-section.advanced { | |
| border-color: #667eea; | |
| background: #f0f4ff; | |
| } | |
| .endpoint-section h3 { | |
| margin-top: 0; | |
| color: #667eea; | |
| font-size: 1.4em; | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| } | |
| .recommended { | |
| background: #fef3c7; | |
| color: #92400e; | |
| padding: 4px 8px; | |
| border-radius: 12px; | |
| font-size: 0.75em; | |
| font-weight: 600; | |
| } | |
| .endpoint { | |
| margin-bottom: 15px; | |
| padding: 15px; | |
| background: white; | |
| border-radius: 6px; | |
| border-left: 4px solid #10b981; | |
| } | |
| .endpoint-path { | |
| font-family: 'Monaco', 'Consolas', monospace; | |
| color: #059669; | |
| font-weight: 600; | |
| font-size: 0.9em; | |
| } | |
| .endpoint-desc { | |
| color: #6b7280; | |
| margin-top: 5px; | |
| font-size: 0.9em; | |
| } | |
| .workflows { | |
| background: #f8fafc; | |
| padding: 30px; | |
| border-radius: 10px; | |
| margin-top: 30px; | |
| } | |
| .workflow { | |
| margin-bottom: 25px; | |
| padding: 20px; | |
| background: white; | |
| border-radius: 8px; | |
| border-left: 4px solid #8b5cf6; | |
| } | |
| .workflow h4 { | |
| margin: 0 0 10px 0; | |
| color: #8b5cf6; | |
| font-size: 1.1em; | |
| } | |
| .workflow-step { | |
| background: #f3f4f6; | |
| padding: 10px; | |
| margin: 5px 0; | |
| border-radius: 4px; | |
| font-family: 'Monaco', 'Consolas', monospace; | |
| font-size: 0.85em; | |
| } | |
| .links { | |
| text-align: center; | |
| margin-top: 40px; | |
| padding-top: 20px; | |
| border-top: 2px solid #e5e7eb; | |
| } | |
| .link-btn { | |
| display: inline-block; | |
| background: #667eea; | |
| color: white; | |
| padding: 12px 24px; | |
| margin: 0 10px; | |
| text-decoration: none; | |
| border-radius: 6px; | |
| font-weight: 600; | |
| transition: background-color 0.3s; | |
| } | |
| .link-btn:hover { | |
| background: #5a67d8; | |
| } | |
| .link-btn.secondary { | |
| background: #6b7280; | |
| } | |
| .link-btn.secondary:hover { | |
| background: #4b5563; | |
| } | |
| @media (max-width: 768px) { | |
| .endpoints-grid { | |
| grid-template-columns: 1fr; | |
| } | |
| .steps { | |
| grid-template-columns: 1fr; | |
| } | |
| .container { | |
| padding: 20px; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <div class="header"> | |
| <h1>📊 SEC Financial Report MCP Server</h1> | |
| <div class="status">✅ Operational</div> | |
| <p>MCP Server for querying SEC EDGAR financial data - A tool-ready API for AI agents and applications</p> | |
| </div> | |
| <div class="quick-start"> | |
| <h2>🚀 Quick Start</h2> | |
| <div class="steps"> | |
| <div class="step"> | |
| <h3>Step 1: Search Company</h3> | |
| <div class="code">POST /api/advanced_search<br>{"company_input": "NVIDIA"}</div> | |
| </div> | |
| <div class="step"> | |
| <h3>Step 2: Get Latest Data</h3> | |
| <div class="code">POST /api/get_latest_financial_data<br>{"cik": "0001045810"}</div> | |
| </div> | |
| <div class="step"> | |
| <h3>Step 3: Analyze Trends</h3> | |
| <div class="code">POST /api/extract_financial_metrics<br>{"cik": "0001045810", "years": 3}</div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="endpoints-grid"> | |
| <div class="endpoint-section"> | |
| <h3>🔧 Basic Tools</h3> | |
| <div class="endpoint"> | |
| <div class="endpoint-path">POST /api/search_company</div> | |
| <div class="endpoint-desc">Search company by name only</div> | |
| </div> | |
| <div class="endpoint"> | |
| <div class="endpoint-path">POST /api/get_company_info</div> | |
| <div class="endpoint-desc">Get company information by CIK</div> | |
| </div> | |
| <div class="endpoint"> | |
| <div class="endpoint-path">POST /api/get_company_filings</div> | |
| <div class="endpoint-desc">Get company SEC filings (10-K, 10-Q, 20-F)</div> | |
| </div> | |
| <div class="endpoint"> | |
| <div class="endpoint-path">POST /api/get_company_facts</div> | |
| <div class="endpoint-desc">Get complete XBRL financial facts</div> | |
| </div> | |
| <div class="endpoint"> | |
| <div class="endpoint-path">POST /api/get_financial_data</div> | |
| <div class="endpoint-desc">Get financial data for specific period</div> | |
| </div> | |
| <div class="endpoint"> | |
| <div class="endpoint-path">GET /health</div> | |
| <div class="endpoint-desc">Server health check</div> | |
| </div> | |
| </div> | |
| <div class="endpoint-section advanced"> | |
| <h3>⭐ Advanced Tools <span class="recommended">RECOMMENDED</span></h3> | |
| <div class="endpoint"> | |
| <div class="endpoint-path">POST /api/advanced_search</div> | |
| <div class="endpoint-desc">Smart search - accepts company name OR CIK</div> | |
| </div> | |
| <div class="endpoint"> | |
| <div class="endpoint-path">POST /api/extract_financial_metrics</div> | |
| <div class="endpoint-desc">Extract multi-year metrics (annual + quarterly)</div> | |
| </div> | |
| <div class="endpoint"> | |
| <div class="endpoint-path">POST /api/get_latest_financial_data</div> | |
| <div class="endpoint-desc">Get most recent annual financial data automatically</div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="workflows"> | |
| <h3>📋 Example Workflows</h3> | |
| <div class="workflow"> | |
| <h4>Get Latest Company Financials</h4> | |
| <div class="workflow-step">1. POST /api/advanced_search {"company_input": "Apple"}</div> | |
| <div class="workflow-step">2. POST /api/get_latest_financial_data {"cik": "<from_step_1>"}</div> | |
| </div> | |
| <div class="workflow"> | |
| <h4>Analyze 5-Year Trends</h4> | |
| <div class="workflow-step">1. POST /api/advanced_search {"company_input": "TSMC"}</div> | |
| <div class="workflow-step">2. POST /api/extract_financial_metrics {"cik": "<from_step_1>", "years": 5}</div> | |
| </div> | |
| <div class="workflow"> | |
| <h4>Get Specific Quarter Data</h4> | |
| <div class="workflow-step">1. POST /api/advanced_search {"company_input": "NVIDIA"}</div> | |
| <div class="workflow-step">2. POST /api/get_financial_data {"cik": "<from_step_1>", "period": "2024Q3"}</div> | |
| </div> | |
| </div> | |
| <div class="links"> | |
| <a href="/docs" class="link-btn">📚 Interactive API Docs</a> | |
| <a href="/redoc" class="link-btn">📖 ReDoc Documentation</a> | |
| <a href="/health" class="link-btn secondary">💚 Health Check</a> | |
| <a href="/api" class="link-btn secondary">🔗 JSON API Info</a> | |
| </div> | |
| <div style="text-align: center; margin-top: 30px; color: #6b7280; font-size: 0.9em;"> | |
| <p><strong>Data Source:</strong> US SEC EDGAR system (real-time) | <strong>Rate Limit:</strong> 10 requests/second</p> | |
| <p><strong>User Agent:</strong> Juntao Peng (jtyxabc@gmail.com) | <strong>Always-On:</strong> HF CPU Upgrade</p> | |
| </div> | |
| </div> | |
| </body> | |
| </html> | |
| """ | |
| return html_content | |
| async def api_info(): | |
| """JSON API information for programmatic access""" | |
| return { | |
| "service": "SEC Financial Report MCP Server", | |
| "version": "1.0.0", | |
| "description": "MCP Server for querying SEC EDGAR financial data - A tool-ready API for AI agents and applications", | |
| "status": "operational", | |
| "user_agent": "Juntao Peng (jtyxabc@gmail.com)", | |
| "quick_start": { | |
| "interactive_docs": "/docs", | |
| "api_documentation": "/redoc", | |
| "health_check": "/health", | |
| "example_usage": { | |
| "step_1": "Search company: POST /api/advanced_search with {\"company_input\": \"NVIDIA\"}", | |
| "step_2": "Get latest data: POST /api/get_latest_financial_data with {\"cik\": \"0001045810\"}", | |
| "step_3": "Get trends: POST /api/extract_financial_metrics with {\"cik\": \"0001045810\", \"years\": 3}" | |
| } | |
| }, | |
| "endpoints": { | |
| "basic_tools": { | |
| "search_company": { | |
| "path": "/api/search_company", | |
| "method": "POST", | |
| "description": "Search company by name only", | |
| "input": {"company_name": "string"} | |
| }, | |
| "get_company_info": { | |
| "path": "/api/get_company_info", | |
| "method": "POST", | |
| "description": "Get company information by CIK", | |
| "input": {"cik": "string"} | |
| }, | |
| "get_company_filings": { | |
| "path": "/api/get_company_filings", | |
| "method": "POST", | |
| "description": "Get company SEC filings (10-K, 10-Q, 20-F)", | |
| "input": {"cik": "string", "form_types": "array or null"} | |
| }, | |
| "get_company_facts": { | |
| "path": "/api/get_company_facts", | |
| "method": "POST", | |
| "description": "Get complete XBRL financial facts", | |
| "input": {"cik": "string"} | |
| }, | |
| "get_financial_data": { | |
| "path": "/api/get_financial_data", | |
| "method": "POST", | |
| "description": "Get financial data for specific period", | |
| "input": {"cik": "string", "period": "YYYY or YYYYQX"} | |
| }, | |
| "health": { | |
| "path": "/health", | |
| "method": "GET", | |
| "description": "Server health check" | |
| } | |
| }, | |
| "advanced_tools": { | |
| "advanced_search": { | |
| "path": "/api/advanced_search", | |
| "method": "POST", | |
| "description": "Smart search - accepts company name OR CIK", | |
| "input": {"company_input": "string"}, | |
| "recommended": True | |
| }, | |
| "extract_financial_metrics": { | |
| "path": "/api/extract_financial_metrics", | |
| "method": "POST", | |
| "description": "Extract multi-year metrics (annual + quarterly)", | |
| "input": {"cik": "string", "years": "integer (1-10)"}, | |
| "recommended": True | |
| }, | |
| "get_latest_financial_data": { | |
| "path": "/api/get_latest_financial_data", | |
| "method": "POST", | |
| "description": "Get most recent annual financial data automatically", | |
| "input": {"cik": "string"}, | |
| "recommended": True | |
| } | |
| } | |
| }, | |
| "usage_guide": { | |
| "for_ai_agents": "Use advanced_search → get_latest_financial_data for quick queries. Use extract_financial_metrics for trend analysis.", | |
| "for_developers": "Visit /docs for interactive API testing with Swagger UI", | |
| "data_source": "US SEC EDGAR system (real-time)", | |
| "supported_forms": ["10-K (annual)", "10-Q (quarterly)", "20-F (foreign annual)"], | |
| "rate_limit": "Follow SEC guidelines: 10 requests/second" | |
| }, | |
| "example_workflows": [ | |
| { | |
| "name": "Get Latest Company Financials", | |
| "steps": [ | |
| "POST /api/advanced_search {\"company_input\": \"Apple\"}", | |
| "POST /api/get_latest_financial_data {\"cik\": \"<from_step_1>\"}" | |
| ] | |
| }, | |
| { | |
| "name": "Analyze 5-Year Trends", | |
| "steps": [ | |
| "POST /api/advanced_search {\"company_input\": \"TSMC\"}", | |
| "POST /api/extract_financial_metrics {\"cik\": \"<from_step_1>\", \"years\": 5}" | |
| ] | |
| }, | |
| { | |
| "name": "Get Specific Quarter Data", | |
| "steps": [ | |
| "POST /api/advanced_search {\"company_input\": \"NVIDIA\"}", | |
| "POST /api/get_financial_data {\"cik\": \"<from_step_1>\", \"period\": \"2024Q3\"}" | |
| ] | |
| } | |
| ], | |
| "links": { | |
| "interactive_docs": "/docs", | |
| "redoc": "/redoc", | |
| "github": "https://github.com/JC321/EasyReportDateMCP", | |
| "huggingface_space": "https://huggingface.co/spaces/JC321/EasyReportDateMCP" | |
| } | |
| } | |
| async def health_check(): | |
| """Health check endpoint""" | |
| return {"status": "healthy", "service": "SEC Financial Report MCP Server"} | |
| async def search_company_by_name(request: CompanySearchRequest): | |
| """ | |
| Search company CIK by company name | |
| Args: | |
| company_name (str): Company name | |
| Returns: | |
| dict: Dictionary containing company information | |
| """ | |
| try: | |
| result = edgar_client.search_company_by_name(request.company_name) | |
| if result is None: | |
| return CompanySearchResponse( | |
| error=f"No company found matching '{request.company_name}'" | |
| ) | |
| return CompanySearchResponse( | |
| cik=result.get("cik"), | |
| name=result.get("name"), | |
| ticker=result.get("ticker") | |
| ) | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| async def get_company_info(request: CompanyInfoRequest): | |
| """ | |
| Get basic company information | |
| Args: | |
| cik (str): Company CIK code | |
| Returns: | |
| dict: Dictionary containing company information | |
| """ | |
| try: | |
| result = edgar_client.get_company_info(request.cik) | |
| if result is None: | |
| return CompanyInfoResponse( | |
| error=f"No company information found for CIK: {request.cik}" | |
| ) | |
| return CompanyInfoResponse( | |
| cik=result.get("cik"), | |
| name=result.get("name"), | |
| tickers=result.get("tickers"), | |
| sic=result.get("sic"), | |
| sic_description=result.get("sic_description") | |
| ) | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| async def get_company_filings(request: CompanyFilingsRequest): | |
| """ | |
| Get all company filing documents | |
| Args: | |
| cik (str): Company CIK code | |
| form_types (list): List of form types (e.g., ['10-K', '10-Q']), None for all types | |
| Returns: | |
| list: List of filing documents | |
| """ | |
| try: | |
| result = edgar_client.get_company_filings(request.cik, request.form_types) | |
| if not result: | |
| return CompanyFilingsResponse( | |
| filings=[], | |
| error=f"No filings found for CIK: {request.cik}" | |
| ) | |
| filings = [ | |
| FilingItem( | |
| form_type=filing.get("form_type", ""), | |
| filing_date=filing.get("filing_date", ""), | |
| accession_number=filing.get("accession_number", ""), | |
| primary_document=filing.get("primary_document", "") | |
| ) | |
| for filing in result | |
| ] | |
| return CompanyFilingsResponse(filings=filings) | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| async def get_company_facts(request: CompanyFactsRequest): | |
| """ | |
| Get all company financial facts data | |
| Args: | |
| cik (str): Company CIK code | |
| Returns: | |
| dict: Company financial facts data | |
| """ | |
| try: | |
| result = edgar_client.get_company_facts(request.cik) | |
| if not result: | |
| return CompanyFactsResponse( | |
| facts={}, | |
| error=f"No financial facts found for CIK: {request.cik}" | |
| ) | |
| return CompanyFactsResponse(facts=result) | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| async def get_financial_data_for_period(request: FinancialDataRequest): | |
| """ | |
| Get financial data for a specific period (supports annual and quarterly) | |
| Args: | |
| cik (str): Company CIK code | |
| period (str): Period in format 'YYYY' or 'YYYYQX' (e.g., '2025' or '2025Q3') | |
| Returns: | |
| dict: Financial data dictionary | |
| """ | |
| try: | |
| result = edgar_client.get_financial_data_for_period(request.cik, request.period) | |
| if not result or "period" not in result: | |
| return FinancialDataResponse( | |
| error=f"No financial data found for CIK: {request.cik}, Period: {request.period}" | |
| ) | |
| # Collect additional details | |
| additional_details = {} | |
| for key, value in result.items(): | |
| if key.endswith("_details"): | |
| additional_details[key] = value | |
| return FinancialDataResponse( | |
| period=result.get("period"), | |
| total_revenue=result.get("total_revenue"), | |
| net_income=result.get("net_income"), | |
| earnings_per_share=result.get("earnings_per_share"), | |
| operating_expenses=result.get("operating_expenses"), | |
| operating_cash_flow=result.get("operating_cash_flow"), | |
| source_url=result.get("source_url"), | |
| source_form=result.get("source_form"), | |
| data_source=result.get("data_source"), | |
| additional_details=additional_details if additional_details else None | |
| ) | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| async def advanced_search_company(request: AdvancedSearchRequest): | |
| """ | |
| Advanced company search (supports both company name and CIK) | |
| Uses FinancialAnalyzer.search_company() method | |
| Args: | |
| company_input (str): Company name or CIK code | |
| Returns: | |
| dict: Company information | |
| """ | |
| try: | |
| result = financial_analyzer.search_company(request.company_input) | |
| if result.get("error"): | |
| return CompanyInfoResponse(error=result["error"]) | |
| return CompanyInfoResponse( | |
| cik=result.get("cik"), | |
| name=result.get("name"), | |
| tickers=result.get("tickers"), | |
| sic=result.get("sic"), | |
| sic_description=result.get("sic_description") | |
| ) | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| async def extract_financial_metrics(request: ExtractMetricsRequest): | |
| """ | |
| Extract financial metrics for multiple years (annual and quarterly) | |
| Uses FinancialAnalyzer.extract_financial_metrics() method | |
| Args: | |
| cik (str): Company CIK code | |
| years (int): Number of years to extract (default: 3, max: 10) | |
| Returns: | |
| dict: List of financial metrics for multiple periods | |
| """ | |
| try: | |
| metrics = financial_analyzer.extract_financial_metrics(request.cik, request.years) | |
| if not metrics: | |
| return MetricsListResponse( | |
| metrics=[], | |
| count=0, | |
| error=f"No financial metrics found for CIK: {request.cik}" | |
| ) | |
| # Format the metrics | |
| formatted_metrics = financial_analyzer.format_financial_data(metrics) | |
| return MetricsListResponse( | |
| metrics=formatted_metrics, | |
| count=len(formatted_metrics) | |
| ) | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| async def get_latest_financial_data(request: LatestDataRequest): | |
| """ | |
| Get the latest financial data for a company | |
| Uses FinancialAnalyzer.get_latest_financial_data() method | |
| Args: | |
| cik (str): Company CIK code | |
| Returns: | |
| dict: Latest financial data | |
| """ | |
| try: | |
| result = financial_analyzer.get_latest_financial_data(request.cik) | |
| if not result or "period" not in result: | |
| return FinancialDataResponse( | |
| error=f"No latest financial data found for CIK: {request.cik}" | |
| ) | |
| # Collect additional details | |
| additional_details = {} | |
| for key, value in result.items(): | |
| if key.endswith("_details"): | |
| additional_details[key] = value | |
| return FinancialDataResponse( | |
| period=result.get("period"), | |
| total_revenue=result.get("total_revenue"), | |
| net_income=result.get("net_income"), | |
| earnings_per_share=result.get("earnings_per_share"), | |
| operating_expenses=result.get("operating_expenses"), | |
| operating_cash_flow=result.get("operating_cash_flow"), | |
| source_url=result.get("source_url"), | |
| source_form=result.get("source_form"), | |
| data_source=result.get("data_source"), | |
| additional_details=additional_details if additional_details else None | |
| ) | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| if __name__ == "__main__": | |
| # Run the server | |
| uvicorn.run( | |
| "mcp_server:app", | |
| host="0.0.0.0", | |
| port=7860, | |
| reload=True | |
| ) | |