Skip to main content

MCP Tool Catalog

53 tools, one server.

Point Claude Desktop, Cursor, or any other Model Context Protocol client at the Oxford Ledge MCP server and these tools become available as typed function calls. Names + descriptions + JSON Schemas are public; the data behind each tool comes from the same pipelines that power the web app.

Quickstart: set up in Claude Desktop below · Source of truth: mcp_package/oxford_ledge_mcp/server.py · Free tier: 100 calls / day per IP.

Set up in Claude Desktop

Install the pip package, mint a key, and add the config below. Note the split: this page lists the hosted catalog (53 tools); the pip package dispatches a 13-tool subset (2 keyless SEC-EDGAR + 2 FRED-keyed + 9 API-key tools) — an agent planning calls against a pip install should plan from the 13, not from every card below.

pip install oxford-ledge-mcp

Install instructions and source: github.com/hs902/oxford-ledge-mcp

Mint a key at Settings → YOUR API KEYS → "+ Create Key" (keys look like ol_live_…; shown once). Then in claude_desktop_config.json:

{
  "mcpServers": {
    "oxford-ledge": {
      "command": "oxford-ledge-mcp",
      "env": {
        "OXFORD_LEDGE_URL": "https://www.oxfordledge.com",
        "OXFORD_LEDGE_API_KEY": "ol_live_YOUR_KEY_HERE",
        "FRED_API_KEY": "optional - only for the two FRED macro tools"
      }
    }
  }
}

get_13f_holdings

What one institutional filer owns: the largest positions in its latest SEC 13F-HR, plus quarter-over-quarter changes. Accepts a numeric CIK or an all-alphabetic ticker (anything else is rejected). Returns {cik, fundName, filingDate, periodOfReport, totalHoldings, totalValue, holdings}. POSITIONS ARE KEYED BY CUSIP, NOT TICKER: each holding is {name (issuer name as filed), cusip, value, shares, type, position_type, lots} -- there is no ticker field, so map CUSIPs yourself if you need symbols. `value` is whole USD (13F values are dollars despite the form's 'x$1000' wording); `position_type` is COM|PRN|PUT|CALL and options/notes are kept separate, so do NOT sum across types. `totalValue` and `totalHoldings` cover the WHOLE filing while `holdings` is truncated to max_holdings (default 50, cap 500), value-ranked. When a prior filing exists the response also carries prevFilingDate and changes:{new_positions, increased, decreased, closed}; that key is ABSENT when only one filing was found. Inverse tools: get_institutional_holders (who holds X) and get_institutional_consensus (what is widely held); ol_13f_filer_analytics for this filer's concentration/turnover. Source: SEC EDGAR 13F-HR, ~45-day quarterly delay. Heavy operation, max 2 concurrent. Typical latency: 3-15s.

Input schema
{
  "type": "object",
  "properties": {
    "fund": {
      "type": "string",
      "description": "Fund CIK number (e.g. 1067983 for Berkshire Hathaway) or fund ticker symbol"
    },
    "max_holdings": {
      "type": "number",
      "description": "Maximum number of holdings to return (default 50)",
      "minimum": 1,
      "maximum": 500
    }
  },
  "required": [
    "fund"
  ]
}

get_activist_stakes

Schedule 13D/13G >5% beneficial-owner filings for a ticker -- event-driven stake-building, unlike quarterly 13F. Returns {ticker, count, filings}, newest first, limit default 50 (hard cap 200). Each filing: ticker, filer_name, filing_date, form_type, shares, percent_of_class (a percent number), purpose, accession_number (build the EDGAR document URL from it), updated_at, plus a derived is_activist that is true IFF form_type contains '13D' -- i.e. it is a FORM-TYPE label, not a judgement: 13D signals active intent (proxy fight, takeover), 13G a passive index/institutional holder. Cache is refreshed from EDGAR when older than 24h; that refresh is bounded to 20s and degrades to serving stale cache on timeout, so an empty result can mean 'nothing on file' or 'first fetch still cold'. Complements get_institutional_consensus. Source: SEC EDGAR. Typical latency: <500ms cached, 2-4s on a cold fetch.

Input schema
{
  "type": "object",
  "properties": {
    "ticker": {
      "type": "string",
      "description": "Stock ticker symbol (e.g. AAPL)"
    },
    "limit": {
      "type": "integer",
      "description": "Max filings to return (default 50)"
    }
  },
  "required": [
    "ticker"
  ]
}

get_anomaly_flags

Run 15 red-flag screens on a ticker (short interest >12%, Altman Z distress, net leverage >4x, negative FCF, negative EV/EBITDA, P/E >50, dividend yield >6%, P/E vs sector median, insider net selling, revenue decline, payout >90%, goodwill >50% of assets, SBC >10% of revenue, declining ROIC, receivables outgrowing revenue). Returns {ticker, flags, count}; each flag is {severity, label, detail, scrollTo} and SEVERITY IS ONLY 'red' OR 'amber' (never low/medium/high), sorted red first. A flag is a question to investigate, NOT a sell signal, and an empty list means the checks did not fire OR the quote/fundamentals fetch returned nothing -- absence is not an all-clear. Three checks (sector-relative P/E, insider selling, ROIC trend) silently skip when their extra inputs are missing. Pair with get_fundamentals and ol_intrinsic_value. Source: vendor quote/fundamentals snapshot plus computed heuristics. Typical latency: 500ms-2s.

Input schema
{
  "type": "object",
  "properties": {
    "ticker": {
      "type": "string",
      "description": "Stock ticker symbol (e.g. AAPL)"
    }
  },
  "required": [
    "ticker"
  ]
}

get_bdc_list

Roster of the ACTIVE BDCs tracked by Oxford Ledge, sorted by portfolio size -- this is our coverage, not the whole BDC universe. No arguments. Returns {bdcs, count}; per BDC: ticker, name, holdingCount, totalFairValue (whole USD, latest filing), filingDate, lastParsed, plus the reconciliation triple reportedTotalFairValue (the filing's OWN stated grand total), fairValueBasis ('parsed-rows' or 'filing-reported') and parsedRowSumFairValue. READ fairValueBasis BEFORE using totalFairValue: when a parse over-counts, totalFairValue is swapped to the filing-reported figure and the raw row sum is preserved in parsedRowSumFairValue, so the two disagreeing numbers are both visible rather than silently reconciled. Wound-down / de-BDC'd issuers are excluded by design, so this is not a historical universe. Borrower-keyed counterpart: search_bdc_borrower. Source: SEC EDGAR BDC filings (Oxford Ledge parse). Latency: <300ms.

Input schema
{
  "type": "object",
  "properties": {}
}

get_bond_data

Single-bond trade detail by CUSIP. Returns a FIXED-KEY envelope: cusip, type, issuer, coupon, maturity, debtType, is144A, lastPrice, lastYield, ytm, high, low, volume, tradeDate, ytw, callable, duration, modifiedDuration, moodysRating, spRating, fitchRating, source, fetchedAt, error (plus `unavailable` on an upstream outage). Every key is ALWAYS present, so presence proves nothing -- check `error` and whether the values are null. `coupon` is a percent number (4.5 means 4.5%), `lastPrice`/high/low are per 100 of face, `ytm`/`lastYield` are percent, and ytm is a locally-computed APPROXIMATION, not a FINRA figure. KNOWN OUTAGE: the FINRA TRACE host this scrapes was retired in 2026-07, so expect an all-null envelope with error set. ol_bond_directory_screen (persisted reference data, no pricing) is the working alternative. Source: FINRA TRACE. Typical latency: 500ms-2s.

Input schema
{
  "type": "object",
  "properties": {
    "cusip": {
      "type": "string",
      "description": "9-character CUSIP identifier (e.g. 037833AK6 for Apple)"
    }
  },
  "required": [
    "cusip"
  ]
}

get_business_summary

First-party 10-K Item-1 business summary for a ticker: a curated, verifier-gated one-shot overview of what the company does, preferred over a generic vendor blurb. Returns a `summary` (a one-line status line, NOT the content), `available`, the `business_summary` prose TRUNCATED AT ~2400 chars, `short_summary`, and provenance (source_form, source_period, source_url, generated_at). When available is false only summary/ticker/business_summary('') come back -- the provenance keys are ABSENT, not null. Honest-empty when no verified summary is on file. WHICH DESCRIPTION TOOL TO PICK: this one for a curated business overview; ol_filing_search for semantic passage retrieval over the full filing corpus (ask a question). Source: SEC EDGAR 10-K Item 1 (Oxford Ledge first-party summary); FREE. Typical latency: <300ms.

Input schema
{
  "type": "object",
  "properties": {
    "ticker": {
      "type": "string",
      "description": "Stock ticker symbol (e.g. AAPL)"
    }
  },
  "required": [
    "ticker"
  ]
}

get_capital_allocation

Where a company sent its cash, up to 10 fiscal years. Returns {ticker, capitalAllocation:{years, dividends, netBuybacks, netDebtChange, acquisitions, sharesOut, isDilutive, summary}}. `years` is newest-first and the rest are PARALLEL ARRAYS aligned to it by index. These are NET, DERIVED series, not raw cash-flow lines: netBuybacks = repurchases minus equity issuance minus share-based comp (so it goes NEGATIVE when a company is a net issuer, which isDilutive[i] mirrors as a boolean), and netDebtChange = repayments minus issuance (positive means net paydown). Gross issuance, gross repayment and SBC are NOT emitted separately. `summary` carries shareCountChange10yr (percent), dilutiveYears, totalDividends, totalNetBuybacks, totalNetDebt, totalAcquisitions. All money is whole USD; sharesOut is a share count. Untagged years are null. Complements get_fundamentals (P&L and balance sheet) and get_debt_maturities (forward ladder). Source: SEC EDGAR XBRL cash-flow tags, 24h cache. Requires Plus tier. Typical latency: 500ms-2s.

Input schema
{
  "type": "object",
  "properties": {
    "ticker": {
      "type": "string",
      "description": "Stock ticker symbol (e.g. AAPL)"
    }
  },
  "required": [
    "ticker"
  ]
}

get_corporate_events

Material 8-K-derived corporate events for a ticker (M&A, executive changes, restructurings, dividends). Returns {ticker, events, count}; THE 20 MOST RECENT EVENTS ONLY -- a hard cap with no caller-settable limit, so this is a recent-events feed, not a history. Each event: id, ticker, eventDate, eventType, headline, description, amount, counterparty, counterpartyTicker, status, sourceUrl, source (camelCase). `amount` is whole USD and is often null. The event_type filter is case-insensitive and accepts a comma-separated list or ALL. Dual-class siblings (GOOG/GOOGL) are unioned. Postgres-only: returns events=[] when PG is unavailable, which is indistinguishable from a genuinely eventless ticker. For Form 4 insider transactions use get_insider_activity. Source: SEC EDGAR 8-K parse; ~24h indexing lag. Typical latency: <500ms.

Input schema
{
  "type": "object",
  "properties": {
    "ticker": {
      "type": "string",
      "description": "Stock ticker symbol (e.g. AAPL)"
    },
    "event_type": {
      "type": "string",
      "description": "Optional filter: acquisition, divestiture, executive_change, restructuring, dividend, or ALL"
    }
  },
  "required": [
    "ticker"
  ]
}

get_debt_maturities

Forward debt maturity ladder parsed from the latest 10-K/20-F footnote. Returns {ticker, maturities:[{year, amount}], thereafter, confidence, confidence_score, source, validation}. AMOUNTS ARE IN MILLIONS OF USD, not raw dollars -- 400 means $400M. `maturities` is a LIST of year/amount pairs (not a year-keyed map), normally the next ~5 years, with everything beyond the table in `thereafter`. `confidence` is high|medium|low|none and `source` is 'table'|'regex'|null -- both describe PARSER certainty, not filer accuracy; `validation` carries {valid, maturity_total, bs_total, diff_pct, warning} cross-checking the ladder against the balance sheet, so check it before quoting a total. An unparseable filer returns maturities=[] with confidence 'none'. For historical issuance/repayment use get_capital_allocation. Source: SEC EDGAR 10-K note extraction. Requires Plus tier. Typical latency: 500ms-2s.

Input schema
{
  "type": "object",
  "properties": {
    "ticker": {
      "type": "string",
      "description": "Stock ticker symbol (e.g. AAPL)"
    }
  },
  "required": [
    "ticker"
  ]
}

get_economic_calendar

Upcoming US macro DATA-RELEASE DATES (a schedule, not estimates). Returns {days, events, count, source_status, error}; each event is only {date, event (short code, e.g. CPI), release_id, release_name}. THERE ARE NO prior/consensus/actual VALUES and no country field -- this answers 'when does CPI print', never 'what will it be'. FOMC MEETING DATES ARE NOT INCLUDED (FRED's release calendar does not carry them); coverage is a curated set of ~6 major BLS/BEA prints (CPI, Employment Situation, GDP, Jobless Claims, PCE, PPI). `days` look-ahead is clamped to 1-180 (default 90); BLS publishes ~1 month out and BEA ~1 quarter, so a longer window adds little. ALWAYS CHECK source_status (fresh|circuit_open|no_api_key|error) -- every failure mode returns events=[] with count 0, which is otherwise indistinguishable from a quiet calendar. Cached 6h. Source: FRED releases/dates. Typical latency: <500ms.

Input schema
{
  "type": "object",
  "properties": {
    "days": {
      "type": "integer",
      "description": "Number of days to look ahead (default 90)"
    }
  }
}

get_fails_to_deliver

SEC fails-to-deliver history for one ticker -- the settlement-failure side of short pressure. Returns {ticker, days, history, count}; each row is {date (the SETTLEMENT date, as ISO text -- the field is `date`, not settlement_date), fails (SHARES failed, not dollars), price (closing price that day, USD), description (issue name)}, OLDEST-FIRST so it charts left to right. `days` is a trailing window, default 180, hard cap 730. Coverage is sparse by nature: SEC publishes a row only on days a ticker actually had fails, so gaps are normal and an empty history is not an anomaly. Pair with ol_short_interest_trend for the other half. Source: SEC Fails-to-Deliver dataset (published twice monthly, ~2-week lag). Typical latency: <300ms.

Input schema
{
  "type": "object",
  "properties": {
    "ticker": {
      "type": "string",
      "description": "Stock ticker symbol (e.g. GME)"
    },
    "days": {
      "type": "integer",
      "description": "Trailing window in days (default 180, max 730)"
    }
  },
  "required": [
    "ticker"
  ]
}

get_fred_data

CURRENTLY NON-FUNCTIONAL. Intended to return one FRED series by id as {series, data}. The upstream macro fetch returns a LIST of latest-value rows rather than the series-keyed map this handler expects, so every call returns {series, error: 'unexpected fred_data shape'} regardless of the id. Nor would it ever return history: the backing snapshot holds only the newest observation per series. WORKAROUND: call get_yield_curve with include_history=false -- its `data` list is exactly that macro snapshot (Treasury tenors, credit-spread OAS, CPI, UNRATE, GDP, mortgage rate, national debt), each row {series, name, value, date, ytdChange}. Units vary by series (UNRATE percent, GDP billions USD, OAS percent). Typical latency: <300ms (fails fast).

Input schema
{
  "type": "object",
  "properties": {
    "series": {
      "type": "string",
      "description": "FRED series ID (e.g. GDP, UNRATE, CPIAUCSL, DFF)"
    }
  },
  "required": [
    "series"
  ]
}

get_fundamentals

Full XBRL financial history for one ticker. Returns {ticker, fundamentals:{years, metrics, balanceSheet, quarterly}}. `years` is newest-first (up to ~10 annual periods) and EVERY entry under `metrics`/`balanceSheet` is a PARALLEL ARRAY aligned to it by index, not a per-year object: revenue, grossProfit, ebit, netIncome, opCF, capex, da, epsDiluted, sharesOut, totalAssets, totalLiabilities, stockholdersEquity, currentAssets, currentLiabilities, totalDebt, cashAndShortTerm, goodwill, intangibleAssets, plus derived fcf (opCF minus capex), grossMarginPct, ebitMarginPct, currentRatio, quickRatio, debtToAssets. `quarterly` is the LAST 12 QUARTERS ONLY (newest first: period, endDate, revenue, grossProfit, ebit, netIncome, epsDiluted, margins) or null. UNITS: dollar figures are whole USD, epsDiluted is USD/share, every *Pct is a percentage number (12.5 means 12.5%). A year the filer did not tag is null, never 0. Returns {error} when the ticker has no companyfacts. For cash deployment use get_capital_allocation; for per-share fair value use ol_intrinsic_value. Source: SEC EDGAR company-facts XBRL (~45-day post-quarter lag), 24h cache. Latency: 500ms-2s cold, <300ms cached.

Input schema
{
  "type": "object",
  "properties": {
    "ticker": {
      "type": "string",
      "description": "Stock ticker symbol (e.g. AAPL)"
    }
  },
  "required": [
    "ticker"
  ]
}

get_insider_activity

Recent Form 4 insider transactions for one ticker. Returns {ticker, transactions}; THE 20 MOST RECENT FILINGS ONLY, newest first -- and the `days` argument is currently a NO-OP on this path, so it will not widen or narrow the window. Each row: id, ticker, filingDate, insiderName, position (the role, which is the load-bearing signal, not the name), title, transType (raw SEC transaction_code -- 'P' open-market buy, 'S' sale, 'A' grant, 'M' option exercise; there is no plain buy/sell field, so filter on this yourself), shares, pricePerShare, totalValue (whole USD), sharesOwned (holding after the trade), url (SEC filing link). Dual-class siblings are unioned. On failure returns {error}. Market-wide open-market buying: ol_insider_recent_buys; multi-insider clusters: ol_insider_cluster_scan; >5% stakes: get_activist_stakes. Source: SEC EDGAR Form 4 (~2-day filing deadline). Requires Plus tier. Typical latency: 500ms-2s.

Input schema
{
  "type": "object",
  "properties": {
    "ticker": {
      "type": "string",
      "description": "Stock ticker symbol (e.g. AAPL)"
    },
    "days": {
      "type": "integer",
      "description": "Number of days of history (default 365)",
      "minimum": 1,
      "maximum": 1825
    }
  },
  "required": [
    "ticker"
  ]
}

get_institutional_consensus

Which tickers the tracked 13F filers most widely hold in common. Returns {as_of_quarter, tickers_found, min_funds, consensus}; each consensus row is {ticker, issuer_name, fund_count, total_value_usd, total_shares, funds:[{fund_name, fund_cik, shares, value_usd, quarter}]}, ranked by fund_count then value. `consensus` is truncated to top_n (default 50, cap 200) while `tickers_found` is the full count meeting min_funds (default 2, cap 20) -- so tickers_found > len(consensus) is normal, not a bug. Values are whole USD. COMMON STOCK ONLY (position_type COM): options and debt positions are excluded, so this is an ownership tally, not exposure. Each fund contributes only its own latest quarter, which may differ across funds. Answers 'who is buying X?'; get_13f_holdings answers 'what does fund X own?'. Source: SEC EDGAR 13F-HR, ~45-day quarterly delay. Latency: <500ms.

Input schema
{
  "type": "object",
  "properties": {
    "top_n": {
      "type": "number",
      "description": "Maximum number of consensus tickers to return (default 50, max 200)."
    },
    "min_funds": {
      "type": "number",
      "description": "Minimum number of notable funds that must hold a ticker for it to appear (default 2, max 20)."
    }
  },
  "required": []
}

get_institutional_holders

Which institutions hold one ticker, and which way they moved. Returns {ticker, holders, total_holders, as_of_quarter, coverage}. `holders` IS CAPPED AT THE TOP 100 BY VALUE (not caller-settable), while `total_holders` is the TRUE filer count across the whole universe -- so total_holders far exceeding len(holders) is normal. Each holder: fund_name, fund_cik, shares, value_usd (whole DOLLARS), quarter, filing_date, change_type (new|increased|decreased|unchanged), shares_change, pct_change (percent) vs that fund's own prior quarter, and optionally includes_sub_managers. `coverage` = {shares_13f, fund_count, shares_outstanding, pct, quarter, basis, overstated}: check `basis` -- 'universe' means a full aggregate, 'top_display' means the aggregate timed out and the figures describe only the capped list; `overstated` true means pct exceeded 100% and is a data defect, deliberately not clamped. Common stock only; funds file off-cadence so quarters can differ per row. On PG failure returns {ticker, holders: [], error}. Direction-only view: ol_ownership_changes; fund-keyed inverse: get_13f_holdings. Rows are CUSIP-resolved: a resolution failure drops a row or mis-attributes it, so pct measures match completeness, not correctness. Source: SEC EDGAR 13F-HR, ~45-day quarterly delay. Latency: <500ms.

Input schema
{
  "type": "object",
  "properties": {
    "ticker": {
      "type": "string",
      "description": "Stock ticker symbol (e.g. AAPL)"
    }
  },
  "required": [
    "ticker"
  ]
}

get_news

Read the Oxford Ledge news archive by ticker and/or keyword, newest first. Default limit 25, hard cap 100; no pagination offset. THE RESPONSE SHAPE DEPENDS ON THE ARGUMENTS. Ticker only (no query) returns {articles, total} where each article is {headline, url, source, date, datetime, summary (always empty), sentiment}. Any free-text query (with or without a ticker) returns {articles, count} where each article is {id, headline, publisher, author, publishedAt, url, sentiment, sentimentScore, snippet (**bold**-marked match), tickers[], tags[]}. Handle both. UNITS: `sentiment` is a LABEL string ('positive'/'neutral'/'negative'), NOT a number -- the numeric is `sentimentScore`, and it is absent from the ticker-only shape. Query matching is prefix-word, not semantic. Source: Oxford Ledge news archive (multi-provider, cron-populated, ~30K rows/day). Typical latency: 500ms-2s.

Input schema
{
  "type": "object",
  "properties": {
    "query": {
      "type": "string",
      "description": "Search query (e.g. 'tariff', 'earnings beat')"
    },
    "ticker": {
      "type": "string",
      "description": "Filter to a specific ticker (e.g. AAPL)"
    },
    "limit": {
      "type": "number",
      "description": "Max results to return (default 25, max 100)"
    }
  }
}

get_portfolio_positions

Stored positions of one saved portfolio. Returns {portfolio_id, positions, error} on EVERY path (error is null on success, a string on failure, and positions is [] either way). Each position is ONLY {ticker, shares (float), costBasis (float, whole USD, may be null)}, ticker-sorted. THERE IS NO PRICE LEG: no current price, market value, gain/loss or weight is returned or computed -- fetch prices separately and value the positions yourself. An unknown portfolio_id yields positions=[] with error=null, so an empty list does not prove the portfolio exists. For the catalog-wide sector mix use get_sector_breakdown. Source: PG portfolio_positions. Typical latency: <300ms.

Input schema
{
  "type": "object",
  "properties": {
    "portfolio_id": {
      "type": "string",
      "description": "Portfolio identifier (default: 'default')"
    }
  }
}

get_sector_breakdown

How Oxford Ledge's covered universe splits across sectors and industries (the whole catalog, NOT a portfolio). No arguments. Returns a BARE LIST (not an object) of {sector, industry, tickerCount, totalMarketCap}, one row per sector+industry PAIR -- so a single sector spans many rows and you must aggregate to get a sector total. totalMarketCap IS ALWAYS NULL: this is a count-only breakdown, cap-weighting is not implemented, so it cannot answer 'how much of the market is tech'. Rows with a blank sector are excluded. Returns {error} (a dict, not a list) when Postgres is unavailable. For a named portfolio use get_portfolio_positions. Source: PG company_profiles, ~5,200 sectored tickers. Typical latency: <300ms.

Input schema
{
  "type": "object",
  "properties": {}
}

get_value_investing_fact

Curated value-investing quote, principle or historical fact. FIELD NAMES: a fact is {id, quote, author, source, source_year, category, subcategory, era, difficulty, created_at} -- the text is `quote` and the attribution is `author`. THE SHAPE DEPENDS ON THE ARGUMENTS: a `query` returns {facts, count, total_facts} where `facts` IS CAPPED AT 10 while `count` reports the full match set (itself capped at 50), so count > len(facts) is expected; a bare `category` returns {fact, total_facts} (one random pick from it); no arguments returns {fact, total_facts} at random. No match returns {error, total_facts}. Pedagogical content only -- never a market signal. Source: Oxford Ledge curated fact store. Typical latency: <300ms.

Input schema
{
  "type": "object",
  "properties": {
    "category": {
      "type": "string",
      "description": "Optional category filter. The vocabulary is EXACTLY: principle, historical_fact, psychology, quote, case_study, contrarian, mistake. Matched case-insensitively; leave empty for random. An unknown value returns {error, available_categories, total_facts} where available_categories is read from the store, so one retry always lands. CORRECTED 2026-08-11: this list previously named margin_of_safety, intrinsic_value, market_psychology, circle_of_competence, patience and risk_management -- SIX categories that have never existed. Only contrarian was real."
    },
    "query": {
      "type": "string",
      "description": "Optional search query to find facts by keyword (e.g. 'moat', 'fear')"
    }
  }
}

get_yield_curve

US Treasury yield curve, plus (by default) the wider macro dashboard. TWO DIFFERENT SHAPES. include_history=true returns {maturities, current, oneYearAgo, threeMonthsAgo, dates} -- PARALLEL ARRAYS indexed by `maturities` over 11 tenors (1M, 3M, 6M, 1Y, 2Y, 3Y, 5Y, 7Y, 10Y, 20Y, 30Y), giving today, ~91 days ago and ~1 year ago for steepening/inversion work. include_history false (the DEFAULT) returns {data: [...]} -- a flat LIST of latest-value rows {series, name, value, date, ytdChange, _source} that mixes the Treasury tenors WITH credit spreads (HY/IG/AAA/BB/B/CCC OAS), CPI, unemployment, GDP, mortgage rate and national-debt series, so it is a macro snapshot, not just a curve. UNITS: yields and spreads are PERCENT numbers (4.25 means 4.25%), not decimals and not basis points. Cached 4h. Source: Treasury.gov daily par yields (FRED fallback) plus FRED series. Typical latency: <300ms cached.

Input schema
{
  "type": "object",
  "properties": {
    "include_history": {
      "type": "boolean",
      "description": "Include yield curve from 1 year ago for comparison (default false)"
    }
  }
}

ol_13f_filer_analytics

Institutional-filer behavior derived purely from 13F holdings: concentration (top-N %, normalized HHI), turnover, and persistence (long-term-holder vs fast-money) for one filer. Pass a NUMERIC CIK (a ticker will not resolve here). Returns {summary, fund, quarter, n_positions, reported_long_equity_value, analytics}; `analytics` = {concentration:{n, top1_pct, top5_pct, top10_pct, hhi, total_value}, turnover:{latest_quarter, prior_quarter, prior_n_positions, n_opened, n_exited, n_added, n_trimmed, turnover_pct, split_suspect}, persistence:{window_quarters, current_count, long_term_count, short_term_count, long_term_share_pct, long_term[], material_sizing_changes[], min_long_quarters}}. *_pct are percentage numbers; values are whole USD; hhi is normalised. A filer with only one stored quarter gets turnover fields of None/0 rather than a fabricated rate, and an un-ingested CIK returns analytics=null. NO position list is returned -- use get_13f_holdings for that. The denominator is the filer's REPORTED 13F long-equity value, explicitly NOT AUM (a 13F omits shorts/cash/bonds/non-US and lags ~45 days). Complements `get_institutional_holders` (positions) with firm-level behavior. Positions are CUSIP-resolved; a resolution failure drops or mis-attributes a row, so treat aggregates as close approximations. Source: SEC EDGAR 13F-HR (public domain; Oxford Ledge derived); FREE. Typical latency: 300ms-1s.

Input schema
{
  "type": "object",
  "properties": {
    "fund": {
      "type": "string",
      "description": "13F filer CIK (e.g. 1067983)."
    }
  },
  "required": [
    "fund"
  ]
}

ol_bdc_borrower_dispersion

MOAT: cross-lender loan-pricing DISPERSION for one private-credit borrower -- how N different BDCs each price the SAME loan (spread / mark / fair value). The credit-mispricing signal no generic MCP has: when one BDC marks a borrower S+550 @ 98 and another S+575 @ 99, the lenders disagree on the credit. Pass the borrower's canonical `borrower_norm` key (from `ol_bdc_top_borrowers` or `search_bdc_borrower`). Returns {summary, borrower_norm, count, lenders}, ordered widest-spread-first; each lender row is {bdc_ticker, filing_date, security_type, lien_position, spread, marked_price, fair_value}. UNITS TRAP: `spread` is the RAW AS-FILED number and is MIXED-UNIT across filers -- one BDC files 5.75 (percent) for what another files as 575 (bps). Ranking is done on a normalised basis internally, but the emitted `spread` is not normalised, so read each value against its own filer and do NOT average or diff them blind. marked_price is out of 100; fair_value is whole USD. Debt tranches only (equity excluded). Rows are each BDC's most recent filing THAT HOLDS this borrower, so vintages can differ across lenders and a wound-down lender's frozen final filing can still appear. Default 25 rows, hard cap 100. Honest-empty when only one BDC holds the name. Source: SEC EDGAR BDC schedules of investments (Oxford Ledge parse); FREE. Typical latency: <400ms.

Input schema
{
  "type": "object",
  "properties": {
    "borrower_norm": {
      "type": "string",
      "description": "Canonical normalized borrower key (from ol_bdc_top_borrowers or borrower search)."
    },
    "limit": {
      "type": "integer",
      "description": "Max lender positions to return (default 25, hard cap 100)."
    }
  },
  "required": [
    "borrower_norm"
  ]
}

ol_bdc_borrower_news_today

MOAT / daily pulse: recent VERIFIED news for the private-credit borrowers held across MULTIPLE BDCs -- credit-desk situational awareness no generic MCP can assemble. Answers 'what broke recently for the cross-BDC borrowers I should watch?'. Returns {summary, count, items}; each item is {borrower, headline, source, url, date}. Caps: limit default 25 / hard 100; since_days default 7 / hard 90; min_holders default 2 (max 50). Matcher-VERIFIED only (>=0.9 confidence) and NO provider summary (trust contract) -- headline + link only, so a false attribution cannot surface. Honest-empty: no recent indexed headlines returns items:[] with a summary that says absence is not a signal. Source: Google News, matched to SEC EDGAR BDC borrowers; FREE. Typical latency: <600ms.

Input schema
{
  "type": "object",
  "properties": {
    "since_days": {
      "type": "integer",
      "description": "How many days back to look (default 7, hard cap 90)."
    },
    "min_holders": {
      "type": "integer",
      "description": "Minimum number of BDC lenders a borrower must appear in (default 2)."
    },
    "limit": {
      "type": "integer",
      "description": "Max news items to return (default 25, hard cap 100)."
    }
  }
}

ol_bdc_common_borrowers

Borrowers common to a GIVEN SET of BDCs -- the cross-portfolio set question ('what do ARCC, OBDC and AGTC all lend to?'). Every other BDC tool runs borrower -> lenders; this one runs lenders -> shared borrowers, so a portfolio-overlap question takes ONE call instead of N per-borrower calls. Returns per borrower: borrower, borrower_norm, holder_count, holders (the actual BDC tickers, not just a count), total_fair_value + total_par_amount in USD, and as_of_oldest/as_of_newest. Each BDC is read at ITS most recent filing and BDCs file on different calendars, so rows MIX filing dates -- read as_of_range before treating the marks as contemporaneous. Debt positions only (equity stakes are not lending relationships). Ordered most-widely-held first. Caps: bdc_tickers truncated at 25, limit 50/200, min_holders max 50; a min_holders above the number of BDCs supplied is rejected rather than returning a misleading empty list. Feed a returned borrower_norm to ol_bdc_borrower_dispersion for cross-lender pricing. Source: SEC 10-K/10-Q schedules of investments.

Input schema
{
  "type": "object",
  "properties": {
    "bdc_tickers": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "BDC symbols to intersect, e.g. [\"ARCC\",\"OBDC\",\"AGTC\"] (max 25)"
    },
    "min_holders": {
      "type": "number",
      "description": "Minimum number of the supplied BDCs that must hold the borrower (default 2, min 2 -- this answers what is SHARED; for one BDC's book use ol_bdc_top_borrowers)",
      "minimum": 2,
      "maximum": 50
    },
    "limit": {
      "type": "number",
      "description": "Max borrowers to return (default 50, max 200)",
      "minimum": 1,
      "maximum": 200
    }
  },
  "required": [
    "bdc_tickers"
  ]
}

ol_bdc_credit_quality

BDC non-accrual credit-deterioration signal: the share of debt fair value on non-accrual (loans that stopped paying) in the latest filing, plus the trailing-quarter trend -- the earliest public read on a private-credit book going bad. Returns {summary, ticker, latest, trend}. `latest` = {filing_date, flagged_fv, determinate_fv, total_debt_fv, flagged_pct, determinate_coverage}; `trend` is per-quarter OLDEST-FIRST with the same fields plus quarter_key. UNITS: the *_fv figures are whole USD of fair value, flagged_pct is a percentage number, determinate_coverage is a 0-1 fraction. `flagged_pct` is deliberately NULL whenever determinate coverage is under 90% of debt fair value -- a partially-determinate quarter never reports a rate computed over a fraction of the book, so treat null as 'withheld', never as zero. `quarters` default 12, hard cap 24. Unparsed or non-BDC tickers return latest=null, trend=[]. Pairs with ol_bdc_borrower_dispersion and ol_bdc_top_borrowers. Source: SEC EDGAR BDC schedule-of-investments non-accrual flags (Oxford Ledge parse); FREE. Latency <400ms.

Input schema
{
  "type": "object",
  "properties": {
    "ticker": {
      "type": "string",
      "description": "BDC ticker (e.g. ARCC, ORCC, FSK)."
    },
    "quarters": {
      "type": "integer",
      "description": "Trailing quarters of trend (default 12, hard cap 24)."
    }
  },
  "required": [
    "ticker"
  ]
}

ol_bdc_fee_load

BDC fee load + NII-based dividend coverage: base + incentive fees (the dominant expense line for an externally-managed BDC) and honest NII/dividend coverage. Returns {summary, ticker, fiscal_year, fees, dividend_coverage}. `fees` = {base_management_fee, incentive_fee, incentive_fee_income_based, incentive_fee_capital_gains_based, net_investment_income}, all whole USD for ONE fiscal year (a single year, not a series); `dividend_coverage` is NII divided by dividends paid, a ratio (0.92 means 0.92x), and under 1.0 means the dividend is funded partly from capital. Fee RATE percentages and the dividend amount itself are not returned. Honest-empty: dividend_coverage is null when NII or dividends are missing, and an untracked ticker returns fees=null. Source: SEC EDGAR 10-K/10-Q Statement-of-Operations XBRL; income-based incentive fee from the SoO R-file (#124), NOT a vendor fundamentals feed; FREE. <400ms.

Input schema
{
  "type": "object",
  "properties": {
    "ticker": {
      "type": "string",
      "description": "BDC ticker (e.g. ARCC, MAIN, HTGC)."
    }
  },
  "required": [
    "ticker"
  ]
}

ol_bdc_loan_pricing_trend

Per-quarter loan-pricing trend for ONE BDC, from the parsed schedule of investments: fair-value-weighted average credit spread, average mark, and position counts, OLDEST-FIRST. Returns {summary, ticker, count, spread_unit, trend}; each quarter is {filing_date, period_end, quarter_key, debt_positions, priced_loans, priced_borrowers, weighted_avg_spread, avg_marked_price, total_debt_fair_value}. `weighted_avg_spread` is in BASIS POINTS, normalized at the store (the as-filed column is mixed-unit -- one filer writes 5.75 percent for what another writes as 575 bps -- so never average a raw spread yourself); the envelope repeats it as `spread_unit`. avg_marked_price is out of 100; total_debt_fair_value is whole USD. Prefer `priced_borrowers` over `debt_positions` for cross-quarter comparisons: position counts are parser-grain-dependent and can appear to double when the filing dialect changes. `quarters` default 16, max 24. Debt positions only. Honest-empty (trend=[]) for an unparsed BDC. Pairs with ol_bdc_borrower_dispersion. Source: SEC EDGAR BDC 10-Q/10-K schedules of investments (Oxford Ledge parse); FREE. Typical latency: <800ms.

Input schema
{
  "type": "object",
  "properties": {
    "ticker": {
      "type": "string",
      "description": "BDC ticker (e.g. ARCC)."
    },
    "quarters": {
      "type": "integer",
      "description": "Quarters to return (default 16, max 24)."
    }
  },
  "required": [
    "ticker"
  ]
}

ol_bdc_mark_changes

MOAT / private credit: the largest quarter-over-quarter MARK moves across a SET of BDC portfolios -- 'which borrowers got marked up or down the most last quarter, and by whom' in ONE deterministic call. Built because the alternative is stitching it by hand: a model asked this question fanned out per-BDC calls, worked from portfolio AVERAGES (the wrong grain), hit a rate limit, and fabricated a row by copying another BDC's numbers. Returns {increases, decreases}, each a global ranking across every portfolio supplied; each row is {borrower, portfolio (the BDC ticker holding it), prior_mark, latest_mark, mark_delta, prior_filing, latest_filing}. Marks are PERCENT OF PAR, fair-value-weighted across all tranches the BDC holds of that borrower; mark_delta is in percentage points. Grain is BORROWER, not position: security_type is deliberately NOT returned, because rolling up across tranches is what stops a continuously-held borrower reading as both entered and exited when its tranche mix is re-parsed. Each BDC is read at ITS two most recent filings and BDCs file on different calendars, so rows MIX vintages -- read `as_of` before treating the moves as contemporaneous. `coverage` names every BDC that was and was NOT read, with a reason per exclusion (no_prior_quarter / no_parsed_holdings / query_failed / over_ticker_cap), so a partial sweep can never read as a complete one. Caps: bdc_tickers truncated at 25 (the excess is listed in coverage, not dropped silently), limit default 10 / hard 50 per direction. Source: SEC 10-K/10-Q schedules of investments (Oxford Ledge parse); FREE.

Input schema
{
  "type": "object",
  "properties": {
    "bdc_tickers": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "BDC symbols to compare, e.g. [\"ARCC\",\"FSK\",\"OBDC\"] (max 25; the excess is reported in `coverage.not_covered_detail` rather than dropped)"
    },
    "limit": {
      "type": "number",
      "description": "Rows per direction (default 10, hard cap 50). Ranking is GLOBAL across the supplied portfolios, not per BDC."
    }
  },
  "required": [
    "bdc_tickers"
  ]
}

ol_bdc_top_borrowers

MOAT / BDC discovery: the private-credit borrowers syndicated across the MOST BDCs, ranked by lender count then exposure -- the entrypoint for the BDC/private-credit category no generic MCP touches. Pairs with `ol_bdc_borrower_dispersion` (feed a returned `borrower_norm` into it to see cross-lender pricing). Returns {summary, count, borrowers}; each row is {borrower (display name), borrower_norm (the key other ol_bdc_* tools take), holder_count (distinct BDC lenders), total_fair_value (whole USD across lenders, latest filings; null when unpriced), industry}. Caps: limit default 25 / hard 100; min_holders default 2 (max 50). Parser mis-ingests (subtotals, maturity-date and instrument-descriptor rows) are filtered out, so the returned count is clean borrowers only. Source: SEC EDGAR BDC schedules of investments (Oxford Ledge parse); FREE. Typical latency: <400ms.

Input schema
{
  "type": "object",
  "properties": {
    "min_holders": {
      "type": "integer",
      "description": "Minimum number of BDC lenders a borrower must appear in (default 2)."
    },
    "limit": {
      "type": "integer",
      "description": "Max borrowers to return (default 25, hard cap 100)."
    }
  }
}

ol_bond_directory_screen

Screen the corporate-bond browse directory by issuer / CUSIP, grade (IG or HY), and optional coupon and maturity bands. Returns {summary, count, total_matching, bonds}; each bond is {cusip, issuer_name, coupon (percent number, 4.5 means 4.5%), coupon_type, maturity_date (ISO), grade, source_etf}. REFERENCE DATA ONLY -- no price, yield, spread or trade activity. `total_matching` counts the issuer/grade filter across the whole directory, so when a coupon or maturity band is set the bands are applied IN-TOOL over a single fetched page of 200 and `count` can be far below total_matching; `offset` paging is honoured ONLY when no band is set. limit default 25, hard cap 100. NOTE ON PROVENANCE: the directory is sourced from the LQD (investment-grade) and HYG (high-yield) ETF holdings; `grade` is the coarse SOURCE BUCKET (IG=LQD, HY=HYG), NOT a credit rating. Its ingest-source redistribution posture is unresolved (COUNSEL memo 2026-07-07), so this is a FIRST-PARTY-only tool and is off the third-party redistribution surface until cleared. FREE. Typical latency: <400ms.

Input schema
{
  "type": "object",
  "properties": {
    "issuer": {
      "type": "string",
      "description": "Issuer-name substring or CUSIP prefix to match (optional)."
    },
    "grade": {
      "type": "string",
      "description": "Source bucket: 'IG' (LQD universe) or 'HY' (HYG universe). Not a rating."
    },
    "coupon_min": {
      "type": "number",
      "description": "Minimum coupon percent, applied in-tool over the fetched page (optional)."
    },
    "coupon_max": {
      "type": "number",
      "description": "Maximum coupon percent, applied in-tool over the fetched page (optional)."
    },
    "maturity_after": {
      "type": "string",
      "description": "Only bonds maturing on/after this ISO date (YYYY-MM-DD), optional."
    },
    "maturity_before": {
      "type": "string",
      "description": "Only bonds maturing on/before this ISO date (YYYY-MM-DD), optional."
    },
    "limit": {
      "type": "integer",
      "description": "Max bonds to return (default 25, hard cap 100)."
    },
    "offset": {
      "type": "integer",
      "description": "Pagination offset (used only when no coupon/maturity band is set)."
    }
  }
}

ol_borrower_profile

Profile + verified M&A/ownership history for one private-credit borrower. Pass the canonical `borrower_norm` key (as returned by ol_bdc_top_borrowers). Returns {summary, borrower_norm, borrower (canonical name or null), profile, acquisitions, count, public_footprint} -- the `profile` (description, industry, sub_industry, known_lenders, lien_position, estimated_size, notes, source) and `acquisitions`: VERIFIED-only M&A/ownership events (event_type, headline, acquirer, target, sponsor, event_date, deal_size_usd, source_url) -- unaudited or refuted events never surface (trust contract). Also returns `public_footprint`: the borrower's public-market footprint via exact normalized-name matching (patents, federal contracts, corporate events), with `matched_entities` provenance (matched_name + ticker + basis) so a same-name match is never mistaken for a guaranteed identity; empty footprint is the normal answer for a private borrower. Honest-empty when the borrower_norm is unknown. Pairs with ol_bdc_top_borrowers (key source) and ol_bdc_borrower_dispersion (cross-lender pricing). Source: SEC EDGAR BDC Schedule-of-Investments + attributed public M&A events (Oxford Ledge proprietary parse); FREE. Typical latency: <400ms.

Input schema
{
  "type": "object",
  "properties": {
    "borrower_norm": {
      "type": "string",
      "description": "Canonical normalized borrower key (from ol_bdc_top_borrowers)."
    },
    "limit": {
      "type": "integer",
      "description": "Max M&A/ownership events to return (default 20, hard cap 100)."
    }
  },
  "required": [
    "borrower_norm"
  ]
}

ol_cftc_cot

CFTC Commitments-of-Traders positioning. Pass a `market` key for that market's weekly history (NEWEST FIRST), or omit it for the latest report across ALL markets (one row per market). Returns {summary, market, rows}; a series row is {report_date, market_key, market_label, report_type, contract_code, mm_long, mm_short, mm_net, open_interest, source_dataset} (the all-market snapshot omits contract_code and source_dataset). mm_* and open_interest are CONTRACT counts, not dollars, and cover the MANAGED-MONEY category only -- commercials, swap dealers and other reportables are not returned. `limit` (series only) default 52, hard cap 156. Discover a valid `market` key from the all-market snapshot first; an unknown key returns rows=[]. Weekly, published Friday for Tuesday positions. Source: CFTC.gov (public domain); FREE. <400ms.

Input schema
{
  "type": "object",
  "properties": {
    "market": {
      "type": "string",
      "description": "CFTC market key (omit for the latest all-market snapshot)."
    },
    "limit": {
      "type": "integer",
      "description": "Weekly reports for a market series (default 52, hard cap 156)."
    }
  },
  "required": []
}

ol_etf_lookthrough

ETF look-through: expand a list of ETF tickers into their underlying common-stock holdings so an agent can see the real single-name exposures behind a fund position. Pass up to 25 ETF tickers (duplicates dropped, list truncated at 25). Returns {summary, count, etfs}; per ETF {etf, name, category, net_assets, in_catalog, opaque, holdings_available (TOTAL underlying positions on file), top_holdings}. `top_holdings` is value-ranked and capped at top_holdings (default 10, hard cap 50), each {underlying_ticker, issuer_name, value_usd (whole USD), weight_pct (percent of the FULL holdings value, so the shown rows will not sum to 100)}. HOLDINGS-ONLY: yield / NAV / premium are deliberately absent. ETFs that do not file 13F (bond, commodity, levered funds) come back with opaque=true and top_holdings=[] rather than silently empty. Source: SEC EDGAR 13F-HR / NPORT-P, latest quarter only (~45-day lag); FREE. Typical latency: <800ms.

Input schema
{
  "type": "object",
  "properties": {
    "tickers": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "ETF ticker symbols to expand (e.g. ['VOO','QQQ']). Max 25.",
      "maxItems": 25
    },
    "top_holdings": {
      "type": "integer",
      "description": "Max underlying holdings per ETF (default 10, hard cap 50)."
    }
  },
  "required": [
    "tickers"
  ]
}

ol_fdic_bank

FDIC-insured banks and thrifts. Pass `query` for a NAME-PREFIX search (matches the start of the name, not a substring), or omit it for the largest active institutions. Both are ordered largest-asset first. Returns {summary, query?, institutions}; each institution is {cert (FDIC certificate number, the identifier), name, stname, city, bkclass, active, asset, dep, estymd, webaddr, ticker (often blank -- most banks are not listed), repdte (the report date the figures are as of)}. UNITS: `asset` and `dep` are raw FDIC units, i.e. THOUSANDS of dollars -- 3,200,000 means $3.2 billion, not $3.2 million. `limit` default 25, hard cap 100, so the prefix search returns at most 25 matches unless you raise it. Call reports only: no branch, CRA or enforcement data. Source: FDIC.gov (public domain); FREE. <400ms.

Input schema
{
  "type": "object",
  "properties": {
    "query": {
      "type": "string",
      "description": "Institution name prefix (omit for the top-by-assets list)."
    },
    "limit": {
      "type": "integer",
      "description": "Max institutions (default 25, hard cap 100)."
    }
  },
  "required": []
}

ol_federal_contracts

Federal-contract obligation history for a ticker, OR the fiscal-year leaderboard -- for government-revenue-dependence diligence. TWO SHAPES, and one of `ticker` or `fiscal_year` is REQUIRED (neither raises INVALID_PARAMS). With `ticker`: {summary, ticker, obligations} where each row is {id, ticker, fiscal_year, total_obligations_usd (whole USD), entity_count, ueis, recipients, dropped_unresolved (how many awardee entities failed the ticker crosswalk -- read it before treating the total as complete), start_date, end_date, fetched_at}, NEWEST FY FIRST. With `fiscal_year` only: {summary, fiscal_year, leaderboard} of {ticker, fiscal_year, total_obligations_usd, entity_count, fetched_at}, largest first. `limit` default 20, hard cap 100. Obligations are federal awards, not company-reported revenue, and a ticker off the crosswalk honestly returns []. Source: USAspending.gov (public domain; OL ticker-crosswalked); FREE. Typical latency: <400ms.

Input schema
{
  "type": "object",
  "properties": {
    "ticker": {
      "type": "string",
      "description": "Stock ticker (omit for the FY leaderboard)."
    },
    "fiscal_year": {
      "type": "integer",
      "description": "Fiscal year for the top-contractors leaderboard."
    },
    "limit": {
      "type": "integer",
      "description": "Max rows (default 20, hard cap 100)."
    }
  },
  "required": []
}

ol_form_d_raises

Recent SEC Form D private-placement filings -- newest first, optionally scoped to an industry group and/or a trailing filing-date window. Returns {summary, count, days, industry, offerings}; each offering is {accession, submission_type, filing_date, issuer_cik, issuer_name, entity_type, state, industry_group, investment_fund_type, is_equity, is_debt, total_offering_amount, offering_indefinite, total_amount_sold, total_remaining, investors_count, date_of_first_sale, first_sale_yet_to_occur, federal_exemptions, quarter}, NEWEST FIRST. Amounts are whole USD AS DISCLOSED -- total_offering_amount is a ceiling, not money raised (use total_amount_sold), and offering_indefinite means no cap was stated. `limit` default 50, hard cap 200. WITHOUT `days` the read is a top-N over the whole table, so pass `days` before making any 'filed this week' claim. `industry` must match the stored industry_group label exactly. Source: SEC EDGAR Form D (public domain); FREE. Latency <400ms.

Input schema
{
  "type": "object",
  "properties": {
    "industry": {
      "type": "string",
      "description": "Industry group filter (optional)."
    },
    "days": {
      "type": "integer",
      "description": "Trailing filing-date window in days (optional; required for recency claims)."
    },
    "limit": {
      "type": "integer",
      "description": "Max offerings (default 50, hard cap 200)."
    }
  },
  "required": []
}

ol_fundamentals_screen

Threshold screen over a caller-supplied ticker set, on latest-year SEC EDGAR XBRL fundamentals. Optional criteria: min_revenue, min_free_cash_flow, min_ebit_margin_pct, min_gross_margin_pct, max_debt_to_assets_pct, min_current_ratio, min_net_income (dollar thresholds in whole USD, *_pct as percentage numbers). Returns {summary, count, passing_count, criteria, results, source}; each result is {ticker, available, fiscal_year, passes, failed_criteria (the ARGUMENT NAMES that failed), metrics (the same snapshot ol_peer_fundamentals returns)}. A MISSING METRIC FAILS its criterion rather than being skipped, and a ticker without XBRL coverage comes back passes=false with failed_criteria ['no_xbrl_coverage'] -- so a fail can mean 'no data', not 'bad company'. Supplying no criteria makes this a coverage report in which everything passes. Input is truncated to the FIRST 10 unique tickers, silently. It NEVER sweeps a universe -- you bound the candidate set -- so it cannot amplify against SEC fair-access limits, and it is not a discovery screen. A fundamentals filter, NOT a buy signal (no price or valuation leg). Source: SEC EDGAR company-facts XBRL.

Input schema
{
  "type": "object",
  "properties": {
    "tickers": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "2-10 candidate ticker symbols to screen (hard-capped at 10)."
    },
    "min_revenue": {
      "type": "number",
      "description": "Min latest-year revenue (USD)."
    },
    "min_free_cash_flow": {
      "type": "number",
      "description": "Min latest-year free cash flow (USD)."
    },
    "min_ebit_margin_pct": {
      "type": "number",
      "description": "Min EBIT margin (percent)."
    },
    "min_gross_margin_pct": {
      "type": "number",
      "description": "Min gross margin (percent)."
    },
    "max_debt_to_assets_pct": {
      "type": "number",
      "description": "Max debt-to-assets (percent)."
    },
    "min_current_ratio": {
      "type": "number",
      "description": "Min current ratio."
    },
    "min_net_income": {
      "type": "number",
      "description": "Min latest-year net income (USD)."
    }
  },
  "required": [
    "tickers"
  ]
}

ol_insider_cluster_scan

FLAGSHIP MOAT: scan a watchlist for ACTIVE multi-insider cluster-buy/sell signals (distinct-actor Form-4 clusters with statistical strength) -- the single most defensible Oxford Ledge signal, which a generic finance MCP cannot reproduce. Answers 'which of these tickers have insiders clustering into the name right now?'. Pass `tickers` for the watchlist grain, OR a single `ticker` for that name's recent fired clusters. Returns {summary, count, events}; each event is {ticker, direction ('buy'/'sell'), insider_count (distinct actors), z_score (2dp, standard deviations vs the name's own history), sector_z_score, percentile, window_start, window_end}. Caps: events default 25 / hard 100, min_insider_count default 3 (max 50), watchlist since_days default 7 (max 365, ignored on the single-ticker path), tickers list truncated at 100. Honest-empty: no fired clusters returns events=[] with a summary saying so -- absence is not a signal. Source: SEC EDGAR Form 4, windowed on FILING date so it is look-ahead-safe; FREE. Typical latency: <500ms.

Input schema
{
  "type": "object",
  "properties": {
    "tickers": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "Watchlist of ticker symbols (e.g. ['AAPL','MSFT']). Max 100. Use this OR `ticker`.",
      "maxItems": 100
    },
    "ticker": {
      "type": "string",
      "description": "Single ticker for its recent fired clusters (alternative to `tickers`)."
    },
    "since_days": {
      "type": "integer",
      "description": "Watchlist window in days (default 7; ignored for single-ticker grain)."
    },
    "min_insider_count": {
      "type": "integer",
      "description": "Minimum distinct insiders for a fired cluster (default 3)."
    },
    "limit": {
      "type": "integer",
      "description": "Max events to return (default 25, hard cap 100)."
    }
  }
}

ol_insider_recent_buys

Recent OPEN-MARKET insider PURCHASES across the whole market -- a daily insider screen. Returns {summary, since_days, count, buys}; each buy is {id, ticker, filingDate, insiderName, position, title, transType, shares, pricePerShare, totalValue (whole USD), sharesOwned, url (SEC filing)}, NEWEST FIRST. since_days default 30 (hard cap 180), limit default 25 (hard cap 100). SAMPLING TRAP: when the window holds more purchases than `limit`, you get the NEWEST N filings, not the whole window -- so never total these rows and call it the period's insider buying. Open-market purchases only (SEC transaction_code 'P'); option exercises, grants and sales are excluded, as are issuers filing on themselves. Source: SEC EDGAR Form 4 (public domain); FREE. Pairs with ol_insider_cluster_scan (multi-buyer clusters) and get_insider_activity (one ticker).

Input schema
{
  "type": "object",
  "properties": {
    "since_days": {
      "type": "integer",
      "description": "Trailing window in days (default 30, hard cap 180)."
    },
    "limit": {
      "type": "integer",
      "description": "Max purchases (default 25, hard cap 100)."
    }
  },
  "required": []
}

ol_institutional_confluence

Quarter-aligned institutional-confluence read for one ticker: 13F institutional accumulation x insider Form-4 net buying x buy-cluster confirmation, fused on the ticker's reference 13F quarter. Returns a `verdict` (confluence_accumulation / confluence_distribution / partial_bullish / partial_bearish / neutral -- or the honesty outcomes insufficient_13f_coverage / insufficient_history, which are never coerced to neutral), plus the `institutional` fund-count deltas, `insider` buy/sell aggregates, `cluster` confirmation state, and a display-ready `coverage` note, plus a one-line `summary`. This is a DERIVED verdict, not raw data: no per-fund rows are returned. Pairs with get_institutional_holders (raw 13F) and ol_insider_cluster_scan (cluster feed). Source: SEC EDGAR 13F-HR + Form 4 (Oxford Ledge derived fusion); FREE, no tier gate and no AI metering. Typical latency: <900ms.

Input schema
{
  "type": "object",
  "properties": {
    "ticker": {
      "type": "string",
      "description": "Stock ticker symbol, e.g. AAPL."
    }
  },
  "required": [
    "ticker"
  ]
}

ol_intrinsic_value

Per-share intrinsic value from SEC EDGAR XBRL -- three textbook models: a levered-FCF DCF, a Greenwald Earnings-Power-Value, and the Graham number. Returns {summary, ticker, available, dcf_per_share, epv_per_share, graham_number, inputs, assumptions, source}. All three values are USD PER SHARE, rounded to 2dp. `inputs` echoes fcf_latest, shares_out, eps_diluted, book_value_per_share, ebit_5y_avg, net_income_5y_avg, total_debt, cash_and_short_term, fiscal_year; `assumptions` echoes the FIXED, NOT-PER-NAME model constants (10% discount, 2.5% terminal growth, 10y horizon, 21% tax) plus the derived fcf_growth and the fcf_growth_basis string explaining how it was fitted. NO current price, market cap or margin-of-safety is returned by design -- fetch a price yourself and compare. Each leg is an honest null when its inputs are missing or non-positive (Graham is null on negative EPS or book value; DCF null on negative FCF), and available=false means all three failed. The DCF is FCFE-style (FCF = opCF minus capex, discounted straight to equity), so it is not comparable to an enterprise-value DCF. Source: SEC EDGAR company-facts XBRL. Typical latency: <2s on a cold EDGAR fetch.

Input schema
{
  "type": "object",
  "properties": {
    "ticker": {
      "type": "string",
      "description": "Stock ticker symbol (e.g. AAPL)."
    }
  },
  "required": [
    "ticker"
  ]
}

ol_ownership_changes

Quarter-over-quarter institutional accumulation/distribution for one ticker: each fund's position classified new / increased / decreased / exited over the last ~4 quarters -- the 'who is moving on X?' flow read. Returns {summary (one line), ticker, quarters_analyzed, counts, new_positions, increased, decreased, exited}. `counts` holds new/increased/decreased/unchanged/exited tallies, but note the UNCHANGED LIST ITSELF IS NOT RETURNED by this tool (only its count), and neither is the split-suspect list. Each fund entry: fund_name, fund_cik, quarter, prev_quarter, filing_date, shares, prev_shares (as filed), shares_change, pct_change (percent), value_usd (whole USD); exited rows instead carry last_quarter, last_shares, last_value_usd. Split-adjusted before comparison and dual-class unioned; pairs whose delta looks like an unconfirmed corporate action are withheld rather than reported as a huge move. Complements get_institutional_holders (snapshot) with direction. Rows are CUSIP-resolved; a resolution failure drops or mis-attributes a row, so treat share figures as close approximations. Source: SEC EDGAR 13F-HR (public domain; OL derived); FREE. Typical latency: <600ms.

Input schema
{
  "type": "object",
  "properties": {
    "ticker": {
      "type": "string",
      "description": "Stock ticker (e.g. AAPL)."
    }
  },
  "required": [
    "ticker"
  ]
}

ol_patents

Recent USPTO patent filings for a ticker (innovation-intensity diligence). Returns {summary, ticker, count, filings}; each filing is {applicant_name, application_number, publication_number, patent_number, title, filing_date, status, application_type}, NEWEST FIRST. `limit` default 50, hard cap 200 -- so this is a recent slice, never a full portfolio, and `count` is the number RETURNED, not the company's total patent estate. OL alias-resolves the applicant (GOOGL spans Alphabet + Google LLC + DeepMind + Waymo). TRAP: a non-empty patent_number means this application is a continuation of an already-granted PARENT, NOT that this application itself was granted -- read `status` for that. Source: USPTO (public domain); FREE. Typical latency: <400ms.

Input schema
{
  "type": "object",
  "properties": {
    "ticker": {
      "type": "string",
      "description": "Stock ticker (e.g. GOOGL)."
    },
    "limit": {
      "type": "integer",
      "description": "Max filings (default 50, hard cap 200)."
    }
  },
  "required": [
    "ticker"
  ]
}

ol_peer_fundamentals

Side-by-side latest-fiscal-year fundamentals for a peer set. Returns {summary, count, peers, source}; each peer is {ticker, available, fiscal_year, revenue, net_income, eps_diluted, free_cash_flow, gross_margin_pct, ebit_margin_pct, debt_to_assets_pct, current_ratio, stockholders_equity}. UNITS: dollar figures are whole USD, eps_diluted is USD/share, *_pct are percentage numbers, current_ratio is a bare ratio. ONE YEAR ONLY -- no history and no growth rates (use get_fundamentals for a series). Input is truncated to the FIRST 6 unique tickers after normalisation, silently. Fiscal years are each filer's own, so peers with different year-ends are NOT calendar-aligned. A ticker without XBRL coverage returns {ticker, available: false} with no metric keys at all, rather than being dropped. No price leg, no vendor data, so no P/E or EV multiples. Source: SEC EDGAR company-facts XBRL; one cold EDGAR fetch per ticker.

Input schema
{
  "type": "object",
  "properties": {
    "tickers": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "2-6 ticker symbols to compare (hard-capped at 6)."
    }
  },
  "required": [
    "tickers"
  ]
}

ol_short_interest_trend

FINRA short-interest trend for one ticker: the biweekly settlement-date short-percent series, OLDEST-FIRST so it reads left-to-right. Returns {summary, ticker, count, as_of, trend}; each trend point is EXACTLY {date (settlement date, ISO), shortPct (a PERCENTAGE number, e.g. 12.4 for 12.4%)} -- no share counts and no days-to-cover. `points` default 6, max 26 readings (26 fortnights is about one year of coverage). NOTE this is a fortnightly SNAPSHOT with a reporting lag, not a live short-float figure and not days-to-cover; `as_of` is the settlement date, not today. Honest-empty for a ticker FINRA does not publish. Source: FINRA (public); FREE. Typical latency: <400ms.

Input schema
{
  "type": "object",
  "properties": {
    "ticker": {
      "type": "string",
      "description": "Stock ticker (e.g. GME)."
    },
    "points": {
      "type": "integer",
      "description": "Readings to return (default 6, max 26)."
    }
  },
  "required": [
    "ticker"
  ]
}

ol_treasury_debt

US Treasury debt composition from the Monthly Statement of the Public Debt. Omit BOTH args for the newest month's full class breakdown (one row per security class, in statement order); pass security_type AND security_class together for that one class's monthly history. Returns {summary, rows} with rows of {record_date, security_type_desc, security_class_desc, debt_held_public_mil_amt, intragov_hold_mil_amt, total_mil_amt} (the latest-month shape also carries src_line_nbr). AMOUNTS ARE IN MILLIONS OF USD -- a 28,000,000 value means $28 trillion. Because the breakdown contains both component and Total rows, summing a column double-counts; filter by security_class_desc first. `limit` (series only) default 120 months, hard cap 360. Monthly, published a few business days after month-end. Sibling of get_yield_curve. Source: Treasury.gov MSPD (public domain); FREE.

Input schema
{
  "type": "object",
  "properties": {
    "security_type": {
      "type": "string",
      "description": "e.g. 'Total Public Debt Outstanding' (with security_class for a series)."
    },
    "security_class": {
      "type": "string",
      "description": "Class within the type; '_' for Total rows."
    },
    "limit": {
      "type": "integer",
      "description": "Monthly points for a series (default 120, hard cap 360)."
    }
  },
  "required": []
}

reading_list_annotate

WRITE (opt-in): save or update YOUR private note on an Oxford Ledge reading-list entry. THE ONLY MUTATING TOOL here; everything else is read-only. DEFAULTS TO A DRY RUN: with _dry_run omitted or true the handler is never called and you get back {dry_run: true, tool, args, idempotency_key, message} -- a proposal, not a save. Re-call with _dry_run: false AND that same _idempotency_key to execute; a real write returns {slug, updated_existing, is_public, body_chars, updated_at}, and a repeat of the same key returns {replay: true, idempotency_key, original_ts, result} without writing twice. Requires Plus tier AND an authenticated Oxford Ledge caller (HTTP session or API key) -- the local stdio server has no account context and will raise AUTH_REQUIRED. The whole write surface is off unless the deployment opts in. `slug` must already exist on /reading-list; `body` is capped at 500 chars. Notes stay PRIVATE and this tool preserves existing visibility -- it can never publish one (that needs the age-attested dashboard flow). Hourly rate-capped, and every executed write appends to your append-only account audit log.

Input schema
{
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "slug": {
      "type": "string",
      "minLength": 1,
      "maxLength": 200,
      "description": "Reading-list entry slug (as on /reading-list)."
    },
    "body": {
      "type": "string",
      "minLength": 1,
      "maxLength": 500,
      "description": "The note text (max 500 chars). Notes are PRIVATE; publishing requires the dashboard flow (age attestation)."
    },
    "_dry_run": {
      "type": "boolean",
      "default": true,
      "description": "Default true: propose without persisting. Re-call with false to execute."
    },
    "_idempotency_key": {
      "type": "string",
      "maxLength": 128,
      "description": "Optional replay-safety key; auto-derived if omitted."
    }
  },
  "required": [
    "slug",
    "body"
  ]
}

search_bdc_borrower

Which BDCs lend to one private-credit borrower, matched fuzzily on name. Returns a single envelope: borrowerName, borrowerNorm (the canonical key the ol_bdc_* tools take), description, industry, totalHolders, totalParAmount, totalFairValue, avgMarkedPrice / minMarkedPrice / maxMarkedPrice, match_type, and `holders` -- one row per tranche with bdcTicker, bdcName, filingDate, filingType, securityType, lienPosition, interestRate (as-filed text), maturityDate, parAmount, fairValue, markedPrice, industry. Money is whole USD; markedPrice is cents-on-the-dollar out of 100. CAVEAT ON `priceHistory`: it is a map of bdcTicker -> array, but each BDC contributes only its MOST RECENT filing, so in practice every array holds ONE quarter -- it is not a time series and will not support a multi-quarter chart. Use ol_bdc_loan_pricing_trend for a real quarterly series. An ambiguous name returns instead {ambiguous: true, matches:[...], holders: []} -- re-call with a specific name. Debt and equity tranches both included. Source: SEC EDGAR BDC schedules of investments (Oxford Ledge parse; quarterly, ~45-60 day filing lag). Typical latency: <500ms.

Input schema
{
  "type": "object",
  "properties": {
    "query": {
      "type": "string",
      "description": "Borrower/company name to search (e.g. Finastra, Medline)"
    }
  },
  "required": [
    "query"
  ]
}

search_bonds

Bond search by issuer name, grouped by issuer. Returns {issuers: [{issuer, bonds:[{cusip, description, coupon, maturity, debtType}]}], totalBonds}; AT MOST 25 BONDS TOTAL across all issuers (fixed upstream count, not caller-settable). `coupon` is the FINRA percent number (4.5 means 4.5%), NOT a decimal, and `maturity` is passed through in FINRA's own format, not normalised ISO. KNOWN OUTAGE: this calls FINRA's public getIssueData host live, and FINRA auth-walled that path behind its Federated Identity Platform in 2026-07 -- expect {error, issuers: []} rather than data. Use ol_bond_directory_screen (persisted LQD/HYG directory) for corporate-bond discovery that works. Source: FINRA TRACE public endpoint. Latency: 500ms-2s, or a ~12s timeout on the dead path.

Input schema
{
  "type": "object",
  "properties": {
    "query": {
      "type": "string",
      "description": "Issuer name to search (e.g. Apple, Goldman Sachs)"
    }
  },
  "required": [
    "query"
  ]
}

search_company

Resolve a company name or ticker to its SEC identity. Returns {results, count}; UP TO 20 MATCHES (hard cap, not caller-settable). Each result carries SEC-EDGAR-derivable identity fields only: ticker, companyName, cik, sicCode, exchange, fiscalYearEnd -- and any field that is null is OMITTED from the row, so never assume `cik` is present. A query under 2 characters returns {error}. No financials, price or description here; once resolved, call get_fundamentals (SEC XBRL) or get_business_summary. Source: Oxford Ledge company_profiles, restricted to the SEC-submissions column allowlist. Typical latency: <300ms.

Input schema
{
  "type": "object",
  "properties": {
    "query": {
      "type": "string",
      "description": "Search query (company name, ticker, or industry)"
    }
  },
  "required": [
    "query"
  ]
}

search_news_archive

CURRENTLY NON-FUNCTIONAL. Intended as a local TF-IDF full-text search over the archived news + pulse-fact corpus, returning {query, count, results, index}. In this build the search-engine entry point it imports does not exist, so every call returns {error: 'Semantic search index not available in this MCP context'} with no results. Use get_news (pass a `query`) for archive keyword search, or ol_filing_search for semantic retrieval over SEC filings. Typical latency: <50ms (fails fast).

Input schema
{
  "type": "object",
  "properties": {
    "query": {
      "type": "string",
      "description": "Search query (e.g. 'oil prices', 'Fed rate cut')"
    },
    "limit": {
      "type": "integer",
      "description": "Max results to return (default 20)"
    }
  },
  "required": [
    "query"
  ]
}

Fine print