Skip to main content
Glama
douglashiga

MCP Finance Server

by douglashiga

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)

  1. get_wheel_put_candidates(symbol='Nordea', market='sweden', delta_min=0.25, delta_max=0.35, dte_min=4, dte_max=10)

  2. analyze_wheel_put_risk(symbol='Nordea', pct_below_spot=5.0, target_dte=7)

  3. get_wheel_contract_capacity(symbol='Nordea', capital_sek=200000, market='sweden')

End-to-end Example 2 (Event Risk Window)

  1. get_wheel_event_risk_window(ticker='NDA-SE.ST', market='sweden', days_ahead=14)

  2. get_corporate_events(ticker='NDA-SE.ST', market='sweden')

  3. 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_prices

  • fundamentals, dividends

  • stock_metrics, market_movers

  • option_metrics

  • option_iv_snapshots (IV history snapshots from option metrics)

  • exchanges, market_indices

  • sector_taxonomy, industry_taxonomy, subindustry_taxonomy

  • stock_classification_snapshots, company_profiles

  • raw_earnings_events, earnings_events

  • raw_market_events, market_events

  • stock_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, and get_option_greeks are 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.

  • 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 for help.

  • get_server_health()

    • Returns health payload equivalent to /health.

  • get_server_metrics(output_format='json'|'prometheus')

    • Returns metrics payload equivalent to /metrics.

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.

  1. Prefer intent-specific tools over mega-tools.

  2. Keep safe defaults (market='sweden', low-risk limit, bounded windows).

  3. Clamp limits and normalize known enum-style fields.

  4. Return explicit criteria and empty_reason for zero-result queries.

  5. Empty result is not a system error (success=true, data=[]).

  6. Include as_of_date or as_of_datetime when data is snapshot-based.

  7. Do not fabricate unavailable backend data; return uncertainty or insufficient-data states.

  8. Keep response keys stable; avoid breaking renames.

  9. Keep table schema stable when possible; evolve at query/service layer.

  10. 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:

  1. Start by calling get_market_capabilities() for intent routing.

  2. For unknown tools, call describe_tool(tool_name) before execution.

  3. Prefer intent-specific tools over broad queries.

  4. Always use explicit market defaults (market='sweden') unless user overrides.

  5. Respect uncertainty: if backend has no data, return insufficient-data response.

  6. Never infer timestamps; use meta.asof from tool responses.

  7. In Yahoo-only mode (IB_ENABLED=false), avoid IB tools and fallback to local/Yahoo tools.

  8. For sensitive info, keep get_account_summary(masked=True) unless user explicitly requests otherwise in trusted environment.

  9. If a tool returns validation_error, fix input and retry once with corrected parameters.

  10. 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:

  • corporate

  • macro

  • monetary_policy

  • geopolitical

  • market_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_endpoint decorator).

  • 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_disabled errors.

  • IBKR + Yahoo:

    • IB_ENABLED=true (default)

    • Requires IB Gateway/TWS connectivity and market data permissions.

Environment Variables (Reference)

  • IB_ENABLED:

    • true|false (default true)

  • MCP_TRANSPORT:

    • sse|stdio (default sse)

  • MCP_HOST:

    • default 0.0.0.0

  • MCP_PORT:

    • default 8000

  • DEFAULT_MARKET:

    • default sweden

  • DEFAULT_MARKET_TIMEZONE:

    • default Europe/Stockholm

  • MCP_TOOL_ALLOWLIST:

    • comma-separated tool names; if set, only listed tools are callable

  • CIRCUIT_BREAKER_FAILURE_THRESHOLD:

    • default 4

  • CIRCUIT_BREAKER_COOLDOWN_SECONDS:

    • default 45

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_KEY for admin API endpoints.

  • Keep DATALOADER_ALLOW_INSECURE=true only 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 resource finance://health

  • get_server_metrics(output_format='json'|'prometheus') and resource finance://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-commit hooks configured (ruff, black, isort, mypy)

  • GitHub Actions CI (.github/workflows/ci.yml) running lint, type-check and tests

  • Recommended release flow:

    • bump version in pyproject.toml

    • create git tag (vX.Y.Z)

    • update changelog/release notes

Operational Notes

  • If screener returns empty, run:

    • Historical Prices Loader

    • Calculate Stock Metrics

    • Update Market Movers

  • If Wheel IV analysis reports insufficient history, run option metrics jobs and Snapshot Option IV for 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.json and run Load 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.

  • 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/running runs are auto-recovered to failed.

  • 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 4d0292f or newer.

  • If seed fails due missing deps locally, install project dependencies first:

    • pip install -e .

License

MIT

-
license - not tested
-
quality - not tested
-
maintenance - not tested

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/douglashiga/MCP-Finance-Server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server