JC321 commited on
Commit
e65eb23
·
verified ·
1 Parent(s): ff53d56

Delete mcp_server.py

Browse files
Files changed (1) hide show
  1. mcp_server.py +0 -966
mcp_server.py DELETED
@@ -1,966 +0,0 @@
1
- """
2
- MCP Server for SEC Financial Report Data Query
3
- Based on FastAPI framework
4
- """
5
-
6
- from fastapi import FastAPI, HTTPException, Request
7
- from fastapi.middleware.cors import CORSMiddleware
8
- from fastapi.responses import HTMLResponse, JSONResponse
9
- from fastapi.staticfiles import StaticFiles
10
- from fastapi.exceptions import RequestValidationError
11
- from pydantic import BaseModel, Field, ValidationError
12
- from typing import Optional, List, Dict, Any
13
- from edgar_client import EdgarDataClient
14
- from financial_analyzer import FinancialAnalyzer
15
- import uvicorn
16
-
17
- # Initialize FastAPI app
18
- app = FastAPI(
19
- title="SEC Financial Report MCP Server",
20
- description="MCP Server for querying SEC EDGAR financial data",
21
- version="1.0.0"
22
- )
23
-
24
- # Configure CORS
25
- app.add_middleware(
26
- CORSMiddleware,
27
- allow_origins=["*"],
28
- allow_credentials=True,
29
- allow_methods=["*"],
30
- allow_headers=["*"],
31
- )
32
-
33
- # Initialize EDGAR client with user information
34
- edgar_client = EdgarDataClient(
35
- user_agent="Juntao Peng Financial Report Metrics App (jtyxabc@gmail.com)"
36
- )
37
-
38
- # Initialize Financial Analyzer
39
- financial_analyzer = FinancialAnalyzer(
40
- user_agent="Juntao Peng Financial Report Metrics App (jtyxabc@gmail.com)"
41
- )
42
-
43
-
44
- # Custom error handler for validation errors (422)
45
- @app.exception_handler(RequestValidationError)
46
- async def validation_exception_handler(request: Request, exc: RequestValidationError):
47
- """
48
- Custom handler for 422 Unprocessable Entity errors
49
- Provides user-friendly error messages
50
- """
51
- errors = exc.errors()
52
-
53
- # Build friendly error message
54
- error_messages = []
55
- for error in errors:
56
- field = " -> ".join(str(loc) for loc in error['loc'])
57
- msg = error['msg']
58
- error_type = error['type']
59
-
60
- if error_type == 'missing':
61
- error_messages.append(f"Missing required field: {field}")
62
- elif 'type_error' in error_type:
63
- error_messages.append(f"Invalid type for field '{field}': {msg}")
64
- else:
65
- error_messages.append(f"Validation error in '{field}': {msg}")
66
-
67
- return JSONResponse(
68
- status_code=422,
69
- content={
70
- "error": "Request validation failed",
71
- "details": error_messages,
72
- "help": {
73
- "note": "Please ensure you send data in the request BODY (not URL parameters)",
74
- "example": {
75
- "cik": "1577552",
76
- "period": "2024" # if applicable
77
- },
78
- "common_issues": [
79
- "Don't put parameters in the URL query string",
80
- "Ensure CIK has no extra spaces",
81
- "Send Content-Type: application/json header",
82
- "Use POST method with JSON body"
83
- ]
84
- }
85
- }
86
- )
87
-
88
-
89
- # Request/Response Models
90
- class UserAgentRequest(BaseModel):
91
- user_agent: str = Field(
92
- default="Financial Report Metrics App (your-email@example.com)",
93
- description="User agent string for identifying request source"
94
- )
95
-
96
-
97
- class CompanySearchRequest(BaseModel):
98
- company_name: str = Field(..., description="Company name to search")
99
-
100
-
101
- class CompanyInfoRequest(BaseModel):
102
- cik: str = Field(..., description="Company CIK code")
103
-
104
- class Config:
105
- # Enable validation for strings
106
- str_strip_whitespace = True
107
-
108
- def __init__(self, **data):
109
- # Strip whitespace and clean CIK
110
- if 'cik' in data and data['cik']:
111
- data['cik'] = str(data['cik']).strip()
112
- super().__init__(**data)
113
-
114
-
115
- class CompanyFilingsRequest(BaseModel):
116
- cik: str = Field(..., description="Company CIK code")
117
- form_types: Optional[List[str]] = Field(
118
- None,
119
- description="List of form types (e.g., ['10-K', '10-Q']), None for all types"
120
- )
121
-
122
- class Config:
123
- # Enable validation for strings
124
- str_strip_whitespace = True
125
-
126
- def __init__(self, **data):
127
- # Strip whitespace and clean CIK
128
- if 'cik' in data and data['cik']:
129
- data['cik'] = str(data['cik']).strip()
130
- super().__init__(**data)
131
-
132
-
133
- class CompanyFactsRequest(BaseModel):
134
- cik: str = Field(..., description="Company CIK code")
135
-
136
- class Config:
137
- # Enable validation for strings
138
- str_strip_whitespace = True
139
-
140
- def __init__(self, **data):
141
- # Strip whitespace and clean CIK
142
- if 'cik' in data and data['cik']:
143
- data['cik'] = str(data['cik']).strip()
144
- super().__init__(**data)
145
-
146
-
147
- class FinancialDataRequest(BaseModel):
148
- cik: str = Field(..., description="Company CIK code")
149
- period: str = Field(
150
- ...,
151
- description="Period in format 'YYYY' or 'YYYYQX' (e.g., '2025' or '2025Q3')"
152
- )
153
-
154
- class Config:
155
- # Enable validation for strings
156
- str_strip_whitespace = True
157
-
158
- def __init__(self, **data):
159
- # Strip whitespace and clean inputs
160
- if 'cik' in data and data['cik']:
161
- data['cik'] = str(data['cik']).strip()
162
- if 'period' in data and data['period']:
163
- data['period'] = str(data['period']).strip()
164
- super().__init__(**data)
165
-
166
-
167
- class AdvancedSearchRequest(BaseModel):
168
- company_input: str = Field(..., description="Company name or CIK code")
169
-
170
-
171
- class ExtractMetricsRequest(BaseModel):
172
- cik: str = Field(..., description="Company CIK code")
173
- years: int = Field(default=3, description="Number of years to extract", ge=1, le=10)
174
-
175
- class Config:
176
- # Enable validation for strings
177
- str_strip_whitespace = True
178
-
179
- def __init__(self, **data):
180
- # Strip whitespace and clean CIK
181
- if 'cik' in data and data['cik']:
182
- data['cik'] = str(data['cik']).strip()
183
- super().__init__(**data)
184
-
185
-
186
- class LatestDataRequest(BaseModel):
187
- cik: str = Field(..., description="Company CIK code")
188
-
189
- class Config:
190
- # Enable validation for strings
191
- str_strip_whitespace = True
192
-
193
- def __init__(self, **data):
194
- # Strip whitespace and clean CIK
195
- if 'cik' in data and data['cik']:
196
- data['cik'] = str(data['cik']).strip()
197
- super().__init__(**data)
198
-
199
-
200
- class CompanySearchResponse(BaseModel):
201
- cik: Optional[str] = None
202
- name: Optional[str] = None
203
- ticker: Optional[str] = None
204
- error: Optional[str] = None
205
-
206
-
207
- class CompanyInfoResponse(BaseModel):
208
- cik: Optional[str] = None
209
- name: Optional[str] = None
210
- tickers: Optional[List[str]] = None
211
- sic: Optional[str] = None
212
- sic_description: Optional[str] = None
213
- error: Optional[str] = None
214
-
215
-
216
- class FilingItem(BaseModel):
217
- form_type: str
218
- filing_date: str
219
- accession_number: str
220
- primary_document: str
221
-
222
-
223
- class CompanyFilingsResponse(BaseModel):
224
- filings: List[FilingItem] = []
225
- error: Optional[str] = None
226
-
227
-
228
- class CompanyFactsResponse(BaseModel):
229
- facts: Dict[str, Any] = {}
230
- error: Optional[str] = None
231
-
232
-
233
- class FinancialDataResponse(BaseModel):
234
- period: Optional[str] = None
235
- total_revenue: Optional[float] = None
236
- net_income: Optional[float] = None
237
- earnings_per_share: Optional[float] = None
238
- operating_expenses: Optional[float] = None
239
- operating_cash_flow: Optional[float] = None
240
- source_url: Optional[str] = None
241
- source_form: Optional[str] = None
242
- data_source: Optional[str] = None
243
- additional_details: Optional[Dict[str, Any]] = None
244
- error: Optional[str] = None
245
-
246
-
247
- class MetricsListResponse(BaseModel):
248
- metrics: List[Dict[str, Any]] = []
249
- count: int = 0
250
- error: Optional[str] = None
251
-
252
-
253
- # API Endpoints
254
- @app.get("/", response_class=HTMLResponse)
255
- async def root():
256
- """Root endpoint with beautiful HTML interface"""
257
- html_content = """
258
- <!DOCTYPE html>
259
- <html lang="en">
260
- <head>
261
- <meta charset="UTF-8">
262
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
263
- <title>SEC Financial Report MCP Server</title>
264
- <style>
265
- body {
266
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
267
- line-height: 1.6;
268
- color: #333;
269
- max-width: 1200px;
270
- margin: 0 auto;
271
- padding: 20px;
272
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
273
- min-height: 100vh;
274
- }
275
- .container {
276
- background: white;
277
- border-radius: 12px;
278
- padding: 40px;
279
- box-shadow: 0 10px 30px rgba(0,0,0,0.1);
280
- }
281
- .header {
282
- text-align: center;
283
- margin-bottom: 40px;
284
- padding-bottom: 20px;
285
- border-bottom: 3px solid #667eea;
286
- }
287
- .header h1 {
288
- color: #667eea;
289
- margin: 0;
290
- font-size: 2.5em;
291
- font-weight: 700;
292
- }
293
- .status {
294
- display: inline-block;
295
- background: #10b981;
296
- color: white;
297
- padding: 8px 16px;
298
- border-radius: 20px;
299
- font-size: 0.9em;
300
- margin-top: 10px;
301
- }
302
- .quick-start {
303
- background: linear-gradient(135deg, #667eea, #764ba2);
304
- color: white;
305
- padding: 30px;
306
- border-radius: 10px;
307
- margin-bottom: 30px;
308
- }
309
- .quick-start h2 {
310
- margin-top: 0;
311
- font-size: 1.8em;
312
- }
313
- .steps {
314
- display: grid;
315
- grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
316
- gap: 20px;
317
- margin-top: 20px;
318
- }
319
- .step {
320
- background: rgba(255,255,255,0.1);
321
- padding: 20px;
322
- border-radius: 8px;
323
- border: 1px solid rgba(255,255,255,0.2);
324
- }
325
- .step h3 {
326
- margin: 0 0 10px 0;
327
- font-size: 1.1em;
328
- }
329
- .code {
330
- background: rgba(0,0,0,0.1);
331
- padding: 10px;
332
- border-radius: 6px;
333
- font-family: 'Monaco', 'Consolas', monospace;
334
- font-size: 0.9em;
335
- overflow-x: auto;
336
- }
337
- .endpoints-grid {
338
- display: grid;
339
- grid-template-columns: 1fr 1fr;
340
- gap: 30px;
341
- margin-bottom: 30px;
342
- }
343
- .endpoint-section {
344
- border: 2px solid #e5e7eb;
345
- border-radius: 10px;
346
- padding: 25px;
347
- background: #f9fafb;
348
- }
349
- .endpoint-section.advanced {
350
- border-color: #667eea;
351
- background: #f0f4ff;
352
- }
353
- .endpoint-section h3 {
354
- margin-top: 0;
355
- color: #667eea;
356
- font-size: 1.4em;
357
- display: flex;
358
- align-items: center;
359
- gap: 10px;
360
- }
361
- .recommended {
362
- background: #fef3c7;
363
- color: #92400e;
364
- padding: 4px 8px;
365
- border-radius: 12px;
366
- font-size: 0.75em;
367
- font-weight: 600;
368
- }
369
- .endpoint {
370
- margin-bottom: 15px;
371
- padding: 15px;
372
- background: white;
373
- border-radius: 6px;
374
- border-left: 4px solid #10b981;
375
- }
376
- .endpoint-path {
377
- font-family: 'Monaco', 'Consolas', monospace;
378
- color: #059669;
379
- font-weight: 600;
380
- font-size: 0.9em;
381
- }
382
- .endpoint-desc {
383
- color: #6b7280;
384
- margin-top: 5px;
385
- font-size: 0.9em;
386
- }
387
- .workflows {
388
- background: #f8fafc;
389
- padding: 30px;
390
- border-radius: 10px;
391
- margin-top: 30px;
392
- }
393
- .workflow {
394
- margin-bottom: 25px;
395
- padding: 20px;
396
- background: white;
397
- border-radius: 8px;
398
- border-left: 4px solid #8b5cf6;
399
- }
400
- .workflow h4 {
401
- margin: 0 0 10px 0;
402
- color: #8b5cf6;
403
- font-size: 1.1em;
404
- }
405
- .workflow-step {
406
- background: #f3f4f6;
407
- padding: 10px;
408
- margin: 5px 0;
409
- border-radius: 4px;
410
- font-family: 'Monaco', 'Consolas', monospace;
411
- font-size: 0.85em;
412
- }
413
- .links {
414
- text-align: center;
415
- margin-top: 40px;
416
- padding-top: 20px;
417
- border-top: 2px solid #e5e7eb;
418
- }
419
- .link-btn {
420
- display: inline-block;
421
- background: #667eea;
422
- color: white;
423
- padding: 12px 24px;
424
- margin: 0 10px;
425
- text-decoration: none;
426
- border-radius: 6px;
427
- font-weight: 600;
428
- transition: background-color 0.3s;
429
- }
430
- .link-btn:hover {
431
- background: #5a67d8;
432
- }
433
- .link-btn.secondary {
434
- background: #6b7280;
435
- }
436
- .link-btn.secondary:hover {
437
- background: #4b5563;
438
- }
439
- @media (max-width: 768px) {
440
- .endpoints-grid {
441
- grid-template-columns: 1fr;
442
- }
443
- .steps {
444
- grid-template-columns: 1fr;
445
- }
446
- .container {
447
- padding: 20px;
448
- }
449
- }
450
- </style>
451
- </head>
452
- <body>
453
- <div class="container">
454
- <div class="header">
455
- <h1>📊 SEC Financial Report MCP Server</h1>
456
- <div class="status">✅ Operational</div>
457
- <p>MCP Server for querying SEC EDGAR financial data - A tool-ready API for AI agents and applications</p>
458
- </div>
459
-
460
- <div class="quick-start">
461
- <h2>🚀 Quick Start</h2>
462
- <div class="steps">
463
- <div class="step">
464
- <h3>Step 1: Search Company</h3>
465
- <div class="code">POST /api/advanced_search<br>{"company_input": "NVIDIA"}</div>
466
- </div>
467
- <div class="step">
468
- <h3>Step 2: Get Latest Data</h3>
469
- <div class="code">POST /api/get_latest_financial_data<br>{"cik": "0001045810"}</div>
470
- </div>
471
- <div class="step">
472
- <h3>Step 3: Analyze Trends</h3>
473
- <div class="code">POST /api/extract_financial_metrics<br>{"cik": "0001045810", "years": 3}</div>
474
- </div>
475
- </div>
476
- </div>
477
-
478
- <div class="endpoints-grid">
479
- <div class="endpoint-section">
480
- <h3>🔧 Basic Tools</h3>
481
- <div class="endpoint">
482
- <div class="endpoint-path">POST /api/search_company</div>
483
- <div class="endpoint-desc">Search company by name only</div>
484
- </div>
485
- <div class="endpoint">
486
- <div class="endpoint-path">POST /api/get_company_info</div>
487
- <div class="endpoint-desc">Get company information by CIK</div>
488
- </div>
489
- <div class="endpoint">
490
- <div class="endpoint-path">POST /api/get_company_filings</div>
491
- <div class="endpoint-desc">Get company SEC filings (10-K, 10-Q, 20-F)</div>
492
- </div>
493
- <div class="endpoint">
494
- <div class="endpoint-path">POST /api/get_company_facts</div>
495
- <div class="endpoint-desc">Get complete XBRL financial facts</div>
496
- </div>
497
- <div class="endpoint">
498
- <div class="endpoint-path">POST /api/get_financial_data</div>
499
- <div class="endpoint-desc">Get financial data for specific period</div>
500
- </div>
501
- <div class="endpoint">
502
- <div class="endpoint-path">GET /health</div>
503
- <div class="endpoint-desc">Server health check</div>
504
- </div>
505
- </div>
506
-
507
- <div class="endpoint-section advanced">
508
- <h3>⭐ Advanced Tools <span class="recommended">RECOMMENDED</span></h3>
509
- <div class="endpoint">
510
- <div class="endpoint-path">POST /api/advanced_search</div>
511
- <div class="endpoint-desc">Smart search - accepts company name OR CIK</div>
512
- </div>
513
- <div class="endpoint">
514
- <div class="endpoint-path">POST /api/extract_financial_metrics</div>
515
- <div class="endpoint-desc">Extract multi-year metrics (annual + quarterly)</div>
516
- </div>
517
- <div class="endpoint">
518
- <div class="endpoint-path">POST /api/get_latest_financial_data</div>
519
- <div class="endpoint-desc">Get most recent annual financial data automatically</div>
520
- </div>
521
- </div>
522
- </div>
523
-
524
- <div class="workflows">
525
- <h3>📋 Example Workflows</h3>
526
- <div class="workflow">
527
- <h4>Get Latest Company Financials</h4>
528
- <div class="workflow-step">1. POST /api/advanced_search {"company_input": "Apple"}</div>
529
- <div class="workflow-step">2. POST /api/get_latest_financial_data {"cik": "&lt;from_step_1&gt;"}</div>
530
- </div>
531
- <div class="workflow">
532
- <h4>Analyze 5-Year Trends</h4>
533
- <div class="workflow-step">1. POST /api/advanced_search {"company_input": "TSMC"}</div>
534
- <div class="workflow-step">2. POST /api/extract_financial_metrics {"cik": "&lt;from_step_1&gt;", "years": 5}</div>
535
- </div>
536
- <div class="workflow">
537
- <h4>Get Specific Quarter Data</h4>
538
- <div class="workflow-step">1. POST /api/advanced_search {"company_input": "NVIDIA"}</div>
539
- <div class="workflow-step">2. POST /api/get_financial_data {"cik": "&lt;from_step_1&gt;", "period": "2024Q3"}</div>
540
- </div>
541
- </div>
542
-
543
- <div class="links">
544
- <a href="/docs" class="link-btn">📚 Interactive API Docs</a>
545
- <a href="/redoc" class="link-btn">📖 ReDoc Documentation</a>
546
- <a href="/health" class="link-btn secondary">💚 Health Check</a>
547
- <a href="/api" class="link-btn secondary">🔗 JSON API Info</a>
548
- </div>
549
-
550
- <div style="text-align: center; margin-top: 30px; color: #6b7280; font-size: 0.9em;">
551
- <p><strong>Data Source:</strong> US SEC EDGAR system (real-time) | <strong>Rate Limit:</strong> 10 requests/second</p>
552
- <p><strong>User Agent:</strong> Juntao Peng (jtyxabc@gmail.com) | <strong>Always-On:</strong> HF CPU Upgrade</p>
553
- </div>
554
- </div>
555
- </body>
556
- </html>
557
- """
558
- return html_content
559
-
560
-
561
- @app.get("/api")
562
- async def api_info():
563
- """JSON API information for programmatic access"""
564
- return {
565
- "service": "SEC Financial Report MCP Server",
566
- "version": "1.0.0",
567
- "description": "MCP Server for querying SEC EDGAR financial data - A tool-ready API for AI agents and applications",
568
- "status": "operational",
569
- "user_agent": "Juntao Peng (jtyxabc@gmail.com)",
570
-
571
- "quick_start": {
572
- "interactive_docs": "/docs",
573
- "api_documentation": "/redoc",
574
- "health_check": "/health",
575
- "example_usage": {
576
- "step_1": "Search company: POST /api/advanced_search with {\"company_input\": \"NVIDIA\"}",
577
- "step_2": "Get latest data: POST /api/get_latest_financial_data with {\"cik\": \"0001045810\"}",
578
- "step_3": "Get trends: POST /api/extract_financial_metrics with {\"cik\": \"0001045810\", \"years\": 3}"
579
- }
580
- },
581
-
582
- "endpoints": {
583
- "basic_tools": {
584
- "search_company": {
585
- "path": "/api/search_company",
586
- "method": "POST",
587
- "description": "Search company by name only",
588
- "input": {"company_name": "string"}
589
- },
590
- "get_company_info": {
591
- "path": "/api/get_company_info",
592
- "method": "POST",
593
- "description": "Get company information by CIK",
594
- "input": {"cik": "string"}
595
- },
596
- "get_company_filings": {
597
- "path": "/api/get_company_filings",
598
- "method": "POST",
599
- "description": "Get company SEC filings (10-K, 10-Q, 20-F)",
600
- "input": {"cik": "string", "form_types": "array or null"}
601
- },
602
- "get_company_facts": {
603
- "path": "/api/get_company_facts",
604
- "method": "POST",
605
- "description": "Get complete XBRL financial facts",
606
- "input": {"cik": "string"}
607
- },
608
- "get_financial_data": {
609
- "path": "/api/get_financial_data",
610
- "method": "POST",
611
- "description": "Get financial data for specific period",
612
- "input": {"cik": "string", "period": "YYYY or YYYYQX"}
613
- },
614
- "health": {
615
- "path": "/health",
616
- "method": "GET",
617
- "description": "Server health check"
618
- }
619
- },
620
-
621
- "advanced_tools": {
622
- "advanced_search": {
623
- "path": "/api/advanced_search",
624
- "method": "POST",
625
- "description": "Smart search - accepts company name OR CIK",
626
- "input": {"company_input": "string"},
627
- "recommended": True
628
- },
629
- "extract_financial_metrics": {
630
- "path": "/api/extract_financial_metrics",
631
- "method": "POST",
632
- "description": "Extract multi-year metrics (annual + quarterly)",
633
- "input": {"cik": "string", "years": "integer (1-10)"},
634
- "recommended": True
635
- },
636
- "get_latest_financial_data": {
637
- "path": "/api/get_latest_financial_data",
638
- "method": "POST",
639
- "description": "Get most recent annual financial data automatically",
640
- "input": {"cik": "string"},
641
- "recommended": True
642
- }
643
- }
644
- },
645
-
646
- "usage_guide": {
647
- "for_ai_agents": "Use advanced_search → get_latest_financial_data for quick queries. Use extract_financial_metrics for trend analysis.",
648
- "for_developers": "Visit /docs for interactive API testing with Swagger UI",
649
- "data_source": "US SEC EDGAR system (real-time)",
650
- "supported_forms": ["10-K (annual)", "10-Q (quarterly)", "20-F (foreign annual)"],
651
- "rate_limit": "Follow SEC guidelines: 10 requests/second"
652
- },
653
-
654
- "example_workflows": [
655
- {
656
- "name": "Get Latest Company Financials",
657
- "steps": [
658
- "POST /api/advanced_search {\"company_input\": \"Apple\"}",
659
- "POST /api/get_latest_financial_data {\"cik\": \"<from_step_1>\"}"
660
- ]
661
- },
662
- {
663
- "name": "Analyze 5-Year Trends",
664
- "steps": [
665
- "POST /api/advanced_search {\"company_input\": \"TSMC\"}",
666
- "POST /api/extract_financial_metrics {\"cik\": \"<from_step_1>\", \"years\": 5}"
667
- ]
668
- },
669
- {
670
- "name": "Get Specific Quarter Data",
671
- "steps": [
672
- "POST /api/advanced_search {\"company_input\": \"NVIDIA\"}",
673
- "POST /api/get_financial_data {\"cik\": \"<from_step_1>\", \"period\": \"2024Q3\"}"
674
- ]
675
- }
676
- ],
677
-
678
- "links": {
679
- "interactive_docs": "/docs",
680
- "redoc": "/redoc",
681
- "github": "https://github.com/JC321/EasyReportDateMCP",
682
- "huggingface_space": "https://huggingface.co/spaces/JC321/EasyReportDateMCP"
683
- }
684
- }
685
-
686
-
687
- @app.get("/health")
688
- async def health_check():
689
- """Health check endpoint"""
690
- return {"status": "healthy", "service": "SEC Financial Report MCP Server"}
691
-
692
-
693
- @app.post("/api/search_company", response_model=CompanySearchResponse)
694
- async def search_company_by_name(request: CompanySearchRequest):
695
- """
696
- Search company CIK by company name
697
-
698
- Args:
699
- company_name (str): Company name
700
-
701
- Returns:
702
- dict: Dictionary containing company information
703
- """
704
- try:
705
- result = edgar_client.search_company_by_name(request.company_name)
706
-
707
- if result is None:
708
- return CompanySearchResponse(
709
- error=f"No company found matching '{request.company_name}'"
710
- )
711
-
712
- return CompanySearchResponse(
713
- cik=result.get("cik"),
714
- name=result.get("name"),
715
- ticker=result.get("ticker")
716
- )
717
- except Exception as e:
718
- raise HTTPException(status_code=500, detail=str(e))
719
-
720
-
721
- @app.post("/api/get_company_info", response_model=CompanyInfoResponse)
722
- async def get_company_info(request: CompanyInfoRequest):
723
- """
724
- Get basic company information
725
-
726
- Args:
727
- cik (str): Company CIK code
728
-
729
- Returns:
730
- dict: Dictionary containing company information
731
- """
732
- try:
733
- result = edgar_client.get_company_info(request.cik)
734
-
735
- if result is None:
736
- return CompanyInfoResponse(
737
- error=f"No company information found for CIK: {request.cik}"
738
- )
739
-
740
- return CompanyInfoResponse(
741
- cik=result.get("cik"),
742
- name=result.get("name"),
743
- tickers=result.get("tickers"),
744
- sic=result.get("sic"),
745
- sic_description=result.get("sic_description")
746
- )
747
- except Exception as e:
748
- raise HTTPException(status_code=500, detail=str(e))
749
-
750
-
751
- @app.post("/api/get_company_filings", response_model=CompanyFilingsResponse)
752
- async def get_company_filings(request: CompanyFilingsRequest):
753
- """
754
- Get all company filing documents
755
-
756
- Args:
757
- cik (str): Company CIK code
758
- form_types (list): List of form types (e.g., ['10-K', '10-Q']), None for all types
759
-
760
- Returns:
761
- list: List of filing documents
762
- """
763
- try:
764
- result = edgar_client.get_company_filings(request.cik, request.form_types)
765
-
766
- if not result:
767
- return CompanyFilingsResponse(
768
- filings=[],
769
- error=f"No filings found for CIK: {request.cik}"
770
- )
771
-
772
- filings = [
773
- FilingItem(
774
- form_type=filing.get("form_type", ""),
775
- filing_date=filing.get("filing_date", ""),
776
- accession_number=filing.get("accession_number", ""),
777
- primary_document=filing.get("primary_document", "")
778
- )
779
- for filing in result
780
- ]
781
-
782
- return CompanyFilingsResponse(filings=filings)
783
- except Exception as e:
784
- raise HTTPException(status_code=500, detail=str(e))
785
-
786
-
787
- @app.post("/api/get_company_facts", response_model=CompanyFactsResponse)
788
- async def get_company_facts(request: CompanyFactsRequest):
789
- """
790
- Get all company financial facts data
791
-
792
- Args:
793
- cik (str): Company CIK code
794
-
795
- Returns:
796
- dict: Company financial facts data
797
- """
798
- try:
799
- result = edgar_client.get_company_facts(request.cik)
800
-
801
- if not result:
802
- return CompanyFactsResponse(
803
- facts={},
804
- error=f"No financial facts found for CIK: {request.cik}"
805
- )
806
-
807
- return CompanyFactsResponse(facts=result)
808
- except Exception as e:
809
- raise HTTPException(status_code=500, detail=str(e))
810
-
811
-
812
- @app.post("/api/get_financial_data", response_model=FinancialDataResponse)
813
- async def get_financial_data_for_period(request: FinancialDataRequest):
814
- """
815
- Get financial data for a specific period (supports annual and quarterly)
816
-
817
- Args:
818
- cik (str): Company CIK code
819
- period (str): Period in format 'YYYY' or 'YYYYQX' (e.g., '2025' or '2025Q3')
820
-
821
- Returns:
822
- dict: Financial data dictionary
823
- """
824
- try:
825
- result = edgar_client.get_financial_data_for_period(request.cik, request.period)
826
-
827
- if not result or "period" not in result:
828
- return FinancialDataResponse(
829
- error=f"No financial data found for CIK: {request.cik}, Period: {request.period}"
830
- )
831
-
832
- # Collect additional details
833
- additional_details = {}
834
- for key, value in result.items():
835
- if key.endswith("_details"):
836
- additional_details[key] = value
837
-
838
- return FinancialDataResponse(
839
- period=result.get("period"),
840
- total_revenue=result.get("total_revenue"),
841
- net_income=result.get("net_income"),
842
- earnings_per_share=result.get("earnings_per_share"),
843
- operating_expenses=result.get("operating_expenses"),
844
- operating_cash_flow=result.get("operating_cash_flow"),
845
- source_url=result.get("source_url"),
846
- source_form=result.get("source_form"),
847
- data_source=result.get("data_source"),
848
- additional_details=additional_details if additional_details else None
849
- )
850
- except Exception as e:
851
- raise HTTPException(status_code=500, detail=str(e))
852
-
853
-
854
- @app.post("/api/advanced_search", response_model=CompanyInfoResponse)
855
- async def advanced_search_company(request: AdvancedSearchRequest):
856
- """
857
- Advanced company search (supports both company name and CIK)
858
- Uses FinancialAnalyzer.search_company() method
859
-
860
- Args:
861
- company_input (str): Company name or CIK code
862
-
863
- Returns:
864
- dict: Company information
865
- """
866
- try:
867
- result = financial_analyzer.search_company(request.company_input)
868
-
869
- if result.get("error"):
870
- return CompanyInfoResponse(error=result["error"])
871
-
872
- return CompanyInfoResponse(
873
- cik=result.get("cik"),
874
- name=result.get("name"),
875
- tickers=result.get("tickers"),
876
- sic=result.get("sic"),
877
- sic_description=result.get("sic_description")
878
- )
879
- except Exception as e:
880
- raise HTTPException(status_code=500, detail=str(e))
881
-
882
-
883
- @app.post("/api/extract_financial_metrics", response_model=MetricsListResponse)
884
- async def extract_financial_metrics(request: ExtractMetricsRequest):
885
- """
886
- Extract financial metrics for multiple years (annual and quarterly)
887
- Uses FinancialAnalyzer.extract_financial_metrics() method
888
-
889
- Args:
890
- cik (str): Company CIK code
891
- years (int): Number of years to extract (default: 3, max: 10)
892
-
893
- Returns:
894
- dict: List of financial metrics for multiple periods
895
- """
896
- try:
897
- metrics = financial_analyzer.extract_financial_metrics(request.cik, request.years)
898
-
899
- if not metrics:
900
- return MetricsListResponse(
901
- metrics=[],
902
- count=0,
903
- error=f"No financial metrics found for CIK: {request.cik}"
904
- )
905
-
906
- # Format the metrics
907
- formatted_metrics = financial_analyzer.format_financial_data(metrics)
908
-
909
- return MetricsListResponse(
910
- metrics=formatted_metrics,
911
- count=len(formatted_metrics)
912
- )
913
- except Exception as e:
914
- raise HTTPException(status_code=500, detail=str(e))
915
-
916
-
917
- @app.post("/api/get_latest_financial_data", response_model=FinancialDataResponse)
918
- async def get_latest_financial_data(request: LatestDataRequest):
919
- """
920
- Get the latest financial data for a company
921
- Uses FinancialAnalyzer.get_latest_financial_data() method
922
-
923
- Args:
924
- cik (str): Company CIK code
925
-
926
- Returns:
927
- dict: Latest financial data
928
- """
929
- try:
930
- result = financial_analyzer.get_latest_financial_data(request.cik)
931
-
932
- if not result or "period" not in result:
933
- return FinancialDataResponse(
934
- error=f"No latest financial data found for CIK: {request.cik}"
935
- )
936
-
937
- # Collect additional details
938
- additional_details = {}
939
- for key, value in result.items():
940
- if key.endswith("_details"):
941
- additional_details[key] = value
942
-
943
- return FinancialDataResponse(
944
- period=result.get("period"),
945
- total_revenue=result.get("total_revenue"),
946
- net_income=result.get("net_income"),
947
- earnings_per_share=result.get("earnings_per_share"),
948
- operating_expenses=result.get("operating_expenses"),
949
- operating_cash_flow=result.get("operating_cash_flow"),
950
- source_url=result.get("source_url"),
951
- source_form=result.get("source_form"),
952
- data_source=result.get("data_source"),
953
- additional_details=additional_details if additional_details else None
954
- )
955
- except Exception as e:
956
- raise HTTPException(status_code=500, detail=str(e))
957
-
958
-
959
- if __name__ == "__main__":
960
- # Run the server
961
- uvicorn.run(
962
- "mcp_server:app",
963
- host="0.0.0.0",
964
- port=7860,
965
- reload=True
966
- )