EasyReportDataMCP / mcp_server.py
JC321's picture
Upload mcp_server.py
b890771 verified
raw
history blame
35.5 kB
"""
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)
@app.exception_handler(RequestValidationError)
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
@app.get("/", response_class=HTMLResponse)
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": "&lt;from_step_1&gt;"}</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": "&lt;from_step_1&gt;", "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": "&lt;from_step_1&gt;", "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
@app.get("/api")
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"
}
}
@app.get("/health")
async def health_check():
"""Health check endpoint"""
return {"status": "healthy", "service": "SEC Financial Report MCP Server"}
@app.post("/api/search_company", response_model=CompanySearchResponse)
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))
@app.post("/api/get_company_info", response_model=CompanyInfoResponse)
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))
@app.post("/api/get_company_filings", response_model=CompanyFilingsResponse)
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))
@app.post("/api/get_company_facts", response_model=CompanyFactsResponse)
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))
@app.post("/api/get_financial_data", response_model=FinancialDataResponse)
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))
@app.post("/api/advanced_search", response_model=CompanyInfoResponse)
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))
@app.post("/api/extract_financial_metrics", response_model=MetricsListResponse)
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))
@app.post("/api/get_latest_financial_data", response_model=FinancialDataResponse)
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
)