File size: 6,964 Bytes
2987b65
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""

MCP Server for SEC EDGAR Financial Data - FastMCP Implementation

Uses Anthropic official FastMCP SDK for cleaner, more maintainable code

"""

from mcp.server.fastmcp import FastMCP
from edgar_client import EdgarDataClient
from financial_analyzer import FinancialAnalyzer

# Initialize EDGAR clients
edgar_client = EdgarDataClient(
    user_agent="Juntao Peng Financial Report Metrics App (jtyxabc@gmail.com)"
)

financial_analyzer = FinancialAnalyzer(
    user_agent="Juntao Peng Financial Report Metrics App (jtyxabc@gmail.com)"
)

# Create FastMCP server with pure JSON response and stateless HTTP
mcp = FastMCP("sec-financial-data", json_response=True, stateless_http=True)


@mcp.tool()
def search_company(company_name: str) -> dict:
    """

    Search for a company by name in SEC EDGAR database.

    

    Args:

        company_name: Company name to search (e.g., Microsoft, Apple, Tesla)

    

    Returns:

        dict: Company information including CIK, name, and ticker symbol

    """
    result = edgar_client.search_company_by_name(company_name)
    if result:
        return result
    else:
        return {"error": f"No company found with name: {company_name}"}


@mcp.tool()
def get_company_info(cik: str) -> dict:
    """

    Get detailed company information including name, tickers, SIC code, and industry description.

    

    Args:

        cik: Company CIK code (10-digit format, e.g., 0000789019)

    

    Returns:

        dict: Company information

    """
    result = edgar_client.get_company_info(cik)
    if result:
        return result
    else:
        return {"error": f"No company found with CIK: {cik}"}


@mcp.tool()
def get_company_filings(cik: str, form_types: list[str] | None = None) -> dict:
    """

    Get list of company SEC filings (10-K, 10-Q, 20-F, etc.) with filing dates and document links.

    

    Args:

        cik: Company CIK code

        form_types: Optional filter by form types (e.g., [10-K, 10-Q])

    

    Returns:

        dict: Filings list with total count and limited results

    """
    result = edgar_client.get_company_filings(cik, form_types)
    if result:
        limited_result = result[:20]
        return {
            "total": len(result),
            "returned": len(limited_result),
            "filings": limited_result
        }
    else:
        return {"error": f"No filings found for CIK: {cik}"}


@mcp.tool()
def get_financial_data(cik: str, period: str) -> dict:
    """

    Get financial data for a specific period including revenue, net income, EPS, operating expenses, and cash flow.

    

    Args:

        cik: Company CIK code

        period: Period in format YYYY for annual or YYYYQX for quarterly (e.g., 2024, 2024Q3)

    

    Returns:

        dict: Financial data for the specified period

    """
    result = edgar_client.get_financial_data_for_period(cik, period)
    if result and "period" in result:
        return result
    else:
        return {"error": f"No financial data found for CIK: {cik}, Period: {period}"}


@mcp.tool()
def extract_financial_metrics(cik: str, years: int = 3) -> dict:
    """

    Extract comprehensive financial metrics for multiple years including both annual and quarterly data.

    Returns data in chronological order (newest first): FY -> Q4 -> Q3 -> Q2 -> Q1.

    

    Args:

        cik: Company CIK code

        years: Number of recent years to extract (1-10, default: 3)

    

    Returns:

        dict: Financial metrics with periods and data

    """
    if years < 1 or years > 10:
        return {"error": "Years parameter must be between 1 and 10"}
    
    # Check if company has filings (use tuple for caching)
    filings_10k = edgar_client.get_company_filings(cik, ('"10-K"',))
    filings_20f = edgar_client.get_company_filings(cik, ('"20-F"',))
    total_filings = len(filings_10k) + len(filings_20f)
    
    if total_filings == 0:
        return {
            "error": f"No annual filings found for CIK: {cik}",
            "suggestion": "Please check if the CIK is correct"
        }
    
    # Extract metrics
    metrics = financial_analyzer.extract_financial_metrics(cik, years)
    
    if metrics:
        formatted = financial_analyzer.format_financial_data(metrics)
        return {
            "periods": len(formatted),
            "data": formatted
        }
    else:
        # Return debug info
        debug_info = {
            "error": f"No financial metrics extracted for CIK: {cik}",
            "debug": {
                "cik": cik,
                "years_requested": years,
                "filings_found": {
                    "10-K": len(filings_10k),
                    "20-F": len(filings_20f)
                },
                "latest_filings": []
            },
            "suggestion": "Try using get_latest_financial_data or get_financial_data with a specific period"
        }
        
        # Add latest filing dates
        all_filings = filings_10k + filings_20f
        for filing in all_filings[:5]:
            debug_info["debug"]["latest_filings"].append({
                "form": filing.get("form_type"),
                "date": filing.get("filing_date")
            })
        
        return debug_info


@mcp.tool()
def get_latest_financial_data(cik: str) -> dict:
    """

    Get the most recent financial data available for a company.

    

    Args:

        cik: Company CIK code

    

    Returns:

        dict: Latest financial data

    """
    result = financial_analyzer.get_latest_financial_data(cik)
    if result and "period" in result:
        return result
    else:
        return {"error": f"No latest financial data found for CIK: {cik}"}


@mcp.tool()
def advanced_search_company(company_input: str) -> dict:
    """

    Advanced search supporting both company name and CIK code. Automatically detects input type.

    

    Args:

        company_input: Company name, ticker, or CIK code

    

    Returns:

        dict: Company information

    """
    result = financial_analyzer.search_company(company_input)
    if result.get("error"):
        return {"error": result["error"]}
    return result


# For production deployment
if __name__ == "__main__":
    import os
    
    # Set port from environment (HF Space sets PORT=7860)
    port = int(os.getenv("PORT", "7860"))
    host = os.getenv("HOST", "0.0.0.0")
    
    # Monkeypatch uvicorn.Config to use our port
    import uvicorn
    original_config_init = uvicorn.Config.__init__
    
    def patched_init(self, *args, **kwargs):
        kwargs['host'] = host
        kwargs['port'] = port
        return original_config_init(self, *args, **kwargs)
    
    uvicorn.Config.__init__ = patched_init
    
    # Run FastMCP with HTTP transport (stateless mode)
    mcp.run(transport="http")