JC321 commited on
Commit
b918cf3
·
verified ·
1 Parent(s): 73c5f65

Upload 3 files

Browse files
Files changed (3) hide show
  1. Dockerfile +3 -7
  2. app.py +187 -15
  3. requirements.txt +0 -1
Dockerfile CHANGED
@@ -1,4 +1,4 @@
1
- FROM python:3.10-slim
2
 
3
  WORKDIR /app
4
 
@@ -14,10 +14,6 @@ COPY edgar_client.py .
14
  COPY financial_analyzer.py .
15
  COPY mcp_server_fastmcp.py .
16
  COPY app.py .
17
- COPY start.sh .
18
-
19
- # Make start script executable
20
- RUN chmod +x start.sh
21
 
22
  # Expose port
23
  EXPOSE 7860
@@ -31,5 +27,5 @@ ENV HOST=0.0.0.0
31
  # Health check
32
  HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 CMD curl -f http://localhost:7860/ || exit 1
33
 
34
- # Run startup script
35
- CMD ["./start.sh"]
 
1
+ FROM python:3.10-slim
2
 
3
  WORKDIR /app
4
 
 
14
  COPY financial_analyzer.py .
15
  COPY mcp_server_fastmcp.py .
16
  COPY app.py .
 
 
 
 
17
 
18
  # Expose port
19
  EXPOSE 7860
 
27
  # Health check
28
  HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 CMD curl -f http://localhost:7860/ || exit 1
29
 
30
+ # Run FastAPI app with integrated MCP server
31
+ CMD ["python", "app.py"]
app.py CHANGED
@@ -1,12 +1,17 @@
1
  """
2
  Simple FastAPI web interface for MCP Server usage guide
3
- MCP server runs separately on port 8001
4
  """
5
  from fastapi import FastAPI, Request
6
- from fastapi.responses import HTMLResponse, JSONResponse
7
  from fastapi.middleware.cors import CORSMiddleware
8
  import uvicorn
9
- import httpx
 
 
 
 
 
10
 
11
  app = FastAPI(title="SEC Financial Data MCP Server")
12
 
@@ -19,22 +24,189 @@ app.add_middleware(
19
  allow_headers=["*"],
20
  )
21
 
22
- # Proxy to MCP server
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  @app.post("/sse")
24
- async def proxy_to_mcp(request: Request):
25
- """Proxy SSE requests to MCP server on port 8001"""
 
26
  try:
27
- body = await request.json()
28
- async with httpx.AsyncClient(timeout=30.0) as client:
29
- response = await client.post(
30
- "http://127.0.0.1:8001/sse",
31
- json=body,
32
- headers={"Content-Type": "application/json"}
33
- )
34
- return JSONResponse(content=response.json(), status_code=response.status_code)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  except Exception as e:
36
  return JSONResponse(
37
- content={"error": str(e), "type": "proxy_error"},
38
  status_code=500
39
  )
40
 
 
1
  """
2
  Simple FastAPI web interface for MCP Server usage guide
3
+ Integrated with MCP server directly
4
  """
5
  from fastapi import FastAPI, Request
6
+ from fastapi.responses import HTMLResponse, JSONResponse, Response
7
  from fastapi.middleware.cors import CORSMiddleware
8
  import uvicorn
9
+ import json
10
+
11
+ # Import MCP server components
12
+ from mcp.server.fastmcp import FastMCP
13
+ from edgar_client import EdgarDataClient
14
+ from financial_analyzer import FinancialAnalyzer
15
 
16
  app = FastAPI(title="SEC Financial Data MCP Server")
17
 
 
24
  allow_headers=["*"],
25
  )
26
 
27
+ # Initialize EDGAR clients
28
+ edgar_client = EdgarDataClient(
29
+ user_agent="Juntao Peng Financial Report Metrics App (jtyxabc@gmail.com)"
30
+ )
31
+
32
+ financial_analyzer = FinancialAnalyzer(
33
+ user_agent="Juntao Peng Financial Report Metrics App (jtyxabc@gmail.com)"
34
+ )
35
+
36
+ # Create FastMCP server with pure JSON response
37
+ mcp = FastMCP("sec-financial-data", json_response=True)
38
+
39
+
40
+ @mcp.tool()
41
+ def search_company(company_name: str) -> dict:
42
+ """Search for a company by name in SEC EDGAR database."""
43
+ result = edgar_client.search_company_by_name(company_name)
44
+ if result:
45
+ return result
46
+ else:
47
+ return {"error": f"No company found with name: {company_name}"}
48
+
49
+
50
+ @mcp.tool()
51
+ def get_company_info(cik: str) -> dict:
52
+ """Get detailed company information."""
53
+ result = edgar_client.get_company_info(cik)
54
+ if result:
55
+ return result
56
+ else:
57
+ return {"error": f"No company found with CIK: {cik}"}
58
+
59
+
60
+ @mcp.tool()
61
+ def get_company_filings(cik: str, form_types: list[str] | None = None) -> dict:
62
+ """Get list of company SEC filings."""
63
+ # Convert list to tuple for caching
64
+ if form_types:
65
+ form_types_tuple = tuple(form_types)
66
+ else:
67
+ form_types_tuple = None
68
+
69
+ result = edgar_client.get_company_filings(cik, form_types_tuple)
70
+ if result:
71
+ limited_result = result[:20]
72
+ return {
73
+ "total": len(result),
74
+ "returned": len(limited_result),
75
+ "filings": limited_result
76
+ }
77
+ else:
78
+ return {"error": f"No filings found for CIK: {cik}"}
79
+
80
+
81
+ @mcp.tool()
82
+ def get_financial_data(cik: str, period: str) -> dict:
83
+ """Get financial data for a specific period."""
84
+ result = edgar_client.get_financial_data_for_period(cik, period)
85
+ if result and "period" in result:
86
+ return result
87
+ else:
88
+ return {"error": f"No financial data found for CIK: {cik}, Period: {period}"}
89
+
90
+
91
+ @mcp.tool()
92
+ def extract_financial_metrics(cik: str, years: int = 3) -> dict:
93
+ """Extract comprehensive financial metrics for multiple years."""
94
+ if years < 1 or years > 10:
95
+ return {"error": "Years parameter must be between 1 and 10"}
96
+
97
+ metrics = financial_analyzer.extract_financial_metrics(cik, years)
98
+
99
+ if metrics:
100
+ formatted = financial_analyzer.format_financial_data(metrics)
101
+ return {
102
+ "periods": len(formatted),
103
+ "data": formatted
104
+ }
105
+ else:
106
+ return {"error": f"No financial metrics extracted for CIK: {cik}"}
107
+
108
+
109
+ @mcp.tool()
110
+ def get_latest_financial_data(cik: str) -> dict:
111
+ """Get the most recent financial data available."""
112
+ result = financial_analyzer.get_latest_financial_data(cik)
113
+ if result and "period" in result:
114
+ return result
115
+ else:
116
+ return {"error": f"No latest financial data found for CIK: {cik}"}
117
+
118
+
119
+ @mcp.tool()
120
+ def advanced_search_company(company_input: str) -> dict:
121
+ """Advanced search supporting both company name and CIK code."""
122
+ result = financial_analyzer.search_company(company_input)
123
+ if result.get("error"):
124
+ return {"error": result["error"]}
125
+ return result
126
+
127
+
128
+ # Mount MCP server SSE endpoint
129
  @app.post("/sse")
130
+ @app.get("/sse")
131
+ async def mcp_sse_endpoint(request: Request):
132
+ """MCP SSE endpoint - handle both GET and POST"""
133
  try:
134
+ # For POST requests, get JSON body
135
+ if request.method == "POST":
136
+ body = await request.json()
137
+
138
+ # Handle JSON-RPC request
139
+ if body.get("method") == "tools/list":
140
+ # List all tools
141
+ tools = []
142
+ for tool_name, tool_func in mcp._tool_manager._tools.items():
143
+ tools.append({
144
+ "name": tool_name,
145
+ "description": tool_func.__doc__ or "",
146
+ "inputSchema": {
147
+ "type": "object",
148
+ "properties": {},
149
+ "required": []
150
+ }
151
+ })
152
+
153
+ return JSONResponse({
154
+ "jsonrpc": "2.0",
155
+ "id": body.get("id"),
156
+ "result": {"tools": tools}
157
+ })
158
+
159
+ elif body.get("method") == "tools/call":
160
+ # Call a specific tool
161
+ tool_name = body.get("params", {}).get("name")
162
+ arguments = body.get("params", {}).get("arguments", {})
163
+
164
+ if tool_name in mcp._tool_manager._tools:
165
+ tool_func = mcp._tool_manager._tools[tool_name]
166
+ result = tool_func(**arguments)
167
+
168
+ return JSONResponse({
169
+ "jsonrpc": "2.0",
170
+ "id": body.get("id"),
171
+ "result": {
172
+ "content": [{
173
+ "type": "text",
174
+ "text": json.dumps(result)
175
+ }]
176
+ }
177
+ })
178
+ else:
179
+ return JSONResponse({
180
+ "jsonrpc": "2.0",
181
+ "id": body.get("id"),
182
+ "error": {
183
+ "code": -32601,
184
+ "message": f"Tool not found: {tool_name}"
185
+ }
186
+ })
187
+
188
+ else:
189
+ return JSONResponse({
190
+ "jsonrpc": "2.0",
191
+ "id": body.get("id"),
192
+ "error": {
193
+ "code": -32601,
194
+ "message": f"Method not found: {body.get('method')}"
195
+ }
196
+ })
197
+
198
+ else:
199
+ # GET request - return info
200
+ return JSONResponse({
201
+ "service": "SEC Financial Data MCP Server",
202
+ "protocol": "MCP 2024-11-05",
203
+ "transport": "SSE",
204
+ "status": "online"
205
+ })
206
+
207
  except Exception as e:
208
  return JSONResponse(
209
+ content={"error": str(e), "type": "server_error"},
210
  status_code=500
211
  )
212
 
requirements.txt CHANGED
@@ -4,4 +4,3 @@ uvicorn[standard]>=0.30
4
  pydantic>=2.10.1
5
  sec-edgar-api==1.1.0
6
  requests==2.31.0
7
- httpx==0.27.0
 
4
  pydantic>=2.10.1
5
  sec-edgar-api==1.1.0
6
  requests==2.31.0