You can enter a 10-K at risk factors instead of walking it by offset, and the tools that return long tables leave a dataframe behind that a single SELECT reads, with writes, DDL and system catalogs rejected. The direction of each ownership tool is spelled out in its own description, which is the part people get wrong: manager in for institutional holdings, issuer in for find_holders and beneficial owners, fund in for fund holdings. Coverage has edges worth planning around — full text starts at 2001, pre-2001 free text needs an entity scope, and the dataframe listing reports when a result was truncated relative to its source.
A client for SEC EDGAR covering entity search, filing documents, XBRL company facts and frames, and the ownership filings — Form 4, 13F-HR, SCHEDULE 13D and 13G, NPORT-P — with a DuckDB-backed dataframe layer that keeps large results queryable instead of truncated.
- Filing text that survives long documents: the primary document as readable text, entered at a named section such as risk factors or item 7, or paged with next_offset for filings past 1M characters (secedgar_get_filing, secedgar_company_search).
- Search with its coverage stated rather than implied: full text from 2001 onward, back to 1993 from the archives by form and entity date, and pre-2001 free text requiring a ticker or CIK scope (secedgar_search_filings).
- Financials read from XBRL rather than from prose — one concept's full history, or the latest value of every supported concept in a single companyfacts read, with a concept search to find the right name first (secedgar_get_financials, secedgar_get_snapshot, secedgar_search_concepts).
- Comparison at either width: 2 to 10 named companies across 1 to 8 concepts aligned on calendar periods, or every reporting company ranked on one concept for one period (secedgar_compare_companies, secedgar_fetch_frames).
- Ownership from both directions, each tool stating which end it takes: a manager's 13F portfolio, the managers reporting one issuer, the 5%-and-over blockholders from SCHEDULE 13D and 13G, and an ETF's positions from NPORT-P (secedgar_get_institutional_holdings, secedgar_find_holders, secedgar_get_beneficial_owners, secedgar_get_fund_holdings).
- Events and insiders decoded: 8-K filings with their item codes resolved, and Form 4 XML turned into reporting person, relationship, date, direction, shares and price per share (secedgar_get_material_events, secedgar_get_insider_transactions).
- A queryable result layer: the dataframes the search, frames, financials, insider and holdings tools materialize, listed with provenance, row counts, schema and expiry, and read by a single read-only SELECT (secedgar_dataframe_describe, secedgar_dataframe_query).
EDGAR_USER_AGENT is the one required setting — the SEC expects requests to identify themselves as "AppName contact@email.com". No key and no account beyond that, since EDGAR is public. Run it over stdio with Bun, serve it over Streamable HTTP on port 3010, or point a client at the public hosted instance at https://secedgar.caseyjhand.com/mcp. One extra tool, secedgar_dataframe_drop, is off unless EDGAR_DATAFRAME_DROP_ENABLED is true, because dataframes expire on their own.
One command — npx -y @cyanheads/secedgar-mcp-server
