{
  "markdown": "<div align=\"center\">\n  <h1>@cyanheads/secedgar-mcp-server</h1>\n  <p><b>Query SEC EDGAR filings, XBRL financials, and company data through MCP. STDIO & Streamable HTTP.</b>\n  <div>16 Tools (+1 opt-in) • 2 Resources • 1 Prompt</div>\n  </p>\n</div>\n\n<div align=\"center\">\n\n[![npm](https://img.shields.io/npm/v/@cyanheads/secedgar-mcp-server?style=flat-square&logo=npm&logoColor=white)](https://www.npmjs.com/package/@cyanheads/secedgar-mcp-server) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![Docker](https://img.shields.io/badge/Docker-ghcr.io-2496ED?style=flat-square&logo=docker&logoColor=white)](https://github.com/users/cyanheads/packages/container/package/secedgar-mcp-server) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^2.0.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![TypeScript](https://img.shields.io/badge/TypeScript-^7.0.2-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.4.0-blueviolet.svg?style=flat-square)](https://bun.sh/)\n\n</div>\n\n<div align=\"center\">\n\n[![Install in Claude Desktop](https://img.shields.io/badge/Install_in-Claude_Desktop-D97757?style=for-the-badge&logo=anthropic&logoColor=white)](https://github.com/cyanheads/secedgar-mcp-server/releases/latest/download/secedgar-mcp-server.mcpb) [![Install in Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en/install-mcp?name=secedgar-mcp-server&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBjeWFuaGVhZHMvc2VjZWRnYXItbWNwLXNlcnZlciJdLCJlbnYiOnsiRURHQVJfVVNFUl9BR0VOVCI6IllvdXJOYW1lIHlvdXItZW1haWxAZXhhbXBsZS5jb20ifX0=) [![Install in VS Code](https://img.shields.io/badge/VS_Code-Install_Server-0098FF?style=for-the-badge&logo=visualstudiocode&logoColor=white)](https://vscode.dev/redirect?url=vscode:mcp/install?%7B%22name%22%3A%22secedgar-mcp-server%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40cyanheads/secedgar-mcp-server%22%5D%2C%22env%22%3A%7B%22EDGAR_USER_AGENT%22%3A%22YourName%20your-email%40example.com%22%7D%7D)\n\n[![Framework](https://img.shields.io/badge/Built%20on-@cyanheads/mcp--ts--core-67E8F9?style=flat-square)](https://www.npmjs.com/package/@cyanheads/mcp-ts-core)\n\n</div>\n\n<div align=\"center\">\n\n**Public Hosted Server:** [https://secedgar.caseyjhand.com/mcp](https://secedgar.caseyjhand.com/mcp)\n\n</div>\n\n---\n\n## Tools\n\nFourteen tools for querying SEC EDGAR data, plus three for SQL analytics over the DuckDB-backed canvas dataframes those tools materialize:\n\n| Tool | Description |\n|:---|:---|\n| `secedgar_company_search` | Find companies and retrieve entity info with optional recent filings |\n| `secedgar_search_filings` | Search EDGAR filings since 1993 — full-text (2001+) plus archive-backed browse for pre-2001 ranges |\n| `secedgar_get_filing` | Fetch a specific filing's metadata and document content |\n| `secedgar_get_financials` | Get historical XBRL financial data for a company |\n| `secedgar_get_snapshot` | One-call financial profile — the latest value of every supported concept, grouped by statement |\n| `secedgar_get_material_events` | 8-K filings with item codes decoded and filterable — earnings, officer changes, non-reliance |\n| `secedgar_get_insider_transactions` | Form 4 / 4-A insider transactions (buys, sells, grants, exercises) parsed from ownership XML |\n| `secedgar_get_institutional_holdings` | 13F-HR quarterly institutional holdings parsed from the information table |\n| `secedgar_find_holders` | Reverse 13F lookup — which institutional managers reported holding an issuer |\n| `secedgar_get_beneficial_owners` | 5%+ blockholders of an issuer, parsed from structured SCHEDULE 13D / 13G filings |\n| `secedgar_get_fund_holdings` | ETF and mutual fund portfolio holdings from the quarterly NPORT-P report |\n| `secedgar_fetch_frames` | Fetch SEC XBRL frames for one concept × one period across all reporting companies |\n| `secedgar_compare_companies` | Compare named companies across several concepts, aligned on calendar periods |\n| `secedgar_search_concepts` | Discover supported XBRL concept names or reverse-lookup a raw tag |\n| `secedgar_dataframe_describe` | List canvas dataframes with provenance, TTL, and schema |\n| `secedgar_dataframe_query` | Run a single-statement SELECT across dataframes |\n| `secedgar_dataframe_drop` | Drop a canvas dataframe by name. Opt-in via `EDGAR_DATAFRAME_DROP_ENABLED=true` — off by default since TTL already handles cleanup |\n\n### `secedgar_company_search`\n\nEntry point for most EDGAR workflows — resolve tickers, names, or CIKs to entity details.\n\n- Supports ticker symbols (`AAPL`, `VOO`), company names (`Apple`), or CIK numbers (`320193`)\n- ETFs and mutual funds resolve by ticker via `company_tickers_mf.json`; fund results include `series_id` and `class_id` for downstream scoping\n- Current and former company names both resolve (`Facebook` → Meta Platforms, `Square` → Block)\n- Near-match suggestions on zero-result name search (e.g. `Microsfot` → `MICROSOFT CORP / MSFT`)\n- Optionally includes recent filings with form type filtering\n- Date filtering (`filed_after` / `filed_before`) and under-filled form filters page into the older submissions archive, reaching filings that predate the ~1000-entry recent window (e.g. a 2005 10-K); `history_scanned_through` discloses the scan depth, and the full filtered history materializes as a `df_<id>` dataframe when it exceeds the inline `filing_limit`\n- Returns entity metadata: SIC code, exchanges, fiscal year end, state of incorporation\n\n---\n\n### `secedgar_search_filings`\n\nSearch EDGAR filings since 1993. Full-text search covers 2001-present (the EFTS index floor); pre-2001 date ranges are served from the archives — pre-2001 full-text matching requires entity scope.\n\n- Exact phrases (`\"material weakness\"`), boolean operators (`revenue OR income`), wildcards (`account*`)\n- Entity targeting within query string (`cik:320193` or `ticker:AAPL`) — scoped server-side by CIK, so filings made under a former company name (same CIK) are included\n- Browse mode: omit `query` to list filings by form type (`forms=[\"S-1\"]`) and/or entity (`ticker:`/`cik:`), optionally narrowed by date — a bare date range is not a valid search and must be paired with forms or entity targeting\n- Pre-2001 date ranges (back to 1993) route to the archives: an entity-scoped range reads the filer's full submissions history; an unscoped forms/date range browses the quarterly full-index. Each row carries a `source` field (`efts` / `submissions` / `full-index`), preserved into the `df_<id>` dataframe\n- Pre-2001 free text is matched by reading documents, so it needs `ticker:`/`cik:` scope to bound the work: the form + date pre-filter picks candidates, up to 50 are read, and `scan` reports candidates / scanned / matched rather than presenting a partial read as a complete one. SEC's request rate is the cost — roughly 5s for a full 50-document scan. Each read covers the whole accession `.txt` (pre-1997 filings expose no per-document URL), so a match can sit in an attached exhibit rather than the body of the requested form\n- A range crossing 2001-01-01 is split at the boundary and merged: the full-text index serves 2001 onward, the archives serve the rest. `period_ending`, `ticker`, `file_description`, `sic`, and `location` exist only on `source: efts` rows, so a merged result carries them on some rows and not others\n- Date range filtering, form type filtering, pagination up to 10,000 results\n- Returns form distribution for narrowing follow-up searches\n- When the entity-scoped window exceeds the inline limit, the already-fetched EFTS window is materialized as a `df_<id>` dataframe — query it with `secedgar_dataframe_query`\n\n---\n\n### `secedgar_get_filing`\n\nFetch a specific filing's metadata and document content by accession number.\n\n- Accepts accession numbers in dash or no-dash format\n- Converts HTML filings to readable plain text\n- Configurable content limit (1K–200K characters, default 50K)\n- Can fetch specific exhibits by document name\n- Binary entries — scanned pages, PDF exhibits, packaged archives and spreadsheets — are marked `binary` in the document catalog and rejected with a `binary_document` error instead of being returned as decoded bytes\n- **Offset paging** for large documents (10-K, S-1/A can exceed 1M chars): pass `next_offset` from a truncated response as `offset` on the next call to continue reading; first-page truncated responses include a detected `outline` (headings with offsets) for targeted navigation\n- **Section targeting** via the `section` param: jumps directly to a named heading by case-insensitive substring match (e.g. `\"risk factors\"`, `\"item 7\"`, `\"certain relationships\"`); on a miss, the error carries the detected outline so you can pick the correct heading\n- Extracted text is cached per `accession + document` (bounded LRU, 8 entries), making subsequent paged calls cheap\n\n---\n\n### `secedgar_get_financials`\n\nGet historical XBRL financial data for a company with friendly concept name resolution.\n\n- Friendly names like `\"revenue\"`, `\"net_income\"`, `\"eps_diluted\"` auto-resolve to correct XBRL tags\n- Handles historical tag changes (e.g., ASC 606 revenue recognition)\n- Automatic deduplication to one value per standard calendar period\n- Filter by annual, quarterly, or all periods\n- Optional `limit` caps the inline series to the most-recent N periods; the full series stays queryable via the `df_<id>` dataframe\n- Quarterly results carry a `caveats` entry naming every calendar quarter absent from the frame-tagged series — SEC reports fiscal Q4 as the 10-K residual, so the calendar quarter that fiscal Q4 spans has no discrete quarterly value (calendar-year filers included), and a filer whose other fiscal quarters span non-calendar durations loses a second quarter the same way\n- A further `caveats` entry when the concept resolved to an XBRL tag SEC has retired from the taxonomy — that only happens when no current tag reports for the filer, and the series can stop years short\n- See `secedgar://concepts` resource for the full mapping\n\n---\n\n### `secedgar_get_snapshot`\n\nBuild a company financial profile in one call instead of a run of `secedgar_get_financials` calls.\n\n- Reads the filer's complete companyfacts payload once, then resolves every supported concept against it\n- Same frame dedup and tag priority as `secedgar_get_financials`, so the two agree for any concept they both cover\n- Duration concepts (income statement, cash flow, per-share) report their latest full year and latest single quarter; balance-sheet and entity-info concepts report their latest point-in-time value\n- Concepts the filer does not report are listed under `gaps` with the XBRL tags that were tried — never zero-filled or interpolated\n- IFRS filers resolve through the mapped IFRS tag variants via `taxonomy: \"ifrs-full\"`, which covers the income statement, balance sheet, cash flow, and per-share concepts; each line reports the taxonomy its value came from\n- Compact single-record profile — no dataframe; reach for `secedgar_get_financials` when you need a time series\n\n---\n\n### `secedgar_get_insider_transactions`\n\nSurface Form 4 / 4-A insider activity for a company by parsing ownership XML. Form 3 initial statements and Form 5 annual statements are not covered — reach those with `secedgar_search_filings` (`forms: [\"3\", \"5\"]`) plus `secedgar_get_filing`.\n\n- Reporting person, relationship to issuer (director, officer + title, 10% owner), and transaction date\n- Transaction code mapped to a readable type (purchase, sale, gift, award, exercise, …); shares signed by acquired/disposed\n- Price per share and shares owned after each transaction; covers non-derivative (open-market) and derivative (option/RSU) lines\n- Filter by `transaction_type` (`purchase`, `sale`, `all`); scans newest filings first\n- The full set of transactions parsed from the scanned recent filings is materialized as a `df_<id>` dataframe (the inline list is a preview capped at `limit`) — query it with `secedgar_dataframe_query` to aggregate net buy/sell by insider\n\n---\n\n### `secedgar_get_institutional_holdings`\n\nSurface 13F-HR quarterly institutional holdings by parsing the information table.\n\n- Pass the institutional filer (CIK or full legal name, e.g. `0000102909` for Vanguard) to see what it holds; for the reverse direction — which managers hold a given company — use `secedgar_find_holders`, whose `filer_cik` results feed straight back into this tool\n- Each holding: issuer name, CUSIP, market value (whole USD), shares/principal, and put/call; raw rows also carry investment discretion\n- Sub-lines for the same security (one per manager/account) are consolidated into distinct positions sorted by value by default — pass `consolidate: false` for raw filing rows\n- Resolves the filing-manager name and reporting quarter from the cover page; target a specific quarter with `quarter` (e.g. `\"2025-Q4\"`)\n- `total_holdings_in_filing` counts raw info-table rows; `total_positions` counts distinct positions after consolidation (both before `limit`)\n- Page through a large information table with `offset` — the response echoes the effective `offset` and returns `next_offset` while rows remain, so every position stays reachable even when the canvas is disabled\n- The full parsed holdings set is materialized as a `df_<id>` dataframe (the inline list is one page of `limit` rows) — query it with `secedgar_dataframe_query` for full-filing aggregation or cross-quarter joins on `cusip` + `reporting_period`\n\n---\n\n### `secedgar_find_holders`\n\nReverse 13F lookup: which institutional managers reported a position in an issuer, for one reporting quarter.\n\n- Searching by `cusip` matches the identifier the 13F information table itself carries — the precise path. Louisiana-Pacific Q1 2026 returns 451 filings by CUSIP `546347105` against 43 by the phrase `\"LOUISIANA-PACIFIC CORP\"`; the name path both under-matches (managers write the name differently) and over-matches (an unrelated issuer sharing a word)\n- A CUSIP is not derivable from a ticker anywhere in EDGAR — read one off any `secedgar_get_institutional_holdings` result, or fall back to the name path\n- `quarter` targets a reporting period (`\"2026-Q1\"`); omit it for the newest quarter whose 45-day filing deadline has passed. The applied quarter and its filing window are echoed back\n- Filings are kept by the period they report, not the date they were filed, so amendments restating an older quarter (roughly 6% of any window) do not land in the wrong quarter's holder list\n- Up to 500 filer rows are fetched per call; `total_filings` reports the full count and `dataset.truncated` flags when more exist\n- **The list is unranked.** EDGAR search relevance carries no signal about position size — read a manager's actual position by passing its `filer_cik` to `secedgar_get_institutional_holdings`\n\n---\n\n### `secedgar_get_beneficial_owners`\n\nThe 5%-and-over stakes in an issuer — the blockholder layer between Form 4 insiders and 13F portfolios. Input is the issuer, the company being held.\n\n- 13D is the activist form and carries the filer's stated purpose of the transaction; 13G is the passive form and has no purpose item at all, which is the substantive difference between a stake that intends to influence control and one that does not. Filter with `form_kind`\n- Every reporting person is listed separately. Voting power, dispositive power, and percent of class are reported per person even on a joint filing where several funds and their controlling principal report the same underlying shares — summing those percentages double-counts the position\n- Coverage starts **2024-12-18**, when SEC replaced the legacy `SC 13D` / `SC 13G` text filings with structured XML under the current `SCHEDULE 13D` / `SCHEDULE 13G` names. Earlier stakes are readable but not parseable, and `legacy_filings_before_coverage` reports how many the issuer has — reach them with `secedgar_search_filings` and read them with `secedgar_get_filing`\n- Amendments carry the current position and are included by default; `include_amendments=false` leaves only the filings that opened a position\n- The full parsed set registers as a `df_<id>` dataframe at one row per reporting person, so it joins the insider and 13F dataframes on issuer CIK\n\n---\n\n### `secedgar_get_fund_holdings`\n\nWhat an ETF or mutual fund owns, from the NPORT-P portfolio report it files each quarter — the inverse of the ownership tools, which answer who owns a company.\n\n- Input is the fund: a ticker (`VOO`), an SEC fund series ID (`S000002839`), or a CIK. Fund trusts are indexed by ticker and series rather than by name, so name the registrant by CIK unless the fund itself trades under that name (`SPDR S&P 500 ETF Trust`)\n- An NPORT-P covers exactly one fund series and a registrant trust files one report per series per period, so a trust running several funds needs the specific fund named. A registrant that resolves to more than one series comes back with the series listed, each with its ticker; one whose series carry no ticker is routed by reading the series off its newest report, because a trust's own filing history interleaves funds whose fiscal quarters end on different months\n- Every result is dated to `report_period_date`. Reports publish roughly two months after the period they cover, so the holdings are the portfolio as of that date, not as of today; `publication_lag_days` states the gap. Target an earlier period with `report_date`, chosen from the `available_report_periods` in any response\n- Positions carry the security name, CUSIP/ISIN/LEI where the filer reports them, share balance, USD value, and percent of net assets, alongside fund-level net assets, total assets, and total liabilities\n- Positions come back largest first by percent of net assets, one page of `limit` rows from `offset`. A broad index fund reports thousands — Vanguard Total Stock Market's most recent report carries 3,524 — so the full report registers as a `df_<id>` dataframe for aggregation and for joining the 13F and insider dataframes on CUSIP\n\n---\n\n### `secedgar_get_material_events`\n\nA company's 8-K history with item codes decoded and filterable — the only surface that can scope by what the event actually was rather than by form.\n\n- Filter with `items` (e.g. `[\"2.02\"]` for results of operations, `[\"5.02\"]` for officer departures, `[\"4.02\"]` for non-reliance); `secedgar_search_filings` and `secedgar_company_search` cannot see items at all\n- Two numbering regimes are both accepted and decoded: the dotted scheme in force since 2004-08-23, and the single integers before it (legacy `12` is the ancestor of `2.02`, `9` of `7.01`). Decoding keys off the code's shape, so a filing straddling the changeover is never mis-decoded, and a window spanning it needs both codes in the filter\n- `item_distribution` counts every code across the scanned window before the filter, so a zero-hit filter comes back with the items that *are* present rather than a dead end\n- A date window pages into the older submissions archive, reaching 8-K filings that predate the ~1000-filing recent window; `history_scanned_through` discloses the scan depth\n- The full decode table is in the `secedgar://filing-types` resource\n- The full filtered set materializes as a `df_<id>` dataframe with item codes on every row — item frequency over time is one `secedgar_dataframe_query` away\n\n---\n\n### `secedgar_fetch_frames`\n\nFetch SEC XBRL frames for one concept × one period across all reporting companies.\n\n- Same friendly concept names as `secedgar_get_financials`\n- Supports annual (`CY2023`), quarterly (`CY2024Q2`), and instant (`CY2023Q4I`) periods\n- Inline response returns one page of the ranked companies (sort + limit), with ticker enrichment\n- Walk further down the ranking with `offset` — the response echoes the effective `offset` and returns `next_offset` while companies remain, so ranks past the first page stay reachable even when the canvas is disabled\n- The full frames response (all reporters, typically 2k–10k rows) is materialized as a `df_<id>` dataframe — query it with `secedgar_dataframe_query`\n- `related_tags` flags alternate-definition tags some filers use as their primary line (e.g. `cash` → restricted-cash-inclusive total, `equity` → NCI-inclusive total), so a whole-universe screen on the base tag isn't silently under-inclusive — query those separately\n\n---\n\n### `secedgar_compare_companies`\n\nCompare 2-10 named companies across 1-8 concepts, aligned on calendar periods — the middle shape between `secedgar_get_financials` (one company over time) and `secedgar_fetch_frames` (one period across the market).\n\n- One companyfacts read per company, resolved through the same frame dedup and tag priority as `secedgar_get_financials`\n- Balance-sheet and entity-info concepts align on the calendar year or quarter their point-in-time snapshot falls in, so they sit in the same matrix as income-statement lines; each cell keeps its underlying XBRL frame\n- `periods` bounds the inline matrix (1-12, default 4) and the window shrinks further when companies x concepts x periods is too large to return in one response; the full aligned series is always materialized as a `df_<id>` dataframe for growth rates and spreads via `secedgar_dataframe_query`\n- A company that fails to resolve is reported in `failed_companies` with a machine-readable reason and the comparison proceeds with the rest\n- A company that does not report a concept is reported in `gaps` with the tags that were tried — never interpolated\n- `caveats` surface a filer missing one or two calendar quarters, a concept that resolved to a retired XBRL tag for one company, period ends that differ inside one aligned period, and concepts whose unit differs across companies\n\n---\n\n### `secedgar_search_concepts`\n\nDiscover supported XBRL concept names before querying financials or cross-company comparisons.\n\n- Search by friendly name, label, or raw XBRL tag\n- Filter by statement group (`income_statement`, `balance_sheet`, `cash_flow`, `per_share`, `entity_info`) or taxonomy\n- Reverse-lookup raw tags like `NetIncomeLoss` to the supported friendly names\n- Surfaces `related_tags` for concepts with a high-coverage alternate-definition tag (e.g. restricted-cash-inclusive cash) so callers can discover them before screening\n- Filtering by `taxonomy: \"ifrs-full\"` narrows the catalog to concepts with an IFRS tag confirmed against live 20-F filings; a concept with no IFRS equivalent is left out rather than mapped to a guess\n- Returns the same catalog used by `secedgar_get_financials`, `secedgar_fetch_frames`, and `secedgar://concepts`\n\n---\n\n### `secedgar_dataframe_describe` / `secedgar_dataframe_query` / `secedgar_dataframe_drop`\n\nIn-conversation SQL analytics over the dataframes that `secedgar_fetch_frames`, `secedgar_compare_companies`, `secedgar_search_filings`, `secedgar_get_financials`, `secedgar_get_material_events`, `secedgar_get_insider_transactions`, `secedgar_get_institutional_holdings`, and `secedgar_find_holders` materialize on a shared DuckDB-backed canvas. Each data-returning call adds a `dataset` field with a `df_XXXXX_XXXXX` handle; pass that handle to `secedgar_dataframe_query` for joins, aggregates, window functions, percentiles — standard DuckDB SQL.\n\n- **Read-only by default.** Writes, DDL, DROP, COPY, PRAGMA, ATTACH, and external-file table functions are rejected by the framework SQL gate. System catalogs (`information_schema`, `pg_catalog`, `sqlite_master`, `duckdb_*`) are denied at the bridge layer so callers can't enumerate dataframes they don't already hold a handle for. `secedgar_dataframe_drop` is the only destructive tool and is opt-in (`EDGAR_DATAFRAME_DROP_ENABLED=true`); TTL handles cleanup otherwise.\n- **Per-table TTL.** Each dataframe ages on its own clock (default 24h, override with `EDGAR_DATASET_TTL_SECONDS`). The canvas itself uses the framework's sliding TTL.\n- **`register_as` chaining.** `secedgar_dataframe_query` can persist its result as a new dataframe (`df_XXXXX_XXXXX`) with a fresh TTL — pipe analyses without re-running the source query.\n\n## Resources\n\n| URI | Description |\n|:---|:---|\n| `secedgar://concepts` | Common XBRL financial concepts grouped by statement, mapping friendly names to XBRL tags |\n| `secedgar://filing-types` | Common SEC filing types with descriptions, cadence, and use cases, plus the full 8-K item-code decode tables for both numbering regimes |\n\n## Prompts\n\n| Prompt | Description |\n|:---|:---|\n| `secedgar_company_analysis` | Guides a structured analysis of a public company's SEC filings: identify recent filings, extract financial trends, surface risk factors, and note material events |\n\n## Features\n\nBuilt on [`@cyanheads/mcp-ts-core`](https://github.com/cyanheads/mcp-ts-core):\n\n- Declarative tool definitions — single file per tool, framework handles registration and validation\n- Structured output schemas with automatic formatting for human-readable display\n- Unified error handling across all tools\n- Pluggable auth (`none`, `jwt`, `oauth`)\n- Structured logging with request-scoped context\n- Runs locally (stdio/HTTP) from the same codebase\n\nSEC EDGAR–specific:\n\n- Rate-limited HTTP client respecting SEC's 10 req/s limit with automatic inter-request delay\n- CIK resolution from tickers (including ETFs and mutual funds via `company_tickers_mf.json`), company names (current and former), or raw CIK numbers with local caching; near-match trigram suggestions on zero-result name queries; committed `former-names.json` asset for prior-name resolution (`Facebook` → Meta, `Square` → Block)\n- Friendly XBRL concept name mapping with historical tag change handling\n- Searchable concept catalog with statement-group metadata and reverse XBRL tag lookup\n- HTML-to-text conversion for filing documents via `html-to-text`\n- In-conversation SQL analytics: `secedgar_fetch_frames`, `secedgar_compare_companies`, `secedgar_search_filings`, `secedgar_get_financials`, `secedgar_get_material_events`, `secedgar_get_insider_transactions`, `secedgar_get_institutional_holdings`, and `secedgar_find_holders` materialize their full result as a DuckDB-backed canvas dataframe queryable via `secedgar_dataframe_query`\n- No API keys required — SEC EDGAR is a free, public API\n\n## Getting started\n\n### Public Hosted Instance\n\nA public instance is available at `https://secedgar.caseyjhand.com/mcp` — no installation required. Point any MCP client at it via Streamable HTTP:\n\n```json\n{\n  \"mcpServers\": {\n    \"secedgar-mcp-server\": {\n      \"type\": \"streamable-http\",\n      \"url\": \"https://secedgar.caseyjhand.com/mcp\"\n    }\n  }\n}\n```\n\n### Self-Hosted / Local\n\nAdd the following to your MCP client configuration file.\n\n```json\n{\n  \"mcpServers\": {\n    \"secedgar-mcp-server\": {\n      \"type\": \"stdio\",\n      \"command\": \"bunx\",\n      \"args\": [\"@cyanheads/secedgar-mcp-server@latest\"],\n      \"env\": {\n        \"EDGAR_USER_AGENT\": \"YourAppName your-email@example.com\",\n        \"MCP_TRANSPORT_TYPE\": \"stdio\"\n      }\n    }\n  }\n}\n```\n\nOr with npx (no Bun required):\n\n```json\n{\n  \"mcpServers\": {\n    \"secedgar-mcp-server\": {\n      \"type\": \"stdio\",\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@cyanheads/secedgar-mcp-server@latest\"],\n      \"env\": {\n        \"EDGAR_USER_AGENT\": \"YourAppName your-email@example.com\",\n        \"MCP_TRANSPORT_TYPE\": \"stdio\"\n      }\n    }\n  }\n}\n```\n\nFor Streamable HTTP, set the transport and start the server:\n\n```sh\nMCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http\n# Server listens at http://localhost:3010/mcp\n```\n\n### Prerequisites\n\n- [Bun v1.3.0](https://bun.sh/) or higher.\n\n### Installation\n\n1. **Clone the repository:**\n\n```sh\ngit clone https://github.com/cyanheads/secedgar-mcp-server.git\n```\n\n2. **Navigate into the directory:**\n\n```sh\ncd secedgar-mcp-server\n```\n\n3. **Install dependencies:**\n\n```sh\nbun install\n```\n\n4. **Build:**\n\n```sh\nbun run build\n```\n\n## Configuration\n\nAll configuration is validated at startup via Zod schemas in `src/config/server-config.ts`. Key environment variables:\n\n| Variable | Description | Default |\n|:---|:---|:---|\n| `EDGAR_USER_AGENT` | **Required.** User-Agent header for SEC compliance. Format: `\"AppName contact@email.com\"`. SEC blocks IPs without a valid User-Agent. | — |\n| `EDGAR_RATE_LIMIT_RPS` | Max requests/second to SEC APIs. Do not exceed 10. | `10` |\n| `EDGAR_TICKER_CACHE_TTL` | Seconds to cache the company tickers lookup file. | `3600` |\n| `EDGAR_DATASET_TTL_SECONDS` | Per-table TTL for canvas-registered dataframes. Sliding window touched on every dataframe op. | `86400` |\n| `EDGAR_DATAFRAME_DROP_ENABLED` | Set to `true` to expose `secedgar_dataframe_drop` — the only destructive tool on this server. Off by default; TTL handles cleanup. | `false` |\n| `EDGAR_MIRROR_ENABLED` | Enable the local SQLite mirror of `company_tickers` + XBRL company-facts so CIK resolution and financials read from disk instead of the live API. Node/Bun only (skipped on Workers). Bootstrap once with `bun run mirror:init`. | `false` |\n| `EDGAR_MIRROR_PATH` | Directory holding the mirror SQLite databases. | `./data/edgar-mirror` |\n| `EDGAR_MIRROR_REFRESH_CRON` | Cron for the in-process nightly refresh (HTTP transport only). Recommended `0 9 * * *`. Omit to refresh out-of-band via `bun run mirror:refresh`. | — |\n| `EDGAR_MIRROR_FALLBACK_LIVE` | When the mirror misses (not yet synced, or a filing newer than the last refresh), fall back to the live SEC API. Set `false` for strict mirror-only reads. | `true` |\n| `CANVAS_PROVIDER_TYPE` | Canvas engine. Defaults to `duckdb`; set to `none` to disable the canvas (e.g. when running on Cloudflare Workers, where DuckDB has no V8-isolate build). | `duckdb` |\n| `MCP_TRANSPORT_TYPE` | Transport: `stdio` or `http` | `stdio` |\n| `MCP_HTTP_PORT` | HTTP server port | `3010` |\n| `MCP_AUTH_MODE` | Authentication: `none`, `jwt`, or `oauth` | `none` |\n| `MCP_LOG_LEVEL` | Log level (`debug`, `info`, `warning`, `error`, etc.) | `info` |\n| `LOGS_DIR` | Directory for log files (Node.js only). | `<project-root>/logs` |\n\n## Running the server\n\n### Local development\n\n- **Build and run the production version:**\n\n  ```sh\n  bun run rebuild\n  bun run start:http   # or start:stdio\n  ```\n\n- **Run checks and tests:**\n  ```sh\n  bun run devcheck     # Lints, formats, type-checks\n  bun run test         # Runs test suite\n  ```\n\n### Docker\n\n```sh\ndocker build -t secedgar-mcp-server .\ndocker run -e EDGAR_USER_AGENT=\"MyApp my@email.com\" -p 3010:3010 secedgar-mcp-server\n```\n\nThe image ships the mirror CLI, so the local mirror (`EDGAR_MIRROR_ENABLED`) can be bootstrapped, inspected, and refreshed inside a running container:\n\n```sh\ndocker exec <container> bun run mirror:verify    # sync status + sample reads\ndocker exec <container> bun run mirror:init      # one-time bootstrap (downloads the SEC bulk archive)\ndocker exec <container> bun run mirror:refresh   # re-ingest when the archive has been rebuilt\n```\n\n## Project structure\n\n| Directory | Purpose |\n|:---|:---|\n| `src/mcp-server/tools/definitions/` | Tool definitions (`*.tool.ts`). Ten SEC EDGAR tools plus three `dataframe_*` tools for SQL analytics. |\n| `src/mcp-server/resources/definitions/` | Resource definitions. XBRL concepts and filing types. |\n| `src/mcp-server/prompts/definitions/` | Prompt definitions. Company analysis prompt. |\n| `src/services/edgar/` | SEC EDGAR API client, XBRL concept mapping, HTML-to-text conversion. |\n| `src/services/canvas-bridge/` | Adapter over the framework `DataCanvas`: `df_<id>` minting, all-nullable schema derivation, per-table TTL bookkeeping, bridge-layer system-catalog SQL deny. |\n| `src/config/` | Server-specific environment variable parsing and validation with Zod. |\n| `tests/` | Unit and integration tests, mirroring the `src/` structure. |\n\n## Development guide\n\nSee [`CLAUDE.md`](./CLAUDE.md) and [`AGENTS.md`](./AGENTS.md) for development guidelines and architectural rules. The short version:\n\n- Handlers throw, framework catches — no `try/catch` in tool logic\n- Use `ctx.log` for logging, `ctx.state` for storage\n- Register new tools and resources in the `createApp()` arrays\n\n## Contributing\n\nIssues and pull requests are welcome. Run checks and tests before submitting:\n\n```sh\nbun run devcheck\nbun run test\n```\n\n## License\n\nThis project is licensed under the Apache 2.0 License. See the [LICENSE](./LICENSE) file for details.\n",
  "bytes": 32379,
  "sha": "17113aa6e4228dd21c792c3af851e9128cc174fcac2474f6f1377b9fee1280b1",
  "repo_slug": "cyanheads/secedgar-mcp-server",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_cyanheads_secedgar_mcp_server_31e7a0a2/readme"
}