JC321 commited on
Commit
2987b65
·
verified ·
1 Parent(s): 7ecbf24

Upload 2 files

Browse files
Files changed (1) hide show
  1. mcp_server_fastmcp.py +220 -0
mcp_server_fastmcp.py ADDED
@@ -0,0 +1,220 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ MCP Server for SEC EDGAR Financial Data - FastMCP Implementation
3
+ Uses Anthropic official FastMCP SDK for cleaner, more maintainable code
4
+ """
5
+
6
+ from mcp.server.fastmcp import FastMCP
7
+ from edgar_client import EdgarDataClient
8
+ from financial_analyzer import FinancialAnalyzer
9
+
10
+ # Initialize EDGAR clients
11
+ edgar_client = EdgarDataClient(
12
+ user_agent="Juntao Peng Financial Report Metrics App (jtyxabc@gmail.com)"
13
+ )
14
+
15
+ financial_analyzer = FinancialAnalyzer(
16
+ user_agent="Juntao Peng Financial Report Metrics App (jtyxabc@gmail.com)"
17
+ )
18
+
19
+ # Create FastMCP server with pure JSON response and stateless HTTP
20
+ mcp = FastMCP("sec-financial-data", json_response=True, stateless_http=True)
21
+
22
+
23
+ @mcp.tool()
24
+ def search_company(company_name: str) -> dict:
25
+ """
26
+ Search for a company by name in SEC EDGAR database.
27
+
28
+ Args:
29
+ company_name: Company name to search (e.g., Microsoft, Apple, Tesla)
30
+
31
+ Returns:
32
+ dict: Company information including CIK, name, and ticker symbol
33
+ """
34
+ result = edgar_client.search_company_by_name(company_name)
35
+ if result:
36
+ return result
37
+ else:
38
+ return {"error": f"No company found with name: {company_name}"}
39
+
40
+
41
+ @mcp.tool()
42
+ def get_company_info(cik: str) -> dict:
43
+ """
44
+ Get detailed company information including name, tickers, SIC code, and industry description.
45
+
46
+ Args:
47
+ cik: Company CIK code (10-digit format, e.g., 0000789019)
48
+
49
+ Returns:
50
+ dict: Company information
51
+ """
52
+ result = edgar_client.get_company_info(cik)
53
+ if result:
54
+ return result
55
+ else:
56
+ return {"error": f"No company found with CIK: {cik}"}
57
+
58
+
59
+ @mcp.tool()
60
+ def get_company_filings(cik: str, form_types: list[str] | None = None) -> dict:
61
+ """
62
+ Get list of company SEC filings (10-K, 10-Q, 20-F, etc.) with filing dates and document links.
63
+
64
+ Args:
65
+ cik: Company CIK code
66
+ form_types: Optional filter by form types (e.g., [10-K, 10-Q])
67
+
68
+ Returns:
69
+ dict: Filings list with total count and limited results
70
+ """
71
+ result = edgar_client.get_company_filings(cik, form_types)
72
+ if result:
73
+ limited_result = result[:20]
74
+ return {
75
+ "total": len(result),
76
+ "returned": len(limited_result),
77
+ "filings": limited_result
78
+ }
79
+ else:
80
+ return {"error": f"No filings found for CIK: {cik}"}
81
+
82
+
83
+ @mcp.tool()
84
+ def get_financial_data(cik: str, period: str) -> dict:
85
+ """
86
+ Get financial data for a specific period including revenue, net income, EPS, operating expenses, and cash flow.
87
+
88
+ Args:
89
+ cik: Company CIK code
90
+ period: Period in format YYYY for annual or YYYYQX for quarterly (e.g., 2024, 2024Q3)
91
+
92
+ Returns:
93
+ dict: Financial data for the specified period
94
+ """
95
+ result = edgar_client.get_financial_data_for_period(cik, period)
96
+ if result and "period" in result:
97
+ return result
98
+ else:
99
+ return {"error": f"No financial data found for CIK: {cik}, Period: {period}"}
100
+
101
+
102
+ @mcp.tool()
103
+ def extract_financial_metrics(cik: str, years: int = 3) -> dict:
104
+ """
105
+ Extract comprehensive financial metrics for multiple years including both annual and quarterly data.
106
+ Returns data in chronological order (newest first): FY -> Q4 -> Q3 -> Q2 -> Q1.
107
+
108
+ Args:
109
+ cik: Company CIK code
110
+ years: Number of recent years to extract (1-10, default: 3)
111
+
112
+ Returns:
113
+ dict: Financial metrics with periods and data
114
+ """
115
+ if years < 1 or years > 10:
116
+ return {"error": "Years parameter must be between 1 and 10"}
117
+
118
+ # Check if company has filings (use tuple for caching)
119
+ filings_10k = edgar_client.get_company_filings(cik, ('"10-K"',))
120
+ filings_20f = edgar_client.get_company_filings(cik, ('"20-F"',))
121
+ total_filings = len(filings_10k) + len(filings_20f)
122
+
123
+ if total_filings == 0:
124
+ return {
125
+ "error": f"No annual filings found for CIK: {cik}",
126
+ "suggestion": "Please check if the CIK is correct"
127
+ }
128
+
129
+ # Extract metrics
130
+ metrics = financial_analyzer.extract_financial_metrics(cik, years)
131
+
132
+ if metrics:
133
+ formatted = financial_analyzer.format_financial_data(metrics)
134
+ return {
135
+ "periods": len(formatted),
136
+ "data": formatted
137
+ }
138
+ else:
139
+ # Return debug info
140
+ debug_info = {
141
+ "error": f"No financial metrics extracted for CIK: {cik}",
142
+ "debug": {
143
+ "cik": cik,
144
+ "years_requested": years,
145
+ "filings_found": {
146
+ "10-K": len(filings_10k),
147
+ "20-F": len(filings_20f)
148
+ },
149
+ "latest_filings": []
150
+ },
151
+ "suggestion": "Try using get_latest_financial_data or get_financial_data with a specific period"
152
+ }
153
+
154
+ # Add latest filing dates
155
+ all_filings = filings_10k + filings_20f
156
+ for filing in all_filings[:5]:
157
+ debug_info["debug"]["latest_filings"].append({
158
+ "form": filing.get("form_type"),
159
+ "date": filing.get("filing_date")
160
+ })
161
+
162
+ return debug_info
163
+
164
+
165
+ @mcp.tool()
166
+ def get_latest_financial_data(cik: str) -> dict:
167
+ """
168
+ Get the most recent financial data available for a company.
169
+
170
+ Args:
171
+ cik: Company CIK code
172
+
173
+ Returns:
174
+ dict: Latest financial data
175
+ """
176
+ result = financial_analyzer.get_latest_financial_data(cik)
177
+ if result and "period" in result:
178
+ return result
179
+ else:
180
+ return {"error": f"No latest financial data found for CIK: {cik}"}
181
+
182
+
183
+ @mcp.tool()
184
+ def advanced_search_company(company_input: str) -> dict:
185
+ """
186
+ Advanced search supporting both company name and CIK code. Automatically detects input type.
187
+
188
+ Args:
189
+ company_input: Company name, ticker, or CIK code
190
+
191
+ Returns:
192
+ dict: Company information
193
+ """
194
+ result = financial_analyzer.search_company(company_input)
195
+ if result.get("error"):
196
+ return {"error": result["error"]}
197
+ return result
198
+
199
+
200
+ # For production deployment
201
+ if __name__ == "__main__":
202
+ import os
203
+
204
+ # Set port from environment (HF Space sets PORT=7860)
205
+ port = int(os.getenv("PORT", "7860"))
206
+ host = os.getenv("HOST", "0.0.0.0")
207
+
208
+ # Monkeypatch uvicorn.Config to use our port
209
+ import uvicorn
210
+ original_config_init = uvicorn.Config.__init__
211
+
212
+ def patched_init(self, *args, **kwargs):
213
+ kwargs['host'] = host
214
+ kwargs['port'] = port
215
+ return original_config_init(self, *args, **kwargs)
216
+
217
+ uvicorn.Config.__init__ = patched_init
218
+
219
+ # Run FastMCP with HTTP transport (stateless mode)
220
+ mcp.run(transport="http")