Featured

Deploy OpenClaw in 60 seconds — 20% off logoDeploy OpenClaw in 60 seconds — 20% off

Launch OpenClaw on Hostinger in about 60 seconds and keep your agent live 24/7. Our referral link gives you 20% off, no coupon code needed.

Launch on Hostinger
Run your Hermes agent on Hostinger, fully managed logoRun your Hermes agent on Hostinger, fully managed

Launch Hermes on Hostinger in one click, fully managed, no VPS knowledge needed. Use code ZACAARON10 for 10% off.

Launch on Hostinger
Crawl and scrape any site into clean data, 10% off logoCrawl and scrape any site into clean data, 10% off

Firecrawl crawls and scrapes any site into clean markdown for your agent. Get 1,000 free credits, and new users get 10% off their first purchase.

Try Firecrawl free
Your own AI agent, running 24/7 with QwikClaw logoYour own AI agent, running 24/7 with QwikClaw

QwikClaw sets up and runs an always-on OpenClaw agent for you. One click, no config files, no server setup.

Deploy now
One API to scrape, enrich, and extract the internet. logoOne API to scrape, enrich, and extract the internet.

Context.dev gives your agents a single API to scrape, enrich, and extract live web data — no proxies, no parsers, no maintenance.

Start building free
SetupClaw: done-for-you OpenClaw for founders & exec teams logoSetupClaw: done-for-you OpenClaw for founders & exec teams

White-glove OpenClaw for founders and exec teams (4–50+ employees): we install, harden, integrate your tools, and maintain it — secured from day one.

Get it set up for you
SEO data APIs for your agent, $1 free credit logoSEO data APIs for your agent, $1 free credit

DataForSEO gives your agent live access to SERP results, keyword data, backlinks, and on-page SEO data through one API. New accounts get a $1 credit, good for up to 20,000 keyword or backlink lookups.

Try DataForSEO free
Reach 47,000+ AI builders

A flat monthly placement in front of developers actively installing AI tools. No lock-in, cancel anytime.

Advertise here

Works with

Claude CodeClaude DesktopCursorVS CodeClineCodex CLIOpenClaw+ any MCP client

Install to Claude Code

This server doesn't publish a one-line install command. Follow the setup in the source repository.

Summary

Query SEC EDGAR filings, XBRL financials, and company data through MCP. STDIO & Streamable HTTP.

README.md

<div align="center"> <h1>@cyanheads/secedgar-mcp-server</h1> <p><b>Query SEC EDGAR filings, XBRL financials, and company data through MCP. STDIO & Streamable HTTP.</b> <div>16 Tools (+1 opt-in) • 2 Resources • 1 Prompt</div> </p> </div>

<div align="center">

![npm](https://www.npmjs.com/package/@cyanheads/secedgar-mcp-server) ![License](./LICENSE) ![Docker](https://github.com/users/cyanheads/packages/container/package/secedgar-mcp-server) ![MCP SDK](https://modelcontextprotocol.io/) ![TypeScript](https://www.typescriptlang.org/) ![Bun](https://bun.sh/)

</div>

<div align="center">

![Install in Claude Desktop](https://github.com/cyanheads/secedgar-mcp-server/releases/latest/download/secedgar-mcp-server.mcpb) ![Install in Cursor](https://cursor.com/en/install-mcp?name=secedgar-mcp-server&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBjeWFuaGVhZHMvc2VjZWRnYXItbWNwLXNlcnZlciJdLCJlbnYiOnsiRURHQVJfVVNFUl9BR0VOVCI6IllvdXJOYW1lIHlvdXItZW1haWxAZXhhbXBsZS5jb20ifX0=) ![Install in VS Code](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)

![Framework](https://www.npmjs.com/package/@cyanheads/mcp-ts-core)

</div>

<div align="center">

Public Hosted Server: https://secedgar.caseyjhand.com/mcp

</div>

---

Tools

Fourteen tools for querying SEC EDGAR data, plus three for SQL analytics over the DuckDB-backed canvas dataframes those tools materialize:

| Tool | Description | |:---|:---| | secedgar_company_search | Find companies and retrieve entity info with optional recent filings | | secedgar_search_filings | Search EDGAR filings since 1993 — full-text (2001+) plus archive-backed browse for pre-2001 ranges | | secedgar_get_filing | Fetch a specific filing's metadata and document content | | secedgar_get_financials | Get historical XBRL financial data for a company | | secedgar_get_snapshot | One-call financial profile — the latest value of every supported concept, grouped by statement | | secedgar_get_material_events | 8-K filings with item codes decoded and filterable — earnings, officer changes, non-reliance | | secedgar_get_insider_transactions | Form 4 / 4-A insider transactions (buys, sells, grants, exercises) parsed from ownership XML | | secedgar_get_institutional_holdings | 13F-HR quarterly institutional holdings parsed from the information table | | secedgar_find_holders | Reverse 13F lookup — which institutional managers reported holding an issuer | | secedgar_get_beneficial_owners | 5%+ blockholders of an issuer, parsed from structured SCHEDULE 13D / 13G filings | | secedgar_get_fund_holdings | ETF and mutual fund portfolio holdings from the quarterly NPORT-P report | | secedgar_fetch_frames | Fetch SEC XBRL frames for one concept × one period across all reporting companies | | secedgar_compare_companies | Compare named companies across several concepts, aligned on calendar periods | | secedgar_search_concepts | Discover supported XBRL concept names or reverse-lookup a raw tag | | secedgar_dataframe_describe | List canvas dataframes with provenance, TTL, and schema | | secedgar_dataframe_query | Run a single-statement SELECT across dataframes | | 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 |

secedgar_company_search

Entry point for most EDGAR workflows — resolve tickers, names, or CIKs to entity details.

  • Supports ticker symbols (AAPL, VOO), company names (Apple), or CIK numbers (320193)
  • ETFs and mutual funds resolve by ticker via company_tickers_mf.json; fund results include series_id and class_id for downstream scoping
  • Current and former company names both resolve (Facebook → Meta Platforms, Square → Block)
  • Near-match suggestions on zero-result name search (e.g. MicrosfotMICROSOFT CORP / MSFT)
  • Optionally includes recent filings with form type filtering
  • 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
  • Returns entity metadata: SIC code, exchanges, fiscal year end, state of incorporation

---

secedgar_search_filings

Search 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.

  • Exact phrases ("material weakness"), boolean operators (revenue OR income), wildcards (account*)
  • 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
  • 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
  • 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
  • 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
  • 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
  • Date range filtering, form type filtering, pagination up to 10,000 results
  • Returns form distribution for narrowing follow-up searches
  • 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

---

secedgar_get_filing

Fetch a specific filing's metadata and document content by accession number.

  • Accepts accession numbers in dash or no-dash format
  • Converts HTML filings to readable plain text
  • Configurable content limit (1K–200K characters, default 50K)
  • Can fetch specific exhibits by document name
  • 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
  • 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
  • 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
  • Extracted text is cached per accession + document (bounded LRU, 8 entries), making subsequent paged calls cheap

---

secedgar_get_financials

Get historical XBRL financial data for a company with friendly concept name resolution.

  • Friendly names like "revenue", "net_income", "eps_diluted" auto-resolve to correct XBRL tags
  • Handles historical tag changes (e.g., ASC 606 revenue recognition)
  • Automatic deduplication to one value per standard calendar period
  • Filter by annual, quarterly, or all periods
  • Optional limit caps the inline series to the most-recent N periods; the full series stays queryable via the df_<id> dataframe
  • 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
  • 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
  • See secedgar://concepts resource for the full mapping

---

secedgar_get_snapshot

Build a company financial profile in one call instead of a run of secedgar_get_financials calls.

  • Reads the filer's complete companyfacts payload once, then resolves every supported concept against it
  • Same frame dedup and tag priority as secedgar_get_financials, so the two agree for any concept they both cover
  • 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
  • Concepts the filer does not report are listed under gaps with the XBRL tags that were tried — never zero-filled or interpolated
  • 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
  • Compact single-record profile — no dataframe; reach for secedgar_get_financials when you need a time series

---

secedgar_get_insider_transactions

Surface 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.

  • Reporting person, relationship to issuer (director, officer + title, 10% owner), and transaction date
  • Transaction code mapped to a readable type (purchase, sale, gift, award, exercise, …); shares signed by acquired/disposed
  • Price per share and shares owned after each transaction; covers non-derivative (open-market) and derivative (option/RSU) lines
  • Filter by transaction_type (purchase, sale, all); scans newest filings first
  • 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

---

secedgar_get_institutional_holdings

Surface 13F-HR quarterly institutional holdings by parsing the information table.

  • 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
  • Each holding: issuer name, CUSIP, market value (whole USD), shares/principal, and put/call; raw rows also carry investment discretion
  • 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
  • Resolves the filing-manager name and reporting quarter from the cover page; target a specific quarter with quarter (e.g. "2025-Q4")
  • total_holdings_in_filing counts raw info-table rows; total_positions counts distinct positions after consolidation (both before limit)
  • 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
  • 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

---

secedgar_find_holders

Reverse 13F lookup: which institutional managers reported a position in an issuer, for one reporting quarter.

  • 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)
  • 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
  • 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
  • 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
  • Up to 500 filer rows are fetched per call; total_filings reports the full count and dataset.truncated flags when more exist
  • 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

---

secedgar_get_beneficial_owners

The 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.

  • 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
  • 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
  • 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
  • Amendments carry the current position and are included by default; include_amendments=false leaves only the filings that opened a position
  • 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

---

secedgar_get_fund_holdings

What 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.

  • 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)
  • 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
  • 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
  • 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
  • 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

---

secedgar_get_material_events

A 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.

  • 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
  • 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
  • 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
  • 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
  • The full decode table is in the secedgar://filing-types resource
  • 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

---

secedgar_fetch_frames

Fetch SEC XBRL frames for one concept × one period across all reporting companies.

  • Same friendly concept names as secedgar_get_financials
  • Supports annual (CY2023), quarterly (CY2024Q2), and instant (CY2023Q4I) periods
  • Inline response returns one page of the ranked companies (sort + limit), with ticker enrichment
  • 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
  • The full frames response (all reporters, typically 2k–10k rows) is materialized as a df_<id> dataframe — query it with secedgar_dataframe_query
  • 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

---

secedgar_compare_companies

Compare 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).

  • One companyfacts read per company, resolved through the same frame dedup and tag priority as secedgar_get_financials
  • 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
  • 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
  • A company that fails to resolve is reported in failed_companies with a machine-readable reason and the comparison proceeds with the rest
  • A company that does not report a concept is reported in gaps with the tags that were tried — never interpolated
  • 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

---

secedgar_search_concepts

Discover supported XBRL concept names before querying financials or cross-company comparisons.

  • Search by friendly name, label, or raw XBRL tag
  • Filter by statement group (income_statement, balance_sheet, cash_flow, per_share, entity_info) or taxonomy
  • Reverse-lookup raw tags like NetIncomeLoss to the supported friendly names
  • 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
  • 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
  • Returns the same catalog used by secedgar_get_financials, secedgar_fetch_frames, and secedgar://concepts

---

secedgar_dataframe_describe / secedgar_dataframe_query / secedgar_dataframe_drop

In-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.

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

Resources

| URI | Description | |:---|:---| | secedgar://concepts | Common XBRL financial concepts grouped by statement, mapping friendly names to XBRL tags | | 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 |

Prompts

| Prompt | Description | |:---|:---| | 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 |

Features

Built on @cyanheads/mcp-ts-core:

  • Declarative tool definitions — single file per tool, framework handles registration and validation
  • Structured output schemas with automatic formatting for human-readable display
  • Unified error handling across all tools
  • Pluggable auth (none, jwt, oauth)
  • Structured logging with request-scoped context
  • Runs locally (stdio/HTTP) from the same codebase

SEC EDGAR–specific:

  • Rate-limited HTTP client respecting SEC's 10 req/s limit with automatic inter-request delay
  • 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)
  • Friendly XBRL concept name mapping with historical tag change handling
  • Searchable concept catalog with statement-group metadata and reverse XBRL tag lookup
  • HTML-to-text conversion for filing documents via html-to-text
  • 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
  • No API keys required — SEC EDGAR is a free, public API

Getting started

Public Hosted Instance

A public instance is available at https://secedgar.caseyjhand.com/mcp — no installation required. Point any MCP client at it via Streamable HTTP:

{
  "mcpServers": {
    "secedgar-mcp-server": {
      "type": "streamable-http",
      "url": "https://secedgar.caseyjhand.com/mcp"
    }
  }
}

Self-Hosted / Local

Add the following to your MCP client configuration file.

{
  "mcpServers": {
    "secedgar-mcp-server": {
      "type": "stdio",
      "command": "bunx",
      "args": ["@cyanheads/secedgar-mcp-server@latest"],
      "env": {
        "EDGAR_USER_AGENT": "YourAppName your-email@example.com",
        "MCP_TRANSPORT_TYPE": "stdio"
      }
    }
  }
}

Or with npx (no Bun required):

{
  "mcpServers": {
    "secedgar-mcp-server": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@cyanheads/secedgar-mcp-server@latest"],
      "env": {
        "EDGAR_USER_AGENT": "YourAppName your-email@example.com",
        "MCP_TRANSPORT_TYPE": "stdio"
      }
    }
  }
}

For Streamable HTTP, set the transport and start the server:

MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http
# Server listens at http://localhost:3010/mcp

Prerequisites

Installation

  1. Clone the repository:
git clone https://github.com/cyanheads/secedgar-mcp-server.git
  1. Navigate into the directory:
cd secedgar-mcp-server
  1. Install dependencies:
bun install
  1. Build:
bun run build

Configuration

All configuration is validated at startup via Zod schemas in src/config/server-config.ts. Key environment variables:

| Variable | Description | Default | |:---|:---|:---| | EDGAR_USER_AGENT | Required. User-Agent header for SEC compliance. Format: "AppName contact@email.com". SEC blocks IPs without a valid User-Agent. | — | | EDGAR_RATE_LIMIT_RPS | Max requests/second to SEC APIs. Do not exceed 10. | 10 | | EDGAR_TICKER_CACHE_TTL | Seconds to cache the company tickers lookup file. | 3600 | | EDGAR_DATASET_TTL_SECONDS | Per-table TTL for canvas-registered dataframes. Sliding window touched on every dataframe op. | 86400 | | 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 | | 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 | | EDGAR_MIRROR_PATH | Directory holding the mirror SQLite databases. | ./data/edgar-mirror | | 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. | — | | 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 | | 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 | | MCP_TRANSPORT_TYPE | Transport: stdio or http | stdio | | MCP_HTTP_PORT | HTTP server port | 3010 | | MCP_AUTH_MODE | Authentication: none, jwt, or oauth | none | | MCP_LOG_LEVEL | Log level (debug, info, warning, error, etc.) | info | | LOGS_DIR | Directory for log files (Node.js only). | <project-root>/logs |

Running the server

Local development

  • Build and run the production version:
  bun run rebuild
  bun run start:http   # or start:stdio
  • Run checks and tests:
  bun run devcheck     # Lints, formats, type-checks
  bun run test         # Runs test suite

Docker

docker build -t secedgar-mcp-server .
docker run -e EDGAR_USER_AGENT="MyApp my@email.com" -p 3010:3010 secedgar-mcp-server

The image ships the mirror CLI, so the local mirror (EDGAR_MIRROR_ENABLED) can be bootstrapped, inspected, and refreshed inside a running container:

docker exec <container> bun run mirror:verify    # sync status + sample reads
docker exec <container> bun run mirror:init      # one-time bootstrap (downloads the SEC bulk archive)
docker exec <container> bun run mirror:refresh   # re-ingest when the archive has been rebuilt

Project structure

| Directory | Purpose | |:---|:---| | src/mcp-server/tools/definitions/ | Tool definitions (.tool.ts). Ten SEC EDGAR tools plus three dataframe_ tools for SQL analytics. | | src/mcp-server/resources/definitions/ | Resource definitions. XBRL concepts and filing types. | | src/mcp-server/prompts/definitions/ | Prompt definitions. Company analysis prompt. | | src/services/edgar/ | SEC EDGAR API client, XBRL concept mapping, HTML-to-text conversion. | | 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. | | src/config/ | Server-specific environment variable parsing and validation with Zod. | | tests/ | Unit and integration tests, mirroring the src/ structure. |

Development guide

See CLAUDE.md and AGENTS.md for development guidelines and architectural rules. The short version:

  • Handlers throw, framework catches — no try/catch in tool logic
  • Use ctx.log for logging, ctx.state for storage
  • Register new tools and resources in the createApp() arrays

Contributing

Issues and pull requests are welcome. Run checks and tests before submitting:

bun run devcheck
bun run test

License

This project is licensed under the Apache 2.0 License. See the LICENSE file for details.

See related servers & alternatives →

Related MCP servers

Browse all →

Related guides

Hand-picked reading to help you choose and use Other servers.