MCP Finance Server
Enables market data, fundamentals, screener analytics, options analytics, and Wheel strategy workflows with a local cached pipeline, defaulting to the Swedish market.
README
MCP Finance Server
MCP server for market data, fundamentals, screener analytics, options analytics, and Wheel strategy workflows.
This project combines:
- Interactive Brokers (real-time market connectivity)
- Yahoo Finance (broad coverage for fundamentals/history)
- Local ELT pipeline (normalized + cached tables for reliable LLM queries)
Default market for LLM-facing analytics is sweden.
Core Goals
- Stable, LLM-friendly tools with explicit defaults and predictable response envelopes.
- Reliable cached analytics from local DB first, with controlled fallbacks.
- Fast answers for practical trading questions (screener, options, Wheel).
- Clear data lineage from raw ingestion to curated analytics tables.
Why This Server vs Others
- IBKR real-time + Yahoo + local curated cache (not only direct Yahoo API reads).
- Wheel-first toolset (put selection, capacity, assignment flow, covered call continuation).
- Options with greeks + option metrics snapshots for IV and screening.
- Event risk modeling focused on Wheel impact windows.
- Local pipeline with deterministic serial jobs and MCP-friendly discovery tools.
End-to-end Example 1 (Wheel Put Selection)
get_wheel_put_candidates(symbol='Nordea', market='sweden', delta_min=0.25, delta_max=0.35, dte_min=4, dte_max=10)analyze_wheel_put_risk(symbol='Nordea', pct_below_spot=5.0, target_dte=7)get_wheel_contract_capacity(symbol='Nordea', capital_sek=200000, market='sweden')
End-to-end Example 2 (Event Risk Window)
get_wheel_event_risk_window(ticker='NDA-SE.ST', market='sweden', days_ahead=14)get_corporate_events(ticker='NDA-SE.ST', market='sweden')get_monetary_policy_events(market='sweden')
Architecture
MCP-Finance-Server/
├── mcp_server.py # MCP tool registry and entrypoint
├── services/
│ ├── market_service.py # Price lookup and symbol resolution
│ ├── option_service.py # IB option chain + greeks
│ ├── option_screener_service.py # Cached option metrics queries
│ ├── screener_service.py # Stock screener + rankings
│ ├── classification_service.py # Sector/subsector/core business/earnings
│ ├── wheel_service.py # Wheel analytics (puts, calls, risk, stress)
│ └── job_service.py # ELT job controls
├── dataloader/
│ ├── models.py # SQLAlchemy models
│ ├── seed.py # DB seed + default jobs
│ ├── scheduler.py # Cron-like job runner
│ └── scripts/ # Extract/transform/curation scripts
└── README.md
Data Model Highlights
Key normalized/curated tables used by MCP tools:
stocks,realtime_prices,historical_pricesfundamentals,dividendsstock_metrics,market_moversoption_metricsoption_iv_snapshots(IV history snapshots from option metrics)exchanges,market_indicessector_taxonomy,industry_taxonomy,subindustry_taxonomystock_classification_snapshots,company_profilesraw_earnings_events,earnings_eventsraw_market_events,market_eventsstock_intelligence_snapshots(local cache for news/holders/recommendations/statements)
MCP Tool Groups
Market and Fundamentals
get_stock_price(symbol, exchange=None, currency='USD')get_historical_data(symbol, duration='1 D', bar_size='1 hour', exchange=None, currency='USD')get_historical_data_cached(symbol, period='1y', interval='1d')search_symbol(query)get_fundamentals(symbol)get_dividends(symbol)get_dividend_history(symbol, period='2y')get_company_info(symbol)get_financial_statements(symbol)get_comprehensive_stock_info(symbol)get_exchange_info(symbol)yahoo_search(query)
Notes:
get_stock_price,get_historical_data,search_symbol,get_option_chain, andget_option_greeksare DB-first (pipeline-backed snapshots).- IB live connection remains required for account/portfolio endpoints and ingestion jobs.
IB Account and Portfolio
get_account_summary(masked=True)- Resource:
finance://account/summary(masked by default) - Resource:
finance://portfolio/positions
Stock Screener
get_stock_screener(market='sweden', sector=None, sort_by='perf_1d', limit=50)get_top_gainers(market='sweden', period='1D', limit=10)get_top_losers(market='sweden', period='1D', limit=10)get_most_active_stocks(market='sweden', period='1D', limit=10)get_top_dividend_payers(market='sweden', sector=None, limit=10)get_technical_signals(market='sweden', signal_type='oversold', limit=20)get_highest_rsi(market='sweden', limit=10)get_lowest_rsi(market='sweden', limit=10)get_fundamental_rankings(market='sweden', metric='market_cap', limit=10, sector=None)
Classification and Earnings
get_companies_by_sector(market='sweden', sector=None, industry=None, subindustry=None, limit=50)get_company_core_business(symbol)get_earnings_events(symbol=None, market='sweden', upcoming_only=False, limit=20)
Options (Cached + Live)
get_option_chain(symbol)get_option_greeks(symbol, last_trade_date, strike, right)get_option_screener(symbol=None, expiry=None, right=None, min_delta=None, max_delta=None, min_iv=None, max_iv=None, has_liquidity=True, limit=50)get_option_chain_snapshot(symbol, expiry=None)get_options_data(symbol, expiration_date=None)
Market Intelligence (Local Cache)
get_news(symbol, limit=10)get_institutional_holders(symbol, limit=50)get_analyst_recommendations(symbol, limit=50)get_technical_analysis(symbol, period='1y')get_sector_performance(symbols)
Wheel Strategy Tools
get_wheel_put_candidates(symbol, market='sweden', delta_min=0.25, delta_max=0.35, dte_min=4, dte_max=10, limit=5, require_liquidity=True)get_wheel_put_annualized_return(symbol, market='sweden', target_dte=7)get_wheel_contract_capacity(symbol, capital_sek, market='sweden', strike=None, margin_requirement_pct=1.0, cash_buffer_pct=0.0, target_dte=7)analyze_wheel_put_risk(symbol, market='sweden', pct_below_spot=5.0, target_dte=7)get_wheel_assignment_plan(symbol, assignment_strike, premium_received, market='sweden')get_wheel_covered_call_candidates(symbol, average_cost, market='sweden', delta_min=0.25, delta_max=0.35, dte_min=4, dte_max=21, min_upside_pct=1.0, limit=5)compare_wheel_premiums(symbol_a, symbol_b, market='sweden', delta_min=0.25, delta_max=0.35, dte_min=4, dte_max=10)evaluate_wheel_iv(symbol, market='sweden', lookback_days=90, high_iv_threshold_percentile=70.0, target_dte=7)simulate_wheel_drawdown(symbol, strike, premium_received, drop_percent=10.0, market='sweden')compare_wheel_start_timing(symbol, market='sweden', wait_drop_percent=3.0, delta_min=0.25, delta_max=0.35, dte_min=4, dte_max=10)build_wheel_multi_stock_plan(capital_sek, symbols=None, market='sweden', delta_min=0.25, delta_max=0.35, dte_min=4, dte_max=10, margin_requirement_pct=1.0, cash_buffer_pct=0.10)stress_test_wheel_portfolio(capital_sek, sector_drop_percent=20.0, symbols=None, market='sweden', delta_min=0.25, delta_max=0.35, dte_min=4, dte_max=10)
Event Calendar Tools
get_event_calendar(market='sweden', category=None, event_type=None, ticker=None, start_date=None, end_date=None, min_volatility_impact='low', limit=50)get_corporate_events(market='sweden', ticker=None, start_date=None, end_date=None, min_volatility_impact='low', limit=50)get_macro_events(market='sweden', start_date=None, end_date=None, min_volatility_impact='low', limit=50)get_monetary_policy_events(market='sweden', start_date=None, end_date=None, min_volatility_impact='low', limit=50)get_geopolitical_events(market='sweden', start_date=None, end_date=None, min_volatility_impact='low', limit=50)get_market_structure_events(market='sweden', start_date=None, end_date=None, min_volatility_impact='low', limit=50)get_wheel_event_risk_window(ticker, market='sweden', days_ahead=14, limit=100)
Local Cached DB Tools
get_earnings_history(symbol, limit=10)get_earnings_calendar(symbol)query_local_stocks(country=None, sector=None)query_local_fundamentals(symbol)
Job and Pipeline Management
list_jobs()get_job_logs(job_name, limit=5)trigger_job(job_name)toggle_job(job_name, active)get_job_status()run_pipeline_health_check()
Capability Discovery
get_market_capabilities()- Returns grouped capabilities (
methods+examples) so an LLM can answer "what can you do?" with concrete actions.
- Returns grouped capabilities (
describe_tool(tool_name)- Returns parameter schema (types/defaults), Pydantic JSON schema (when available), docstring summary and examples for one tool.
help_tool(tool_name)- Alias for
describe_tool, useful for agents that ask forhelp.
- Alias for
get_server_health()- Returns health payload equivalent to
/health.
- Returns health payload equivalent to
get_server_metrics(output_format='json'|'prometheus')- Returns metrics payload equivalent to
/metrics.
- Returns metrics payload equivalent to
Health and Metrics Resources
- Resource:
finance://status - Resource:
finance://health - Resource:
finance://metrics
LLM Service Design Rules
Use these rules when adding/updating tools so other agents can depend on stable behavior.
- Prefer intent-specific tools over mega-tools.
- Keep safe defaults (
market='sweden', low-risklimit, bounded windows). - Clamp limits and normalize known enum-style fields.
- Return explicit
criteriaandempty_reasonfor zero-result queries. - Empty result is not a system error (
success=true,data=[]). - Include
as_of_dateoras_of_datetimewhen data is snapshot-based. - Do not fabricate unavailable backend data; return uncertainty or insufficient-data states.
- Keep response keys stable; avoid breaking renames.
- Keep table schema stable when possible; evolve at query/service layer.
- Protect admin endpoints with API key and strict script-path validation.
Agent Directives (MCP-friendly)
Use these directives when wiring another AI agent to this server:
- Start by calling
get_market_capabilities()for intent routing. - For unknown tools, call
describe_tool(tool_name)before execution. - Prefer intent-specific tools over broad queries.
- Always use explicit market defaults (
market='sweden') unless user overrides. - Respect uncertainty: if backend has no data, return insufficient-data response.
- Never infer timestamps; use
meta.asoffrom tool responses. - In Yahoo-only mode (
IB_ENABLED=false), avoid IB tools and fallback to local/Yahoo tools. - For sensitive info, keep
get_account_summary(masked=True)unless user explicitly requests otherwise in trusted environment. - If a tool returns
validation_error, fix input and retry once with corrected parameters. - If a source is open-circuited (
circuit_open), avoid repeated retries until cooldown.
Event Modeling Rules
Use unified events in market_events with this minimum contract:
- Identification:
event_id,event_type,category,subtype - Time:
event_datetime_utc,timezone,market,is_market_hours,is_pre_market,is_after_market - Scope:
ticker,sector,country,region,affected_markets - Impact:
expected_volatility_impact,systemic_risk_level,is_recurring,confidence_score - Specific data:
expected_eps,previous_eps,expected_revenue,previous_value,forecast_value,actual_value
Supported categories:
corporatemacromonetary_policygeopoliticalmarket_structure
Stable Response Envelope
Preferred shape:
{
"success": true,
"data": {},
"error": {
"code": null,
"message": null,
"details": null
},
"meta": {
"source": "ibkr|yahoo|local_db|pipeline|system|...",
"asof": "2026-02-13T12:34:56.000000+00:00",
"cache_ttl": null,
"request_id": "uuid",
"default_market": "sweden",
"market_timezone": "Europe/Stockholm"
}
}
Notes:
- The envelope is normalized in
mcp_server.py(tool_endpointdecorator). - Existing service-specific keys (
count,criteria, etc.) are preserved.
Wheel Analytics Formulas
Used in wheel_service.py and exposed by MCP tools:
- Put period return (%) =
(premium / strike) * 100 - Annualized return (%) =
period_return * (365 / DTE) - Cash-secured capital per contract =
strike * 100 - Capacity =
floor((capital * (1-cash_buffer_pct)) / (strike * 100 * margin_requirement_pct)) - Break-even (short put) =
strike - premium - Assignment probability proxy =
abs(delta) - Drawdown scenario at expiry =
max(0, break_even - final_price)
Notes:
abs(delta)is a proxy, not a true probability model.- Timing comparison tools return scenario analysis with explicit uncertainty (no forecasts).
Data Pipeline Jobs (Key)
Major jobs in dataloader/seed.py include:
- Raw ingestion: Yahoo prices/fundamentals, IBKR prices, IBKR instruments, option metrics.
- Normalization: prices, fundamentals, classification taxonomy, company profiles.
- Curation: earnings events.
- Analytics: stock metrics, market movers, option IV snapshots, event calendar.
- Intelligence cache: news, institutional holders, analyst recommendations, financial statements snapshots.
- Loaders: stock list, reference data, dividends, historical prices, index performance.
- Validation: pipeline health check.
Seed behavior (python -m dataloader.seed):
- Idempotent sync of default jobs: missing jobs are created and existing jobs are updated (description/script/cron/timeout/tables) without duplicating rows.
- Full first load runs by default and always in serial (no parallel job execution).
- Load order is phase-based to maximize robustness:
MASTER: reference/tickers/raw+transform/history/dividends/earnings/classification.IB: IBKR extractors/options (still serial, can be skipped).COMPUTE: metrics/movers/events/intelligence snapshots.
- First-load dataset includes:
- historical prices (5y),
- dividends (5y),
- earnings (10y) + curation,
- classification normalization + company profile enrichment,
- stock metrics + market movers,
- normalized event calendar generation.
- market intelligence snapshots.
- Built-in fallback: if screener baseline remains empty, it retries historical load + metrics/movers.
- Optional flags:
--skip-first-load: only init DB + sync jobs.--skip-ib: skip IB-dependent steps during first load.--warmup: deprecated alias (first load is already default).
Quick Start
5-minute onboarding (plug-and-play)
pip install -e .
python -m dataloader.seed --skip-ib
IB_ENABLED=false mcp-finance
Then point your MCP client to this server (examples below).
Docker
docker compose up -d
docker compose --profile init run seed
Local
pip install -e .
python -m dataloader.seed
mcp-finance
python -m dataloader.app
Runtime Profiles
Yahoo-only:IB_ENABLED=false- No IB connection attempt on startup.
- IB tools return structured
ib_disablederrors.
IBKR + Yahoo:IB_ENABLED=true(default)- Requires IB Gateway/TWS connectivity and market data permissions.
Environment Variables (Reference)
IB_ENABLED:true|false(defaulttrue)
MCP_TRANSPORT:sse|stdio(defaultsse)
MCP_HOST:- default
0.0.0.0
- default
MCP_PORT:- default
8000
- default
DEFAULT_MARKET:- default
sweden
- default
DEFAULT_MARKET_TIMEZONE:- default
Europe/Stockholm
- default
MCP_TOOL_ALLOWLIST:- comma-separated tool names; if set, only listed tools are callable
CIRCUIT_BREAKER_FAILURE_THRESHOLD:- default
4
- default
CIRCUIT_BREAKER_COOLDOWN_SECONDS:- default
45
- default
MCP Client Config Examples
Claude Desktop
{
"mcpServers": {
"finance": {
"command": "mcp-finance",
"env": {
"IB_ENABLED": "false",
"DEFAULT_MARKET": "sweden",
"DEFAULT_MARKET_TIMEZONE": "Europe/Stockholm"
}
}
}
}
Cursor
{
"mcp": {
"servers": {
"finance": {
"command": "mcp-finance",
"args": [],
"env": {
"IB_ENABLED": "true",
"MCP_TRANSPORT": "stdio"
}
}
}
}
}
Windsurf
{
"mcpServers": [
{
"name": "finance",
"command": "mcp-finance",
"env": {
"IB_ENABLED": "false"
}
}
]
}
Minimal Python Agent
import os
import subprocess
env = os.environ.copy()
env["IB_ENABLED"] = "false"
proc = subprocess.Popen(["mcp-finance"], env=env)
print("MCP Finance server PID:", proc.pid)
Security Notes
- Set
DATALOADER_API_KEYfor admin API endpoints. - Keep
DATALOADER_ALLOW_INSECURE=trueonly in local development. - Configure CORS with
DATALOADER_ALLOWED_ORIGINS. - Optional tool allowlist: set
MCP_TOOL_ALLOWLIST=get_market_capabilities,describe_tool,.... - Account summary is masked by default (
get_account_summary(masked=true)). - If running MCP over network transport (
MCP_TRANSPORT=sse), keep host/firewall restricted and use API/auth controls on your MCP client gateway.
Threat Model (What this server does NOT do)
- Does not place orders.
- Does not modify positions.
- Does not submit trades.
- Does not transfer funds.
- Does not provide guaranteed forecasts.
Read-only by design: analytics, screening, events, account inspection, and pipeline control only.
Observability and Operations
get_server_health()and resourcefinance://healthget_server_metrics(output_format='json'|'prometheus')and resourcefinance://metrics- Structured JSON logs per tool call (
request_id, tool, source, latency, success) - Circuit breaker by source (IBKR/Yahoo/local groups) with cooldown
- Built-in tool-level metrics: calls/failures/avg latency/source breakdown
- Rate limiter metrics exposed via IB connection runtime snapshot
Code Quality and Releases
pre-commithooks configured (ruff,black,isort,mypy)- GitHub Actions CI (
.github/workflows/ci.yml) running lint, type-check and tests - Recommended release flow:
- bump
versioninpyproject.toml - create git tag (
vX.Y.Z) - update changelog/release notes
- bump
Operational Notes
- If screener returns empty, run:
Historical Prices LoaderCalculate Stock MetricsUpdate Market Movers
- If Wheel IV analysis reports insufficient history, run option metrics jobs and
Snapshot Option IVfor multiple days. - If option greeks are sparse, ensure IB market data permissions and option subscriptions are active.
- For macro/monetary/geopolitical events, maintain
dataloader/data/manual_events.jsonand runLoad Event Calendar. - To refresh local news/holders/recommendations/statements cache, run
Load Market Intelligence. - To avoid duplicate manual runs, backend and frontend both protect against concurrent triggers for the same job:
- backend returns
"Job '<name>' is already queued/running"when an open run exists, - frontend disables the run button while request is in flight.
- backend returns
- Scheduler execution model is now a single-worker queue:
- all cron/manual triggers are enqueued with
status='queued', - only one job runs at a time globally,
- same job is deduplicated while already
queued/running. - on scheduler startup, orphan
queued/runningruns are auto-recovered tofailed.
- all cron/manual triggers are enqueued with
- Jobs UI uses in-app notifications/toasts and confirmation modal (no browser
alert()flow).
Troubleshooting
- Error during metrics seed like
operands could not be broadcast together with shapes (29,) (30,):- fixed by using robust return series (
pct_change) in volatility calculation instead of brittle array slicing. - ensure you are on commit
4d0292for newer.
- fixed by using robust return series (
- If seed fails due missing deps locally, install project dependencies first:
pip install -e .
License
MIT
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。