SEC EDGAR MCP Server
Point-in-time, survivorship-bias-free SEC EDGAR fundamentals for AI agents. 1994 to present.
What is the SEC EDGAR MCP Server?
The SEC EDGAR MCP server gives AI agents structured, permission-aware access to SEC EDGAR through the Model Context Protocol. With 68 pre-built actions, agents can read, create, and update SEC EDGAR data on behalf of authorized users.
Willow ships the SEC EDGAR MCP server as part of an enterprise control plane. Every call runs behind SSO (Okta, Azure AD), enforces RBAC and least-privilege at runtime, writes to a full audit trail, and integrates with Splunk and Loki for SIEM visibility. Connect from Claude Desktop, Claude Code, Cursor, ChatGPT, VS Code, n8n, or any custom agent. Install once, distribute org-wide, and see exactly how SEC EDGAR is being used by every AI agent in your stack.
Tools
search_companies
Search for US public companies by name, ticker symbol, CIK (SEC identifier), or SIC industry code. Returns ticker, company name, sector, industry, exchange, and current S&P 500 membership status. Use this tool to resolve a company name to ticker/CIK before calling `get_company_fundamentals`, `get_valuation_metrics`, or other tools that require a ticker — they do not fuzzy-match company names. **Use this tool — NOT `get_pit_universe` — when the user asks about CURRENT S&P 500 members.** To list current S&P 500 members, call this tool with no search parameters and filter results by `sp500_member=true`. This returns the live snapshot as of query time. Example: "List 5 current S&P 500 members" → call `search_companies` with no parameters, then filter by `sp500_member=true` and return the first 5. **Use `get_pit_universe` ONLY when the user explicitly needs a survivorship-free historical universe as of a specific past date** (e.g. "S&P 500 members as of March 2018"). If the user says "current," "today," "now," or gives no date, use `search_companies` instead. **Data details:** `sic_code` is the 4-digit SIC; `industry` is the human-readable label. `sector` is SIC-derived with GICS-style labels — NOT licensed GICS, so industrial conglomerates may map differently from official GICS (e.g. 3M → 'Health Care' by SIC vs Industrials by GICS). S&P 500 membership is sourced from index_membership.parquet (current SP500 = `index_name='SP500' AND removal_date IS NULL`). Available on all plans.
get_company_fundamentals
Retrieve standardized SEC EDGAR fundamental financial metrics for a US public company. Returns revenue, gross profit, operating income, net income, EPS (diluted), total assets, total liabilities, stockholders' equity, cash & equivalents, total debt, operating cash flow, and capital expenditures for one or more fiscal periods. Data sourced from 10-K (annual) and 10-Q (quarterly) filings. Point-in-time: no look-ahead bias — pass `as_of_date` (YYYY-MM-DD) to reconstruct exactly the information set known on that date. This returns the raw as-reported line items; for pipeline-computed ratios (margins, ROE, ROIC, leverage, per-share) use `get_financial_ratios`, and for margins combined with DCF/DDM model inputs use `get_valuation_metrics` — both derive from the figures this tool returns.
get_valuation_metrics
Get comprehensive valuation and profitability metrics for a US public company. Returns per-period data combining computed ratios (gross_margin, operating_margin, net_margin, ROE, ROA, ROIC, debt_to_equity, FCF, FCF margin) with optional pre-computed DCF model inputs (WACC, fcf_base_per_share, stage1_growth_rate, terminal_growth_rate, dcf_value_per_share, ddm_value_per_share). Profitability + cash flow + leverage fields are sourced from fact.parquet (PIT-safe via accepted_at). DCF/DDM fields are sourced from valuation.parquet (pipeline-computed; recomputed each pipeline run, so NOT strictly PIT-safe). DCF/DDM fields are commonly null — for newer tickers, transition periods, or before the valuation pipeline has run. Each null carries a `null_reasons[field]` entry with one of: VALUATION_NOT_COMPUTED, INPUT_MISSING, INPUT_NEGATIVE, PIPELINE_PENDING. ALWAYS check `null_reasons` before assuming a field is zero — null != 0. For agents needing strict PIT DCF reasoning, use the SDK or compute DCF inputs from `get_company_fundamentals` directly. Use this *instead of* `get_financial_ratios` when DCF/intrinsic value matters; use `get_financial_ratios` when you only need the ratio table without DCF wiring. Available on all plans.
get_financial_ratios
Get pipeline-computed financial ratios from ratio.parquet. Served categories: profitability (margins, ROE, ROA, ROIC), liquidity (current ratio, quick ratio), leverage (D/E, interest coverage, net debt/EBITDA), efficiency (asset turnover, inventory days), per_share (EPS, BVPS, FCF/share), owner_earnings (Buffett FCF, owner yield), and the pipeline-emitted forensic, growth, and rank (cross-sectional *_sector_pctile) categories. NOT every category exists for every ticker — the exact set is data-driven; omit `categories` to get whatever this ticker has, or read the `available_categories` list returned in the CATEGORY_NOT_AVAILABLE envelope. valuation (EV/EBITDA, P/E, P/FCF) is DECLARED BUT NOT YET POPULATED for any ticker (price feed pending) — requesting it returns a CATEGORY_NOT_AVAILABLE envelope, never data. Includes TTM (trailing twelve months) rows alongside annual periods. Each row carries `is_calendar_aligned` (boolean) — TRUE when period_end is actually on the entity's fiscal year boundary (±7 days), FALSE when the pipeline emitted a calendar-quarter row tagged fiscal_period='FY' for a non-December fiscal-year filer. Filter to `is_calendar_aligned=TRUE` if you're joining ratios with fact-table fundamentals on (entity, fiscal_year). Ratios are pipeline-derived — they're recomputed on each pipeline run with ON CONFLICT DO UPDATE. For historical cuts use `as_of_date` (the canonical cross-tool name; alias `period_end_before`). PIT semantics are data-driven: when the ratio data carries an SEC `accepted_at` timestamp, `as_of_date` filters point-in-time by accepted_at (zero look-ahead, latest-knowable per period) and `_meta.pit_safe=true`; when it does not (today's data), the cut is by ratio.period_end and `_meta.pit_safe=false`. For guaranteed accepted_at PIT regardless, use `get_company_fundamentals` (which carries fact.accepted_at). Use this *instead of* `get_valuation_metrics` when you only need ratios (no DCF wiring); use `get_valuation_metrics` when you also need DCF/DDM. Each ratio is a `{value, unit, category, reason}` entry; a response-level `lineage` (DerivedLineage) envelope marks the values pipeline-derived and points to `get_company_fundamentals` / `verify_fact_lineage` for filing-level provenance. Use the returned `value` exactly — do not recompute it; a null value carries a `reason` (e.g. INPUT_MISSING, DENOMINATOR_NEGATIVE) so missing is never confused with a real zero. Available on all plans.
get_sec_filing_links
Get direct links to original SEC EDGAR filings for any US public company. Returns two per-filing deep links: `sec_url` (the EDGAR filing-index page listing every document) and `viewer_url` (the SEC iXBRL inline-viewer for the specific accession). Supported form_types (enum): 10-K, 10-Q, 8-K, 20-F, 40-F, 10-K/A, 10-Q/A, 20-F/A, 40-F/A. Other forms (6-K, DEF 14A, Form 4, 13F) are NOT yet exposed by this tool — use `describe_schema` to confirm the parquet has them, then read raw via the SDK. 8-K item codes are filterable via `event_types` (e.g. ['2.02'] for earnings, ['1.01'] for material agreements, ['5.02'] for officer changes). PIT-safe — filings are filtered by accepted_at, never by report_date alone. Use this *instead of* `verify_fact_lineage` when you want a list of filings; use `verify_fact_lineage` when you want one specific fact-to-filing trace. Available on all plans.
get_capital_allocation_profile
Get a multi-year capital allocation breakdown for a US public company. Shows how management deploys cash across all six categories — capex, R&D, M&A, dividends, buybacks, and debt — plus pre-computed deployment ratios (% of operating cash flow) and over-distribution flags. Use this tool when the user asks: how does a company allocate capital, what's the buyback-vs-dividend mix, is the company over-distributing, is growth funded by R&D or M&A, what's the cash return ratio trend, or any 'where does the money go' question. Also use for owner-earnings analysis (Buffett-style) and reinvestment-rate analysis (Damodaran-style). Data sourced from annual 10-K filings (SEC EDGAR) — income statement (R&D), investing section (capex, M&A), financing section (dividends, buybacks, debt). All figures are point-in-time safe via the as_of_date parameter — no look-ahead bias. R&D semantics: R&D is included as a deployment category despite being an income-statement expense, because for knowledge-economy businesses (tech, pharma, industrials with heavy engineering) it represents the primary growth reinvestment vehicle and often dwarfs capex. R&D is already deducted before reaching operating cash flow, so `rd_pct_ocf` is INFORMATIONAL — it does not reduce OCF a second time. The `total_deployment_pct_ocf` field excludes R&D from its sum to preserve the cash-flow identity (OCF = capex + M&A + dividends + buybacks + debt repayment + change in cash). Flags object: pre-computed booleans for common analytical questions. Use `buybacks_exceed_fcf` to identify years a company returned more to shareholders via repurchases than it generated in free cash flow. Use `total_returns_exceed_fcf` for the stricter test (buybacks + dividends > FCF). Use `debt_funded_distribution` to distinguish over-distribution funded by leverage (typical industrials) from over-distribution funded by cash hoard (Apple 2018-2019 post-tax-reform repatriation). NOT yet included (separate roadmap items): `buyback_yield_implied` requires a price × shares market-cap series; equity-method investments and intangibles are excluded from `acquisitions_net` to keep M&A semantics tight (request `other_investing_outflows` if needed). Available on all plans.
get_peer_comparables
Get ratio-based peer comparison for a company and its closest competitors. Peers are selected by matching 2-digit SIC industry code. Returns pipeline-computed ratios from up to 10 peers alongside the subject company for direct benchmarking. Ratio categories: profitability, liquidity, leverage, efficiency, per_share, owner_earnings, valuation. TTM (trailing twelve months) ratios are used when available for the most current view. Use as_of_date to compare peers at a specific historical date. PIT semantics for the figure leg are data-driven: when the ratio data carries an SEC accepted_at timestamp, as_of_date filters point-in-time by accepted_at (zero look-ahead, _meta.pit_safe=true); when it does not (today's data), the cut is by ratio.period_end (_meta.pit_safe=false). NOTE: peer SELECTION still uses CURRENT S&P 500 membership as a size/relevance ranking proxy regardless of as_of_date (W3-G2). Available on every plan — sample returns the subset covered by the sample bucket.
get_pit_universe
Use this tool to answer questions about historical index membership — e.g. "Was Company X in the S&P 500 on date Y?" or "Which companies were in the Russell 2000 on 2010-01-01?" Use this INSTEAD OF `search_companies` when the question involves a specific historical date or asks whether a company was an index member at a point in the past. `search_companies` only returns current membership snapshots and cannot answer historical membership questions. Returns a survivorship-free universe of companies valid on a specific as_of_date: only companies that existed and were index members on that exact date — no hindsight contamination. Supports SP500, RUSSELL1000, RUSSELL2000, RUSSELL3000 via index_membership.parquet (accurate join/leave dates with [) interval semantics). To check a single company's membership, pass its ticker and the target date; if the company appears in the response it was a member, if absent it was not. Returns per company: CIK, ticker, name, sector, industry, SIC code, plus per-row membership confidence (high/medium/low). Check `_meta.pit_safe`: true only when every matched row is high-confidence; medium/low rows downgrade it to false — treat low-confidence rows with caution for backtest use. NOTE: `sector` is SIC-derived (GICS-aligned labels via sic_to_sector.csv), not licensed GICS — industrial conglomerates may map differently. Treat as a screening bucket, not an authoritative GICS label. Use as the first step of a quantitative backtest before calling `get_compute_ready_stream` to pull Parquet data for the universe. Returns empty array (with error detail) if the date is out of range or the index_membership data has no coverage for that date. Available on every plan — sample tier returns the subset covered by the sample bucket.
get_compute_ready_stream
STATUS: pending — direct R2 Parquet access is in private beta (ETA 2026-Q3). Calls return 501 FEATURE_NOT_AVAILABLE today. When live: returns a pre-signed Cloudflare R2 URL for bulk Parquet access that can be piped into Python/DuckDB/Polars for high-throughput computation that exceeds the MCP context window. Datasets: fact (per-entity partition — requires ticker), ratio (all computed ratios), valuation (DCF inputs), filing (SEC filing metadata), references (company universe), index_membership (historical index composition). URL would expire in 15 minutes. TODAY use the Python SDK (`pip install valuein-sdk`) for the same data via DuckDB.
describe_schema
Returns the Parquet schema for all tables in the Valuein SEC data warehouse. Includes table descriptions, column names, types, primary keys, and foreign-key references. Use this tool to understand the data model before querying with other tools. No data reads required — schema is embedded in the manifest. Available on all plans.
verify_fact_lineage
Use this tool when the user asks BOTH what a financial figure is AND which filing reported it — for example "What was Apple's most recently reported revenue, and which 10-Q filed it?" or "Show me the accession ID for Tesla's latest net income" or "Which filing form reported Amazon's Q3 operating cash flow?" This tool returns a single fact plus its complete filing provenance: entity, concept, period, value, accession ID, filing URL, and form type (10-K, 10-Q, etc.). Use this INSTEAD OF `search_companies` when the user already names a company and wants a financial figure with its source filing — `search_companies` only resolves company identifiers and returns no financial data. Use this INSTEAD OF `get_company_fundamentals` when the user explicitly wants to know which filing or form type reported a number, or needs the accession ID — `get_company_fundamentals` returns metrics across multiple periods but omits filing provenance. Two lookup modes: (1) by fact_id (SHA-256 hash of entity_id|accession_id|concept|period_end|unit) for deterministic identity; or (2) by concept name (e.g., TotalRevenue, NetIncome, EPSDiluted, TotalAssets, OperatingCashFlow) plus a ticker to retrieve the most recently reported fact. Optionally pin a point-in-time cutoff via as_of_date (YYYY-MM-DD) — returns the latest filing accepted by SEC on or before that date, eliminating look-ahead bias. Check `_meta.pit_safe` in the response to confirm PIT correctness. DURATION: income-statement flow concepts (NetIncome, TotalRevenue, etc.) are reported over a window, and a single 10-K tags BOTH a 12-month figure and a 3-month Q4 stub at the same fiscal-year-end period_end. On a tie this tool returns the longer (headline) window, and every result carries `period_type` (instant | quarterly | half_year | nine_month | annual | duration) and `period_span_days` so you always know whether a number is a quarter or a full year — never present a 3-month stub as the annual figure. Provide either fact_id or concept (required). Returns empty result with error_code FACT_NOT_FOUND if no matching fact exists for the given concept and ticker. Available on all plans.
compare_periods
Compare a company's core financial metrics across two fiscal periods side-by-side. Shows absolute and percentage changes with significance classification (minor < 5%, notable 5–15%, significant > 15%). The response includes a `material_changes` count: this is the number of metrics whose `significance` ∈ {notable, significant} (i.e. absolute percentage change > 5%). Use it as a quick scalar to triage filings — anything > ~3 typically signals a material event worth deeper review. Use period format: 'FY2024' for annual, 'Q1-2024' for quarterly. Pass `period_a` as the EARLIER period and `period_b` as the LATER one — if you invert them the server auto-swaps and sets `swapped: true` in the response so deltas always carry the correct sign (rather than silently flipping). Point-in-time safe via as_of_date. Available on all plans.
screen_universe
Rank companies by cross-sectional factor scores from factor_scores.parquet. Returns the underlying factors (roe, gross_margin, operating_margin, net_profit_margin, revenue_growth_yoy, fcf_to_assets, debt_to_equity, asset_turnover, current_ratio, piotroski_f_score) plus their percentile ranks (1.0 = best in universe; 0.0 = worst). `composite_rank` is a composite percentile rank across the factor set — a one-number screening shortcut. For single-factor sorts, use the specific rank column (e.g. `sort_by=roe_rank`); for a balanced multi-factor view, use `sort_by=composite_rank` (default). Two modes: full-universe (omit ticker) or single-entity (ticker set — useful for spot-checking ONE company's factor profile without scrolling the whole leaderboard). Sector filter is SIC-derived (GICS-aligned labels, not licensed GICS — see `get_pit_universe` description for the caveat). Use this *instead of* `get_financial_ratios` when you want CROSS-SECTIONAL comparison (rank vs peers); use `get_financial_ratios` when you want one company's ratios over time. Supports survivorship-free POINT-IN-TIME screening: pass `as_of_date` (YYYY-MM-DD) to reconstruct the cross-section as it was knowable on that date — factor_scores carries the SEC `accepted_at` of each period, so the screen filters to scores whose underlying filing had been accepted by the as-of date (zero look-ahead) and ranks each entity at its latest-KNOWABLE period. Omit `as_of_date` for the latest available snapshot. When supplied, the response carries `as_of_date` and `pit_safe=true`. Available on every plan — sample returns the subset covered by the sample bucket.
get_earnings_signals
Reported earnings results and a model-derived earnings-trend signal for a company, by fiscal period: actual reported EPS, a trailing-trend EPS estimate (`eps_trend_est`), the deviation of actual vs that trend (`eps_surprise_pct`), reported revenue, and year-over-year revenue growth. IMPORTANT: `eps_trend_est` is NOT Wall Street analyst consensus — Valuein is sourced purely from SEC EDGAR and carries no consensus feed. It is a deterministic estimate computed from the company's own prior reported EPS, so `eps_surprise_pct` measures how far the print landed from its own trailing trend, not whether it 'beat the Street'. Use it to track earnings/revenue trajectory and momentum, not to claim a consensus beat or miss. Point-in-time safe — pass as_of_date to filter by SEC acceptance (accepted_at) for look-ahead-free backtests. Available on all plans.
search_filing_text
Semantic search over SEC 10-K / 10-Q narrative sections — Risk Factors, MD&A, Business, Legal Proceedings, Controls & Procedures, Footnotes. Phrase the query as a statement rather than a question for best recall (e.g. "companies with rising supply chain concentration risk" not "what companies have supply chain risk?"). Returns narrative passages (text, not numeric facts) ranked by semantic similarity, each with the source filing accession id + URL for citation; `score` is a [0,1] similarity, not a financial figure. For dollar/ratio figures use `get_company_fundamentals` / `get_financial_ratios`. Cached at the per-plan tier for 10 min.
get_insider_transactions
Form 3 / 4 / 5 / 144 line items for a US public company. Returns each transaction (or initial holding / proposed sale) with the insider's name, role, transaction code, share count, price, and notional. Filters by lookback window, transaction code (P=purchase, S=sale, A=grant, M=option exercise, F=tax withholding, etc.), insider role, and minimum share threshold. Institutional tier only — sample / sp500 / pro return ENTITLEMENT_DENIED with an upgrade link.
get_institutional_holdings
Returns top-N institutional holders of a US public company at a specific period_end (latest by default), with aggregate institutional shares, total market value, holder count, and HHI concentration (sum of squared share-of-total percentages). Sourced from Form 13F-HR via the by-issuer partition. Institutional tier only. 13F filings carry a ~45-day reporting lag — staleness_warning fires when latest data is older than 90 days.
get_manager_portfolio
Returns a 13F filer's full portfolio at a specific period_end (latest by default), with QoQ deltas vs the prior quarter (new / increased / decreased / exited / unchanged). Specify the filer either by filer_cik (preferred) or filer_name (fuzzy match against entity.name; multiple matches raise an ambiguity error so you can disambiguate by CIK). Institutional tier only.
get_blockholders
Returns SC 13D / SC 13G blockholder disclosures (5%+ stakes) for a US public company. Each row carries percent_owned, sole/shared voting + dispositive split, schedule_type, and the first-class ``going_active`` flag — TRUE when the same filer flipped 13G → 13D within the lookback window (the single most actionable activist signal in this dataset). Use latest_only=true (default) to dedupe to the most recent filing per filer. Use collapse_groups=true to fold multi-person filings into one row. Institutional tier only.
get_insider_sentiment
Role-weighted insider sentiment score on a fixed [-100, +100] scale for a single issuer over a lookback window. Role weights: CEO/CFO = 3.0 (via officer_title pattern), other NEO Officer = 2.0, 10%-Owner = 1.5, Director = 1.0. P = +1, S = -1; option exercises, grants, and tax withholdings are neutralised. Cluster flag = TRUE when ≥3 distinct insiders transacted within any 30-day window inside the lookback. Institutional tier only.
get_top_holders
Classification-aware UNION across insider transactions (latest post_transaction_shares per insider), 13F institutional holdings, and SC 13D / 13G blockholder filings for one issuer. Each row carries holder_class ∈ {insider, institutional, blockholder_13D, blockholder_13G}. Dedupes overlapping filers by precedence (13D > 13G > institutional > insider). One call, classified cap table — Bloomberg charges separately for INSIDER<GO>, OWNER<GO>, and HDS<GO>; this consolidates them.
get_smart_money_flow
Composite flow score on [-100, +100] aggregating insider transactions, 13F institutional Δ-shares vs the prior quarter, and SC 13D/13G blockholder changes over a lookback window. Each component normalised independently, then combined with configurable weights (default: institutional 0.4, blockholder 0.4, insider 0.2). Returns per-component attribution so an agent can see WHY the score is what it is — not just the headline number. NOTE: the institutional component is a QoQ share-change signal computed over the top-5 13F filers on a MATCHED current-vs-prior basis (a filer only counts when its prior-quarter book is observable), NOT the issuer's complete institutional book — treat the score as a directional signal, not an exact flow. `coverage.coverage_confidence` (0–1) reports how much of that basis had a real prior quarter; when it is 0 the institutional component is forced to 0 so a 13F ingestion gap can never surface as a false max-conviction buy. See the `coverage` block for holder coverage + staleness. The score is a unitless composite, not a dollar figure. Institutional tier only.
save_thesis
Persist a directional investment thesis (bull / bear / neutral) on a ticker. The thesis becomes part of the caller's private research diary; pair with `list_theses` + `score_thesis_outcome` to track conviction-vs-outcome over time. Pass `idempotency_key` for at-most-once semantics from a retrying agent. **Use this AFTER** the agent has finished its analysis, not before — the thesis records the conclusion, not the question. Pair with `source_report_id` to link the thesis back to a published report so the buyer's thesis-tracking carries provenance. Tier: all paid + free tiers (sample tier rejected — sample is guest access with no customerId binding). Per-tier cap on # of stored theses: sp500=100, pro=500, full=10,000.
list_theses
Return the caller's saved theses, newest-first. Filters: ticker (exact), view, status. Cursor-based pagination — pass `next_cursor` from the previous response to fetch the next page. Sample tier rejected (no per-user state).
get_thesis
Fetch a single saved thesis by its id. Returns the full record including outcome (if scored). Returns NOT_FOUND if the id is unknown or belongs to another user. For the claims composing a thesis use list_claims_for_thesis; for an individual claim use get_claim. Tier: paid + free (sample rejected).
delete_thesis
Soft-delete a saved thesis: status flips to `archived` (the row stays for audit / re-scoring). Idempotent — archiving an already-archived thesis succeeds. Hard-delete is not supported by design; future versions may expire archived theses after N years. This does not delete the claims linked to the thesis — use delete_claim for those. Tier: paid + free (sample rejected).
score_thesis_outcome
Grade a saved thesis against fundamental momentum since its creation. Pulls revenue / operating-margin / EPS / OCF deltas and aggregates into a score in [-1, +1]. Bull theses are graded by directional alignment, bear by inverse, neutral by closeness-to-flat. The grade is persisted back to the thesis row; re-call to refresh once new fundamentals land. **Note (PR 2)**: scoring is fundamental-only — does NOT yet include market-price returns. Phase 2 will mix in price data via a partner feed; the response shape is stable.
score_due_theses
Find every thesis past its horizon with no outcome yet, and grade each via `score_thesis_outcome`. Operates on the caller's own theses by default; can target any user when called with `customer_id` from a `full`-tier (admin) token. Returns a summary + per-thesis results. Idempotent — a re-call only re-grades anything not already graded.
run_workflow
Resolve a saved workflow by id and return a structured execution plan for a single ticker. Each plan entry names a real MCP tool or SOP plus its ticker-substituted arguments; the calling agent invokes them in order, applying any `skip_if` predicate against the previous step's output. **This tool does NOT execute the steps server-side.** It plans; the agent runs. Iterate through `plan[]` in order, call the named tool/SOP with `args`, accumulate outputs, and apply each step's `skip_if` (skip the step when the previous output's `path` equals `equals`). Workflows are private state owned by the calling user. Sample-tier callers are rejected. Pair with `list_workflows` (frontend) to discover available workflow_ids.
list_public_theses_by_user
Return the PUBLIC theses + reputation aggregate for a user identified by Stripe customer_id. Used by the /[handle] profile page to render an analyst's track record. Only entries with visibility='public' are surfaced — private theses never leak. Reputation is correct/(correct+wrong) over graded theses; null when n < 5 (sample too small). Sample tier rejected; sp500+ only.
save_claim
Persist a single falsifiable, evidence-backed CLAIM — the atomic unit of the research graph. Use this for each discrete assertion an analysis produces (e.g. 'NVDA gross margin stays above 70% through FY2026'), then compose claims into a thesis with `link_claim_to_thesis`. Claims are scored independently of theses, so claim accuracy is tracked as its own track record. Pick `claim_type` by HOW it's judged, not what it's about: `assertion` = true now, checked against data; `prediction` = resolves at `horizon_days` via `verifiable_condition`; `judgment` = qualitative, not auto-scored. Use `tags` for the topic (financial, valuation, macro, …). Set `eval_mode: 'auto'` + a `verifiable_condition` for deterministic grading, else `'agent'`/`'manual'`. Tier: all paid + free tiers (sample rejected — guest has no customerId). Verifiable claims must cite evidence.
list_claims
List the caller's saved claims, most-recent-first, with AND-composed filters and cursor pagination. Filter by ticker, claim_type (assertion/prediction/judgment), tag, or lifecycle status (open/confirmed/refuted/expired/stale/needs_review). Archived claims are excluded unless include_archived is set. Tier: all paid + free tiers (sample rejected).
get_claim
Fetch a single claim by id, plus the ids of theses it supports/refutes and its full append-only score history. Use this to inspect a claim's evidence, current status, and how its outcome has evolved. Tier: all paid + free tiers (sample rejected).
delete_claim
Soft-delete a claim by id. The row and its score history are preserved for audit (archived, not erased); the claim drops out of default list_claims results. Idempotent — deleting an already-archived claim succeeds. Tier: all paid + free tiers (sample rejected).
link_claim_to_thesis
Attach a claim to a thesis with a role: 'supports' (the claim, if true, strengthens the thesis), 'refutes' (if true, weakens it — track disconfirming evidence first-class), or 'context' (relevant but not directional). Idempotent — re-linking updates the role. A claim can support one thesis and refute another. This composes theses from claims; it does NOT make the thesis score a function of claim scores (they're scored independently). Tier: paid + free (sample rejected).
unlink_claim_from_thesis
Remove the link between a claim and a thesis. Idempotent — succeeds whether or not the link existed. The claim and thesis themselves are untouched. Tier: paid + free (sample rejected).
list_claims_for_thesis
List the claims composing a thesis, each with its role (supports/refutes/context). This is how you read a thesis as the structured argument it is — its supporting and disconfirming claims with their current statuses. Archived claims are omitted. Tier: paid + free (sample rejected).
score_claim
Resolve a claim's outcome. By default auto-grades an `auto` claim by evaluating its verifiable_condition against SEC fundamentals (confirmed/refuted), or marks it `needs_review` when it can't be resolved deterministically (judgment, antecedent, or missing data). To record a human/agent judgment instead, pass `manual_status` (+ optional score/reason). Idempotent — re-scoring the same resolution is a no-op. Tier: sp500+ (sample rejected).
score_due_claims
Find every auto-gradable claim that is due (assertions in open/needs_review/stale; predictions whose horizon has passed) and resolve each against fundamentals. Operates on the caller's own claims by default; can target any user when called with `customer_id` from a `full`-tier (admin) token. Returns a summary + per-claim results. Idempotent — re-calling only re-resolves what changed.
list_public_claims_by_user
Return the PUBLIC claims + claim-accuracy reputation for a user identified by Stripe customer_id. Used by the /[handle] profile to render an analyst's claim-level track record — a separate signal from thesis-outcome accuracy. Only visibility='public' claims surface; private state never leaks. Accuracy is confirmed/(confirmed+refuted) over resolved claims; null when n < 5. Sample tier rejected; sp500+ only.
save_citation_override
Persist a correction of a citation value. The correction is keyed on the canonical `fact_id` (a stable hash of CIK + accession + concept + period) so it applies to every report that references that same fact — including agent-regenerated reports. Re-saving the same fact_id replaces the prior correction in place (no duplicate row). The `fact_id` is VERIFIED against live SEC data (scoped to `ticker`) before the correction is stored — a fact_id that doesn't resolve to a real fact is rejected with FACT_NOT_FOUND and nothing is persisted. You therefore must supply the `ticker` the fact belongs to. Use this when the user notices an inaccuracy in an AI-generated report and wants the fix to persist. Provide `notes` for the rationale (≤500 chars) and `source_report_id` for provenance. Tier caps: sp500=500, pro=5000, full=50000.
list_citation_overrides
Author-only newest-first listing of the caller's citation corrections. Filterable by ticker (e.g. all AAPL corrections) or by a single fact_id (returns 0 or 1 row). Pair with `save_citation_override` and `delete_citation_override`. Sample tier rejected. Agent use: call with `ticker` to introspect what corrections the user has previously applied on that ticker — useful for system prompts that respect prior corrections during regeneration.
delete_citation_override
Remove a user-authored citation correction by fact_id. Idempotent — deleting a missing override returns deleted=false without error. Once deleted, reports that previously rendered the corrected value revert to the canonical fact value on next regeneration. Tier: paid + free (sample rejected).
save_watchlist
Upsert a named watchlist with a list of tickers. Replace semantics — the full ticker list is the source of truth for that name. Use this for both creation AND modification (delete + recreate is not required for edits). 500-ticker cap per list. Names are case-insensitive uniqueness.
list_watchlists
Paginated newest-first listing of the caller's watchlists (id, name, tickers, status, counts). Filter by `status` (active/archived/all). Returns metadata only — use get_watchlist for one list's full ticker set, or watchlist_diff for new filings across a list. Tier: sp500+ (sample rejected).
get_watchlist
Fetch a single watchlist (full ticker set + criteria) by its name, not an id (case-insensitive). NOT_FOUND if the name is unknown to this user. Tier: sp500+ (sample rejected).
delete_watchlist
Soft-delete a watchlist by its name (not id): status flips to `archived` (still readable via list_watchlists status=all/archived). The name is freed for reuse by a new save_watchlist. Idempotent. Tier: sp500+ (sample rejected).
watchlist_diff
Return new SEC filings across the caller's watchlist tickers since a given date. Reads filing.parquet — does not call insider/ratio surfaces (use those tools separately if you need them). Concurrency-bounded; max 50 tickers per call.
create_alert
Persist an alert and register it with the firing pipeline. Three condition shapes: * `filing_event` — fire when a ticker files a chosen form type (8-K, 10-K, etc.). * `ratio_threshold` — fire when a ticker's financial ratio crosses a threshold (e.g. interest_coverage < 1.5). * `watchlist_change` — fire on any filing on any ticker in a named watchlist. Delivery channels: `email` (transactional via Resend) or `webhook` (HMAC-SHA256-signed POST). The cron evaluator runs every 5 minutes. Use `test_alert` to verify your channel is wired correctly before relying on the cron.
list_alerts
Paginated newest-first listing of the caller's alerts (id, condition, channel, status, trigger_count, evaluator health). Filter by `status` (active/paused/deleted/all). Use the returned alert id with delete_alert or test_alert. Tier: sp500+ (sample rejected).
delete_alert
Soft-delete an alert by its id (from create_alert/list_alerts): status flips to `deleted` and it is removed from the cron evaluator index so it stops firing. Alerts are immutable — to change one, delete then create_alert. Idempotent. Tier: sp500+ (sample rejected).
test_alert
Fire a synthetic notification through the alert's configured channel. Use this immediately after `create_alert` to verify the channel (email address valid / webhook URL reachable + HMAC verification on the receiver). The synthetic fire is logged as `attempt=1 channel='test'` so it doesn't affect the real fire counter — the next genuine match still fires normally.
list_alert_inbox
Newest-first listing of the caller's in-app alert inbox. Each item is a single FIRE of an alert with a `dashboard` channel — written by the cron evaluator (or `test_alert`); use list_alerts instead for the alert definitions themselves. By default dismissed items are hidden and read items are included. Cursor-paginated by `fired_at`. Sample tier rejected — alerts are a paid-tier feature (sp500+).
mark_inbox_read
Set `read_at` on a single inbox item by its id (from list_alert_inbox or the alerts feed resource) — not an alert id. Idempotent — re-marking does NOT reset the first-read timestamp; there is no unmark. Returns the new unread_count so the agent/UI can update its badge without a follow-up call. Tier: sp500+ (sample rejected).
dismiss_inbox_item
Soft-delete a single inbox item by its id (from list_alert_inbox) — not an alert id; sets `dismissed_at`. The row stays queryable via `list_alert_inbox(include_dismissed=true)` for audit. Idempotent. Tier: sp500+ (sample rejected).
create_report
Synchronously generate a research report and persist it under the caller's authorship. Two subtypes: • `reverse_dcf` — solves the stage-1 free-cash-flow growth rate the market price implies, with a 5×5 sensitivity grid across WACC × terminal-growth assumptions. Returns full markdown + structured JSON + every numerical claim's citation chain to the originating SEC accession. • `thesis` — snapshot a saved thesis (via `save_thesis`) as a frozen narrative report with at-a-glance table, author notes, anchor fundamentals (latest annual), and lineage to the source filing. Later edits to the thesis do NOT propagate — generate a new report to capture new state. Tier: sample tier rejected — reports are per-author state.
get_report
Fetch the current HEAD of a report by id. `format=markdown` returns the rendered body, `format=json` returns the full structured payload (sections + citations + report-type-specific data), `format=preview` returns abstract-only. Authors see any of their own reports; non-authors only get `preview` of listed reports and need the report's required tier for full bodies. Sample-tier non-authors are downgraded to preview regardless of input. For an archived prior version use `get_report_version`, not this tool.
list_my_reports
Cursor-paginated newest-first listing of the caller's own reports (owner-scoped). Filters compose with AND; `status` defaults to 'ready' so pass status='draft' or 'all' to see drafts. Use `cursor` from the previous response's `next_cursor` to fetch the next page (limit max 100). Sample tier rejected (no per-author state).
delete_report
Soft-delete a report owned by the caller: status flips to `delisted`, visibility to `private` — not a hard delete, the row and R2 artifact are preserved (90-day audit window). Idempotent (deleting an already-delisted report succeeds). Sample tier rejected.
compute_dcf
Forward discounted-cash-flow valuation (two-stage Gordon-growth model): caller provides growth + WACC + terminal assumptions, returns per-share intrinsic value (`value_per_share_cents`, cents USD) + 5×5 sensitivity grid. Pulls FCF base + net debt + shares from R2; caller can override any field. Definitions (consistent with `get_financial_ratios` / `get_capital_allocation_profile`): FCF base = operating_cash_flow − capex (absolute USD); net_debt = total_debt − (cash + short-term investments). Shares resolve via a fallback chain (valuation row → fact CommonSharesOutstanding → net_income/eps_diluted), reported as `result.shares_source`. The pulled inputs are echoed in `result.inputs_echo` with their source lineage so the valuation is reproducible and traceable. A null `value_per_share_cents` means the model is degenerate (e.g. WACC ≤ terminal growth, or FCF base ≤ 0) or a required input was unavailable — it is NOT a zero valuation; the `reason` field explains. Use the returned figures exactly. Use this when you want to drive the assumptions yourself; for the pipeline's pre-computed DCF/DDM value and inputs (no assumptions needed) use `get_valuation_metrics` instead. Does NOT persist a report — use `create_report` (report_type:'reverse_dcf') for that. Tier: sp500+.
forensic_audit
Deterministic forensic-accounting scores for a single ticker: partial Beneish M-Score, Sloan accruals, and a solvency snapshot. Returns a red-flag narrative ranked by severity, with citations to source filings. Used by the `forensic_earnings_brief` SOP. Note: full Beneish needs AR / current assets / PPE / SGA / current liabilities, which aren't in our fundamentals model. We compute the recoverable subset (SGI + TATA + LVGI) and flag `partial=true`. Tier: sp500+.
update_report
Replace one or more sections of an existing report owned by the caller. Useful for authoring workflows where the agent's first draft (`create_report`) is refined by additional analysis before publishing. Bumps `version`. Re-embeds for Vectorize if the report is currently listed. Does NOT change price / tier / visibility — use publish_report for those.
list_report_versions
Author-only newest-first listing of a report's archived version history. Each entry summarises what changed (sections edited, etc.) so the workspace UI can render a clickable history without loading every artifact. Pair with `get_report_version` to fetch a specific version's content for diffing against HEAD.
get_report_version
Author-only fetch of a specific archived version of one of your reports, by positive-integer `version`. Returns metadata + the full payload (sections, citations, structured, markdown) — enough to render a diff against the current HEAD in the workspace editor. Use after `list_report_versions` identifies the version number you want; for the current HEAD use `get_report` instead.
render_report
Return a 15-minute presigned download URL for a report in the requested binary format. `format=md` presigns the cached markdown — instant, no compute. `format=docx` returns a branded Word document with a cover page (logo, title, ticker, tier badge), the report body (abstract, sections, citations table with clickable SEC EDGAR links), and a back page (methodology, sources, disclaimer). The DOCX is cached in R2 alongside the markdown after first build so repeat downloads are instant; pass `force_regenerate: true` to bust the cache (e.g. right after `update_report`). Tier gate mirrors `get_report`: authors always see their own reports; non-authors below the report's required tier get an upgrade prompt.
generate_dcf_xlsx
Render a forward DCF result into a professional Excel workbook (Summary + 5×5 Sensitivity heatmap + Inputs sheet). Native conditional formatting — no chart images needed. Returns a 15-minute presigned R2 download URL. Pair with `compute_dcf` for a typical analyst flow: agent calls `compute_dcf({ticker, ...})`, then passes the structured result straight to `generate_dcf_xlsx({ticker, dcf_result, ...})` to materialise a shareable file. Tier: pro+.
generate_research_brief_docx
Render a structured research brief into a professionally-styled Word document — cover, abstract, optional snapshot table, body sections, and a citations table with clickable SEC EDGAR links. No embedded charts in v1; pair with `generate_dcf_xlsx` / `generate_comps_xlsx` for visuals the analyst pastes in. Consumes the same `sections` + `citations` shape `create_report` emits, so the typical flow is two tool calls: `create_report` → `generate_research_brief_docx`. Tier: pro+.
generate_comps_xlsx
Render a peer comparables table into an Excel workbook. The Comps sheet is formatted as a named Excel Table (`ValueinPeerComps`) so the user gets one-click Insert Chart on any column — the cleanest workaround for not embedding chart objects server-side. Subject-row highlight makes side-by-side comparison instant. A Summary sheet adds subject vs peer-median deltas. Pair with `get_peer_comparables` for a typical flow. Tier: pro+.
Customize Tools
Edit descriptions, modify arguments, select tools, or add new ones
Set Up Your SEC EDGAR MCP Server in Minutes
Add the following configuration to your MCP client. Authentication is handled via OAuth. Compatible with Claude Desktop, Claude Code, Cursor, ChatGPT, VS Code, n8n, and any MCP-compatible agent.
Claude Desktop
{
"mcpServers": {
"willow-sec-edgar": {
"type": "http",
"url": "https://<org>.mcp-s.com/mcp/mcp/sec-edgar"
}
}
}Cursor
{
"mcpServers": {
"willow-sec-edgar": {
"type": "http",
"url": "https://<org>.mcp-s.com/mcp/mcp/sec-edgar"
}
}
}Claude Code
claude mcp add willow-sec-edgar --transport http https://<org>.mcp-s.com/mcp/mcp/sec-edgarn8n
{
"url": "https://<org>.mcp-s.com/mcp/mcp/sec-edgar",
"method": "POST"
}Or click "Install with Willow" above to set up automatically with SSO and RBAC preconfigured.
Enterprise Governance for SEC EDGAR
Willow adds the layer SEC EDGAR and every other SaaS doesn't ship out of the box: every call runs behind SSO (Okta, Azure AD), enforces RBAC and least-privilege at runtime, writes to full audit logs, and detects shadow AI usage across your stack. One MCP gateway. Any agent. Every tool.
SEC EDGAR MCP Server FAQ
What is the SEC EDGAR MCP server?
The SEC EDGAR MCP server is a Model Context Protocol implementation that lets AI agents like Claude, Cursor, and ChatGPT read and write SEC EDGAR data through a standardized interface. Willow hosts and governs this server so enterprises can roll it out without a security review backlog.
How is Willow's SEC EDGAR MCP server different from the official one?
The official SEC EDGAR MCP server is scoped to a single user's account and does not include enterprise governance. Willow's version adds SSO, RBAC, audit logging, shadow AI detection, and centralized control over which actions agents can take across the entire org.
Which AI clients work with the SEC EDGAR MCP server?
Claude Desktop, Claude Code, Cursor, ChatGPT, VS Code with MCP support, n8n, and any custom agent built with OpenAI Agents SDK, LangChain, Vercel AI SDK, or Anthropic SDK.
Is the SEC EDGAR MCP server secure? How does Willow handle authentication?
Every call runs behind your existing SSO (Okta, Azure AD). Per-user OAuth scopes the agent to exactly what that user can do in SEC EDGAR, nothing more. No credentials reach the LLM. Every action writes to an audit trail.
Can I limit which SEC EDGAR actions agents can take?
Yes. Willow lets you scope agents to specific actions, specific projects, or specific environments. Toggle actions on or off in the dashboard, or enforce policy via infrastructure-as-code through GitHub.
How do I detect shadow SEC EDGAR MCP servers in my org?
Willow's browser extension and discovery service surface unmanaged MCP servers, skills, and AI agents across the org. If a developer installed an unapproved SEC EDGAR MCP locally, you'll see it.
What does the SEC EDGAR MCP server cost?
Pricing depends on org size and deployment model (SaaS, dedicated cloud, self-host). See withwillow.ai/pricing or contact sales for a quote.
How do I install the SEC EDGAR MCP server with Willow?
Install via the Willow Connect Panel in one click, or paste the JSON snippet above into your Claude Desktop, Cursor, or Claude Code config. SSO and RBAC inherit from your existing Willow setup.
Compare Willow MCP Gateway
See how Willow stacks up against other MCP platforms on governance, security, and enterprise readiness.
Your agents are already in the wild.
Give them a Basecamp. Go from AI chaos to AI work, in minutes.