Initial commit
This commit is contained in:
394
venv/lib/python3.10/site-packages/edgar/ai/mcp/server.py
Normal file
394
venv/lib/python3.10/site-packages/edgar/ai/mcp/server.py
Normal file
@@ -0,0 +1,394 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
EdgarTools MCP Server
|
||||
|
||||
MCP (Model Context Protocol) server providing AI agents access to SEC filing data.
|
||||
This module provides the main entry point for the MCP server.
|
||||
|
||||
Usage:
|
||||
python -m edgar.ai.mcp # Via module
|
||||
edgartools-mcp # Via console script
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
from typing import Any
|
||||
|
||||
from mcp import Resource, Tool
|
||||
from mcp.server import NotificationOptions, Server
|
||||
from mcp.server.models import InitializationOptions
|
||||
from mcp.server.stdio import stdio_server
|
||||
from mcp.types import TextContent
|
||||
|
||||
# Set up logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
logger = logging.getLogger("edgartools-mcp")
|
||||
|
||||
|
||||
def setup_edgar_identity():
|
||||
"""Configure SEC identity from environment variable.
|
||||
|
||||
The SEC requires proper identification for API requests. This function
|
||||
checks for the EDGAR_IDENTITY environment variable and configures it.
|
||||
If not set, logs a warning but continues (API errors will guide user).
|
||||
"""
|
||||
try:
|
||||
from edgar import set_identity
|
||||
|
||||
identity = os.environ.get('EDGAR_IDENTITY')
|
||||
if not identity:
|
||||
logger.warning(
|
||||
"EDGAR_IDENTITY environment variable not set. "
|
||||
"The SEC requires proper identification for API requests.\n"
|
||||
"Add to your MCP client configuration:\n"
|
||||
' "env": {"EDGAR_IDENTITY": "Your Name your.email@example.com"}\n'
|
||||
"Or set in your shell: export EDGAR_IDENTITY=\"Your Name your.email@example.com\""
|
||||
)
|
||||
return
|
||||
|
||||
set_identity(identity)
|
||||
logger.info(f"SEC identity configured: {identity}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error setting up EDGAR identity: {e}")
|
||||
|
||||
# Create the server
|
||||
app = Server("edgartools")
|
||||
|
||||
|
||||
@app.list_tools()
|
||||
async def list_tools() -> list[Tool]:
|
||||
"""List available tools."""
|
||||
return [
|
||||
Tool(
|
||||
name="edgar_company_research",
|
||||
description="Get company overview and background. Returns profile, 3-year financial trends, and recent filing activity. Use this for initial company research or to get a snapshot of recent performance.",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"identifier": {
|
||||
"type": "string",
|
||||
"description": "Company ticker (AAPL), CIK (0000320193), or name (Apple Inc)"
|
||||
},
|
||||
"include_financials": {
|
||||
"type": "boolean",
|
||||
"description": "Include 3-year income statement showing revenue and profit trends",
|
||||
"default": True
|
||||
},
|
||||
"include_filings": {
|
||||
"type": "boolean",
|
||||
"description": "Include summary of last 5 SEC filings",
|
||||
"default": True
|
||||
},
|
||||
"include_ownership": {
|
||||
"type": "boolean",
|
||||
"description": "Include insider and institutional ownership data (currently not implemented)",
|
||||
"default": False
|
||||
},
|
||||
"detail_level": {
|
||||
"type": "string",
|
||||
"enum": ["minimal", "standard", "detailed"],
|
||||
"description": "Response detail: 'minimal' (key metrics only), 'standard' (balanced), 'detailed' (comprehensive data)",
|
||||
"default": "standard"
|
||||
}
|
||||
},
|
||||
"required": ["identifier"]
|
||||
}
|
||||
),
|
||||
Tool(
|
||||
name="edgar_analyze_financials",
|
||||
description="Detailed financial statement analysis across multiple periods. Use this for trend analysis, growth calculations, or comparing financial performance over time.",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"company": {
|
||||
"type": "string",
|
||||
"description": "Company ticker (TSLA), CIK (0001318605), or name (Tesla Inc)"
|
||||
},
|
||||
"periods": {
|
||||
"type": "integer",
|
||||
"description": "Number of periods: 4-5 for trends, 8-10 for patterns (max 10)",
|
||||
"default": 4
|
||||
},
|
||||
"annual": {
|
||||
"type": "boolean",
|
||||
"description": "Use annual periods (true) for long-term trends and year-over-year comparisons, or quarterly periods (false) for recent performance and current earnings. Quarterly provides more recent data but may show seasonal volatility.",
|
||||
"default": True
|
||||
},
|
||||
"statement_types": {
|
||||
"type": "array",
|
||||
"items": {"type": "string", "enum": ["income", "balance", "cash_flow"]},
|
||||
"description": "Statements to include: 'income' (revenue, profit, growth), 'balance' (assets, liabilities, equity), 'cash_flow' (operating, investing, financing cash flows)",
|
||||
"default": ["income"]
|
||||
}
|
||||
},
|
||||
"required": ["company"]
|
||||
}
|
||||
),
|
||||
Tool(
|
||||
name="edgar_industry_overview",
|
||||
description="Get overview of an industry sector including company count, major players, and aggregate metrics. Use this to understand industry landscape before diving into specific companies.",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"industry": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"pharmaceuticals", "biotechnology", "software",
|
||||
"semiconductors", "banking", "investment",
|
||||
"insurance", "real_estate", "oil_gas", "retail"
|
||||
],
|
||||
"description": "Industry sector to analyze"
|
||||
},
|
||||
"include_top_companies": {
|
||||
"type": "boolean",
|
||||
"description": "Include list of major companies in the sector",
|
||||
"default": True
|
||||
},
|
||||
"limit": {
|
||||
"type": "integer",
|
||||
"description": "Number of top companies to show (by filing activity)",
|
||||
"default": 10
|
||||
}
|
||||
},
|
||||
"required": ["industry"]
|
||||
}
|
||||
),
|
||||
Tool(
|
||||
name="edgar_compare_industry_companies",
|
||||
description="Compare financial performance of companies within an industry sector. Automatically selects top companies or accepts custom company list for side-by-side financial comparison.",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"industry": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"pharmaceuticals", "biotechnology", "software",
|
||||
"semiconductors", "banking", "investment",
|
||||
"insurance", "real_estate", "oil_gas", "retail"
|
||||
],
|
||||
"description": "Industry sector to analyze"
|
||||
},
|
||||
"companies": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"description": "Optional: Specific tickers to compare (e.g., ['AAPL', 'MSFT', 'GOOGL']). If omitted, uses top companies by market presence.",
|
||||
"default": None
|
||||
},
|
||||
"limit": {
|
||||
"type": "integer",
|
||||
"description": "Number of companies to compare if not specified (default 5, max 10)",
|
||||
"default": 5
|
||||
},
|
||||
"periods": {
|
||||
"type": "integer",
|
||||
"description": "Number of periods for comparison (default 3)",
|
||||
"default": 3
|
||||
},
|
||||
"annual": {
|
||||
"type": "boolean",
|
||||
"description": "Annual (true) or quarterly (false) comparison",
|
||||
"default": True
|
||||
}
|
||||
},
|
||||
"required": ["industry"]
|
||||
}
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
@app.call_tool()
|
||||
async def call_tool(name: str, arguments: dict[str, Any] | None) -> list[TextContent]:
|
||||
"""Handle tool calls."""
|
||||
if arguments is None:
|
||||
arguments = {}
|
||||
|
||||
try:
|
||||
if name == "edgar_company_research":
|
||||
from edgar.ai.mcp.tools.company_research import handle_company_research
|
||||
return await handle_company_research(arguments)
|
||||
elif name == "edgar_analyze_financials":
|
||||
from edgar.ai.mcp.tools.financial_analysis import handle_analyze_financials
|
||||
return await handle_analyze_financials(arguments)
|
||||
elif name == "edgar_industry_overview":
|
||||
from edgar.ai.mcp.tools.industry_analysis import handle_industry_overview
|
||||
return await handle_industry_overview(arguments)
|
||||
elif name == "edgar_compare_industry_companies":
|
||||
from edgar.ai.mcp.tools.industry_analysis import handle_compare_industry_companies
|
||||
return await handle_compare_industry_companies(arguments)
|
||||
else:
|
||||
raise ValueError(f"Unknown tool: {name}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Error in tool %s: %s", name, e)
|
||||
return [TextContent(
|
||||
type="text",
|
||||
text=f"Error: {str(e)}"
|
||||
)]
|
||||
|
||||
|
||||
@app.list_resources()
|
||||
async def list_resources() -> list[Resource]:
|
||||
"""List available resources."""
|
||||
return [
|
||||
Resource(
|
||||
uri="edgartools://docs/quickstart",
|
||||
name="EdgarTools Quickstart Guide",
|
||||
description="Quick start guide for using EdgarTools",
|
||||
mimeType="text/markdown"
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
@app.read_resource()
|
||||
async def read_resource(uri: str) -> str:
|
||||
"""Read a resource."""
|
||||
if uri == "edgartools://docs/quickstart":
|
||||
return """# EdgarTools Quickstart
|
||||
|
||||
## Basic Usage
|
||||
|
||||
```python
|
||||
from edgar import Company, get_current_filings
|
||||
|
||||
# Get company information
|
||||
company = Company("AAPL")
|
||||
print(f"{company.name} - CIK: {company.cik}")
|
||||
|
||||
# Get filings
|
||||
filings = company.get_filings(form="10-K", limit=5)
|
||||
for filing in filings:
|
||||
print(f"{filing.form} - {filing.filing_date}")
|
||||
|
||||
# Get current filings across all companies
|
||||
current = get_current_filings(limit=20)
|
||||
for filing in current.data.to_pylist():
|
||||
print(f"{filing['company']} - {filing['form']}")
|
||||
```
|
||||
|
||||
## Available Tools
|
||||
|
||||
- **edgar_get_company**: Get detailed company information
|
||||
- **edgar_current_filings**: Get the latest SEC filings
|
||||
|
||||
## Example Queries
|
||||
|
||||
- "Get information about Apple Inc including recent financials"
|
||||
- "Show me the 20 most recent SEC filings"
|
||||
- "Find current 8-K filings"
|
||||
"""
|
||||
else:
|
||||
raise ValueError(f"Unknown resource: {uri}")
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point for MCP server."""
|
||||
try:
|
||||
# Get package version for server version
|
||||
from edgar.__about__ import __version__
|
||||
|
||||
# Configure EDGAR identity from environment
|
||||
setup_edgar_identity()
|
||||
|
||||
async def run_server():
|
||||
"""Run the async MCP server."""
|
||||
logger.info(f"Starting EdgarTools MCP Server v{__version__}")
|
||||
|
||||
# Use stdio transport
|
||||
async with stdio_server() as (read_stream, write_stream):
|
||||
await app.run(
|
||||
read_stream,
|
||||
write_stream,
|
||||
InitializationOptions(
|
||||
server_name="edgartools",
|
||||
server_version=__version__, # Sync with package version
|
||||
capabilities=app.get_capabilities(
|
||||
notification_options=NotificationOptions(),
|
||||
experimental_capabilities={}
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
asyncio.run(run_server())
|
||||
|
||||
except KeyboardInterrupt:
|
||||
logger.info("Server stopped by user")
|
||||
except Exception as e:
|
||||
logger.error(f"Server error: {e}", exc_info=True)
|
||||
raise
|
||||
|
||||
|
||||
def test_server():
|
||||
"""Test that MCP server is properly configured and ready to run.
|
||||
|
||||
Returns:
|
||||
bool: True if all checks pass, False otherwise
|
||||
"""
|
||||
import sys
|
||||
|
||||
print("Testing EdgarTools MCP Server Configuration...\n")
|
||||
|
||||
all_passed = True
|
||||
|
||||
# Test 1: EdgarTools import check
|
||||
try:
|
||||
from edgar import Company
|
||||
from edgar.__about__ import __version__
|
||||
print(f"✓ EdgarTools v{__version__} imports successfully")
|
||||
except ImportError as e:
|
||||
print(f"✗ EdgarTools import error: {e}")
|
||||
print(" Install with: pip install edgartools")
|
||||
all_passed = False
|
||||
|
||||
# Test 2: MCP framework check
|
||||
try:
|
||||
from mcp.server import Server
|
||||
print("✓ MCP framework available")
|
||||
except ImportError as e:
|
||||
print(f"✗ MCP framework not installed: {e}")
|
||||
print(" Install with: pip install edgartools[ai]")
|
||||
all_passed = False
|
||||
|
||||
# Test 3: Identity configuration check
|
||||
identity = os.environ.get('EDGAR_IDENTITY')
|
||||
if identity:
|
||||
print(f"✓ EDGAR_IDENTITY configured: {identity}")
|
||||
else:
|
||||
print("⚠ EDGAR_IDENTITY not set (recommended)")
|
||||
print(" Set with: export EDGAR_IDENTITY=\"Your Name your@email.com\"")
|
||||
print(" Or configure in MCP client's env settings")
|
||||
|
||||
# Test 4: Quick functionality test
|
||||
try:
|
||||
from edgar import get_current_filings
|
||||
print("✓ Core EdgarTools functionality available")
|
||||
except Exception as e:
|
||||
print(f"✗ EdgarTools functionality check failed: {e}")
|
||||
all_passed = False
|
||||
|
||||
# Summary
|
||||
print()
|
||||
if all_passed:
|
||||
print("✓ All checks passed - MCP server is ready to run")
|
||||
print("\nTo start the server:")
|
||||
print(" python -m edgar.ai")
|
||||
print(" or")
|
||||
print(" edgartools-mcp")
|
||||
return True
|
||||
else:
|
||||
print("✗ Some checks failed - please fix the issues above")
|
||||
return False
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
# Check for --test flag
|
||||
if "--test" in sys.argv or "-t" in sys.argv:
|
||||
sys.exit(0 if test_server() else 1)
|
||||
else:
|
||||
main()
|
||||
Reference in New Issue
Block a user