secedgar-mcp-server-216ddf — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited secedgar-mcp-server-216ddf (Plugin) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
<div align="center"> <h1>@cyanheads/secedgar-mcp-server</h1> <p><b>Query SEC EDGAR filings, XBRL financials, and company data via MCP. STDIO or Streamable HTTP.</b> <div>16 Tools (+1 opt-in) • 2 Resources • 1 Prompt</div> </p> </div>
<div align="center">
</div>
<div align="center">
</div>
<div align="center">
Public Hosted Server: https://secedgar.caseyjhand.com/mcp
</div>
SEC EDGAR filings, financials, and ownership data. Look up companies by ticker, name, or CIK, search filings back to 1993, compare financials across companies, and trace ownership through insider trades, 13F holdings, 13D/13G blockholders, and fund portfolios. No API key required.
| 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 from 2001, archive browse before that |
secedgar_get_filing | Fetch a filing's metadata and document text, paged or by section |
secedgar_get_financials | Get historical XBRL financial data for one company and concept |
secedgar_get_snapshot | One-call financial profile: the latest value of every supported concept |
secedgar_get_material_events | 8-K filings with item codes decoded and filterable |
secedgar_get_insider_transactions | Form 4 / 4-A insider transactions parsed from ownership XML |
secedgar_get_institutional_holdings | 13F-HR quarterly holdings of one institutional manager |
secedgar_find_holders | Reverse 13F lookup: which managers reported holding an issuer |
secedgar_get_beneficial_owners | 5%+ blockholders of an issuer 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 | One XBRL concept × one period across every reporting company |
secedgar_compare_companies | Compare named companies across several concepts, aligned on fiscal years or calendar quarters |
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 |
| Resource | Description |
|---|---|
secedgar://concepts | XBRL financial concepts grouped by statement, mapping friendly names to XBRL tags |
secedgar://filing-types | Common SEC filing types, plus the 8-K item-code tables for both numbering regimes |
secedgar_search_concepts serves the same concept catalog to tool-only clients.
| Prompt | Description |
|---|---|
secedgar_company_analysis | Structured analysis of a company's SEC filings: financial trends, risk factors, material events |
| Skill | Description |
|---|---|
secedgar-guide | Which tool answers each ownership question, where CIKs, accession numbers, CUSIPs, and fund series IDs come from, and the coverage limits to check before reporting data absent |
The skill ships in skills/secedgar-guide/SKILL.md. Plugin hosts load it from the package, and MCP clients that support the Skills extension read it from the server as skill://secedgar-guide/SKILL.md.
secedgar_company_search <sub>tool</sub>query takes a ticker (equities, ETFs, mutual funds), a current or former company name, or a CIK; include_filings (default on) adds up to filing_limit filings (default 10; a value up to 1,000 is served at the 50 inline cap), filtered by forms, filed_after, and filed_beforeseries_id / class_id for a fund ticker; fails as no_match (with near matches in data.suggestions) or multiple_matcheshistory_scanned_through reports how far it gotsecedgar_search_filings <sub>tool</sub>query (phrases, OR, -exclusion, wildcard*, ticker: / cik: scoping), or browse by forms and/or entity with no query; filed_after / filed_before work alone (a lone filed_after runs through today, a lone filed_before starts at 2001-01-01, or at 1993-01-01 before 2001); limit up to 100 (default 20; a value up to 1,000 is served at 100), offset up to 9,999; only sort: "relevance" on a 2001-onward search without ticker: / cik: scope pages EDGAR server-side, where offsets reach 9,900 (a page of 100 there ends at its 10,000-document cap) and a deeper one fails as offset_out_of_rangeticker: / cik: scope and reads up to 50 documents (scan reports the counts)source (efts, submissions, full-index); full-text rows list matched_documents (filenames for secedgar_get_filing); the response carries total, form_distribution, total_is_exact, and total_documents for full-text searchessecedgar_get_filing <sub>tool</sub>accession_number in dash or 18-digit form, optional cik to speed the lookup, document for an exhibit, include_xbrl for XBRL artifacts; content_limit 1,000–200,000 characters per page (default 50,000; up to 1,000,000 is served at 200,000)offset / next_offset until content_truncated is false, or jump with section (a heading such as item 1 or part i), then drop section to page on, since it outranks offset; the first page of a truncated document carries an outline of up to 50 headings with offsetsdocuments splits the filing into primary, exhibits, and auxiliary; binary entries (scans, PDFs, archives) fail as binary_document, and a section miss returns section_not_found with the outlinesection reaches its Items; documents still lists the raw XML, which document returns as filed. A holdings report whose raw file passes 600 KB reads as filed0001.htm) is read under its accession-prefixed name, and document takes either spelling; if neither is served, the full submission <accession>.txt is read instead and the notice says socik, the filer is found through EDGAR full-text search and the accession number's prefix, then, when none of those directories serves the filing, through the filers on the filing's index page — so a pre-2001 filing made through a filing agent resolves on its own, and a full-text search outage falls back instead of failing the callsecedgar_get_financials <sub>tool</sub>company (ticker or CIK) plus concept as a friendly name or raw XBRL tag; taxonomy us-gaap (default), ifrs-full, or dei; period_type annual, quarterly, or all; unit picks one SEC unit key (ZAR, USD, USD/EUR) when a concept is reported in several; limit 1–100 trims the inline seriesform, filed, accession_number, and tag, all in one unit (other units are named in caveats); tags_tried lists the tags checked, and an empty result fails as no_concept_data, no_frame_data, no_period_data, or no_unit_dataconcept that is neither a friendly name nor an UpperCamelCase tag fails as unknown_concept, with a formula for standard combinations (free_cash_flow, ebitda, working_capital) or up to three closest friendly namessecedgar_get_snapshot <sub>tool</sub>company, taxonomy us-gaap (default) or ifrs-full, and period_type annual, quarterly, or both (default); one companyfacts read covers every supported concept, and nothing is staged as a dataframelines entry reports the latest annual and quarterly value for a duration concept, or the latest instant value for balance-sheet and entity-info concepts, each with the tag it came from, under the line's taxonomy; concepts the filer doesn't report land in gaps with tags_triedsecedgar_get_material_events <sub>tool</sub>company plus up to 20 items codes, dotted (2.02) since 2004-08-23 and single integers (12) before; the two regimes don't overlap, so pair them across the changeover; filed_after / filed_before work alone and reach into the archive; limit 1–1,000, served at 100 (default 20)items to code, label, and regime (current / legacy); item_distribution counts every code in the window, and total_8k_scanned against total_matched shows what the filter removedhistory_scanned_through and dataset.truncated report what went unreadsecedgar_get_insider_transactions <sub>tool</sub>company is the issuer; transaction_type purchase (code P), sale (code S), or all (default); limit 1–1,000, served at 100 (default 20); does not cover Forms 3 or 5filed_after / filed_before (inclusive, either alone) read any period since mid-2003, up to 100 filings; history_scanned_through names the oldest filing parsedtransaction_code and transaction_type, is_derivative, unsigned shares_traded with direction (acquire / dispose), price per share, and shares owned after; dataset.truncated flags filings beyond those parsed, and filings_other_issuer counts Form 4s the company filed as a reporting owner of another issuer, which add no rows. Price text preserves the numeric digits in fixed-point notation, with at least two decimal places.secedgar_get_institutional_holdings <sub>tool</sub>company is the 13F filer (a CIK is most reliable), not a portfolio company, which is secedgar_find_holders' job; quarter as YYYY-QN, defaulting to the newest filing; limit 1–500 (default 20; up to 1,000 is served at 500) with offset / next_offset; consolidate (default true) merges sub-lines into positions sorted by valueno_filings_found naming the notice's accession number and periodacceptanceDateTime order: a RESTATEMENT replaces everything composed so far (earlier NEW HOLDINGS included); a NEW HOLDINGS amendment appends its rows, unless its table repeats every composed row (CUSIP, class, put/call, discretion, shares, share type), in which case it applies as a restatement. Values normalize by each accession's own filing date. Only tables from the last readable restatement on are readamendments (newest filed first) discloses each accession, filing date, and cover-page type (UNAVAILABLE with a reason when unreadable), plus applied_as: restatement, new_holdings, superseded, or not_applied (type or table unreadable, named in the notice); inspect any with secedgar_get_filing. composed_accessions lists the filings the rows come from, in application order. amendment_search_complete=false warns that bounded or unavailable archive discovery may have missed amendmentsmarket_value_usd in whole USD, shares or principal, put_call, and source_accession_numbers (a consolidated position lists each filing it came from; also a dataframe column); total_holdings_in_filing counts the original 13F-HR's raw rows, total_holdings_composed the composed raw rows, and total_positions distinct composed positions; accession_number and filing_date name the original 13F-HR; a shared legal name fails as ambiguous_entitysecedgar_find_holders <sub>tool</sub>issuer as a ticker, CIK, or name, plus an optional 9-character cusip, the precise match key (a name phrase-match both over- and under-matches); quarter as YYYY-QN, defaulting to the newest quarter past its 45-day filing deadline; limit 1–100 (default 20; up to 1,000 is served at 100) from up to 500 fetched filingsfiler_cik, accession_number, and form, while search_mode (cusip / name), total_filings, fetched, and holders_in_quarter size the result; the list is unranked, so pass a filer_cik to secedgar_get_institutional_holdings to read the positionsecedgar_search_filings continuation with the query, dates and document offset. Filter results.period_ending to the quarter end, then deduplicate across the sample's filer_cik and search results' cik, keeping the latest filing_date. Advance by 100 documents per full page up to offset 9,900, whose page ends at EDGAR's 10,000-document paging ceiling; lower-bound totals may hide more beyond itsecedgar_get_beneficial_owners <sub>tool</sub>issuer is the company being held; form_kind all (default), 13D, or 13G; include_amendments (default true); limit 1–20 matching parsed filings (default 10; up to 1,000 is served at 20). The newest-first scan examines at most 50 documents, excluding schedules naming another issuer; exclusions and failed documents do not consume limitreporting_persons with voting power, dispositive power, and percent_of_class per person, which joint filers report for the same shares, so they don't sum; a 13D carries purpose_of_transaction, a 13G has nonepurpose_of_transaction and each person's notes come back as filed, line breaks included, up to 5,000 and 2,000 characters; a longer value keeps its first characters and sets purpose_truncated or notes_truncated, and the notice points to the whole text in the dataframe's purpose_of_transaction and notes columns or through secedgar_get_filinglegacy_filings_before_coverage counts the issuer's older SC 13D / SC 13G text filingstotal_structured_filings partitions into filings_examined + filings_not_examined; examined documents partition into filings_parsed + filings_excluded_other_issuer + filings_failed.length. Failed accessions carry document_not_found (404) or unparseable (including missing issuer identity), and set partial. truncated means indexed schedules remain unexamined after the result limit or scan ceiling, independently of failuressecedgar_get_fund_holdings <sub>tool</sub>fund as a ticker (VOO), series ID (S000002839), or CIK; a multi-series trust fails as series_required with the series listed, so pass series_id; report_date picks a period from available_report_periods; limit 1–100 (default 20; up to 1,000 is served at 100) with offset / next_offsetpercent_of_net_assets, with name, CUSIP / ISIN / LEI, balance and units, value_usd, and asset and issuer category, alongside fund net assets, total assets, and total liabilities. Position currency is its denomination; valuations stay in USD. Percentage text preserves the numeric digits in fixed-point notation.report_period_date, roughly two months before filing_date; publication_lag_days states the gapsecedgar_fetch_frames <sub>tool</sub>concept as a friendly name or raw tag, period as CY2023, CY2024Q2, or CY2023Q4I, taxonomy us-gaap (default) or dei, unit (default USD), sort desc / asc; limit 1–100 (default 25; up to 1,000 is served at 100) with offset / next_offsetunqueried_tags and related_tags list variants to fetch separately; value_distribution.max_to_p95_ratio flags scale outliers, period_end_range shows fiscal-year mixing, and caveats flags fiscal-Q4 gapsconcept that is neither a friendly name nor an UpperCamelCase tag fails as unknown_concept; one whose tag has no frame for the period is no_data, which names the tag read and lists a friendly name's other tags in unqueried_tags (older periods often sit under an older tag: revenue before CY2014 is under Revenues or SalesRevenueNet)secedgar_compare_companies <sub>tool</sub>companies × 1–8 concepts (up to 100 of each are accepted: the first 10 companies and first 8 distinct concepts are compared, and the rest come back unread in deferred_companies / deferred_concepts, with a notice naming the re-calls that compare them); taxonomy us-gaap (default) or ifrs-full; period_type annual (default) or quarterly; periods 1–12 (default 4; up to 1,000 is served at 12), trimmed further when the inline matrix gets too largecells align each value on a calendar period and keep its frame, period_end, and source tag; a call with no comparable annual data fails no_comparable_datafailed_companies (reason not_found, ambiguous, or no_company_facts) and gaps report what's missing, and caveats flag differing period ends and unit mismatches between companies; a company named twice (a ticker and its CIK) is read once, under the first inputunknown_concepts with a hint; the call fails as unknown_concept only when every concept is unrecognizedsecedgar_search_concepts <sub>tool</sub>search is a substring over friendly name, label, and tags, so a raw tag like NetIncomeLoss reverse-maps to its friendly name; group and taxonomy filter; no arguments returns the full catalogtags, ifrs_tags (only where an IFRS element was confirmed in live 20-F filings), related_tags, unit, and groupsecedgar_dataframe_describe <sub>tool</sub>name for one dataframe; omit it to list the tenant's active dataframes, newest firstsource_tool, query_params, created_at / expires_at, row_count, truncated, and the column_schema that SQL for secedgar_dataframe_query has to matchsecedgar_dataframe_query <sub>tool</sub>sql (joins, aggregates, window functions, CTEs); row_limit 1–10,000 (default 1,000) and preview for fewer inline rows, each served at 10,000 up to 100,000, and register_as (df_XXXXX_XXXXX) to save the result as a new dataframecolumns, rows, row_count, and row_count_capped, which when true means row_count is the cap, not a total; BIGINT columns serialize as stringsnon_select_statement, denied_function, multi_statement, system_catalog_access, and others); an unqualified current_schema is refused as denied_function even when it names a column, so read such a column as <table>.current_schemasecedgar_dataframe_drop <sub>tool</sub>name of the dataframe to drop; idempotent, returning dropped: false when nothing matchedEDGAR_DATAFRAME_DROP_ENABLED=true; when disabled it is absent from tools/listsecedgar://concepts <sub>resource</sub>text/markdown, with the us-gaap, IFRS, and alternate-definition tags for each conceptsecedgar_get_financials, secedgar_compare_companies, and secedgar_fetch_frames accept as conceptssecedgar://filing-types <sub>resource</sub>text/markdownsecedgar_get_material_events' items filtersecedgar_company_analysis <sub>prompt</sub>company required; focus_areas optional free textsecedgar_get_snapshot with trends via secedgar_get_financials, filing review, material events, and a peer comparison via secedgar_compare_companies with secedgar_fetch_frames for a market-wide ranking) and a findings template; insider, institutional, or blockholder terms in focus_areas add those ownership steps, and "ownership" adds all threeBuilt on @cyanheads/mcp-ts-core: stdio and Streamable HTTP transports, pluggable auth (none / jwt / oauth), swappable storage (in-memory, filesystem, Supabase, Cloudflare KV/R2/D1), structured logging with optional OpenTelemetry tracing.
EDGAR-specific:
rate_limited with a retryAfter countdown for EDGAR_RATE_LIMIT_COOLDOWN_SECONDS, then one probe goes outsecedgar_get_financials, secedgar_get_snapshot, and secedgar_compare_companies share the same dedup and tag priority, so their numbers agree, and caveats flag gaps in the seriesEDGAR_MIRROR_ENABLED) that serves CIK resolution and financials from diskAgent-friendly output:
dataset field has staged its full result as a DuckDB dataframe (df_<id>), while the inline list stays capped at limit; inspect it with secedgar_dataframe_describe, then query it with secedgar_dataframe_querysource on filing-search rows, search_mode, 8-K item regime, typed failed_companies reasons, and gaps with tags_tried in place of zero-filled valueshistory_scanned_through, total_is_exact, publication_lag_days, and dataset.truncated tell agents how deep a scan went and what it left outcompany, filed_after / filed_before, and forms mean the same thing on every tool, and common alternate spellings (ticker, cik, start_date, end_date, form_types, and others) are accepted as aliases, as is filing_limit for limit on secedgar_search_filings, secedgar_get_material_events, and secedgar_get_beneficial_owners, where limit counts filingsConnect to the hosted instance, no install:
claude mcp add --transport http secedgar-mcp-server https://secedgar.caseyjhand.com/mcpOr run it locally:
claude mcp add secedgar-mcp-server -e EDGAR_USER_AGENT="YourAppName [email protected]" -- npx -y @cyanheads/secedgar-mcp-serverFor other clients, add this to the MCP config file:
{
"mcpServers": {
"secedgar-mcp-server": {
"command": "npx",
"args": ["-y", "@cyanheads/secedgar-mcp-server"],
"env": { "EDGAR_USER_AGENT": "YourAppName [email protected]" }
}
}
}SEC requires a User-Agent in "AppName [email protected]" form on every request; no account or key is needed. For Docker or a source build, see Running the server.
| Variable | Description | Default |
|---|---|---|
EDGAR_USER_AGENT | Required. User-Agent sent to SEC, as "AppName [email protected]". SEC blocks IPs without one. A value that can't be sent as an HTTP header (an em dash or curly quote, an inner line break) stops the server at startup with an error naming this variable. | — |
EDGAR_RATE_LIMIT_RPS | Max requests per second to SEC (1–10). | 10 |
EDGAR_RATE_LIMIT_COOLDOWN_SECONDS | Seconds to refuse calls locally after a 429 before one probe goes out. | 600 |
EDGAR_TICKER_CACHE_TTL | Seconds to cache the company and fund ticker files. | 3600 |
EDGAR_DATASET_TTL_SECONDS | Seconds a canvas dataframe lives, counted from when it is staged; queries do not extend it. | 86400 |
EDGAR_DATAFRAME_DROP_ENABLED | Set true to expose secedgar_dataframe_drop, the only destructive tool. | false |
EDGAR_CLAMP_OVERSIZED_LIMITS | Serve an over-cap limit, content_limit, periods, preview, or row_limit at its cap, and answer an over-cap companies or concepts list up to its cap with the rest returned unread, instead of rejecting it. Set false to reject above the cap. | true |
EDGAR_MIRROR_ENABLED | Enable the local SQLite mirror of company tickers and XBRL company-facts. Node/Bun only; bootstrap once with bun run mirror:init. | false |
EDGAR_MIRROR_PATH | Directory holding the mirror databases. | ./data/edgar-mirror |
EDGAR_MIRROR_REFRESH_CRON | In-process refresh cron (HTTP transport only), e.g. 0 9 * * *. Omit to refresh with bun run mirror:refresh. | — |
EDGAR_MIRROR_FALLBACK_LIVE | Fall back to the live SEC API on a mirror miss. Set false for mirror-only reads. | true |
CANVAS_PROVIDER_TYPE | Canvas engine; none disables dataframes. | 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 |
LOG_TOOL_FAILURE_PAYLOADS | Log each failed tool call's arguments and result, redacted by key name and capped at LOG_TOOL_FAILURE_PAYLOAD_MAX_BYTES (default 16384). A secret inside a free-form value is not redacted. | false |
OTEL_ENABLED | Enable OpenTelemetry instrumentation (spans, metrics, completion logs). | false |
OTEL_EXPORTER_OTLP_ENDPOINT | OTLP base URL for traces and metrics. | — |
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT | OTLP log export endpoint (e.g. http://localhost:4318/v1/logs). | — |
See .env.example for the full list of optional overrides.
Requires Bun v1.4.0 or higher (or Node.js v24+).
git clone https://github.com/cyanheads/secedgar-mcp-server.git
cd secedgar-mcp-server && bun install
cp .env.example .env # set EDGAR_USER_AGENT bun run rebuild
bun run start:http # or start:stdio; HTTP listens at http://localhost:3010/mcp bun run devcheck # Lint, format, typecheck, security
bun run test # Vitest test suite
bun run lint:mcp # Validate MCP definitions against specdocker run --rm -e EDGAR_USER_AGENT="YourAppName [email protected]" -p 3010:3010 ghcr.io/cyanheads/secedgar-mcp-server:latestThe image defaults to HTTP transport on port 3010, stateless session mode, and logs to /var/log/secedgar-mcp-server. To run it over stdio from an MCP client, use "command": "docker" with "args": ["run", "-i", "--rm", "-e", "MCP_TRANSPORT_TYPE=stdio", "-e", "EDGAR_USER_AGENT=YourAppName [email protected]", "ghcr.io/cyanheads/secedgar-mcp-server:latest"]. To build locally, docker build -t secedgar-mcp-server .; OpenTelemetry peer dependencies are installed by default — build with --build-arg OTEL_ENABLED=false to omit them.
The image ships the mirror CLI, so a running container can bootstrap and refresh its own mirror:
docker exec <container> bun run mirror:verify # sync status + sample reads
docker exec <container> bun run mirror:init # one-time bootstrap from the SEC bulk archive
docker exec <container> bun run mirror:refresh # re-ingest after SEC rebuilds the archive| Directory | Purpose |
|---|---|
src/index.ts | createApp() entry point: registers resources and prompts, starts the SEC client, canvas, and optional mirror. |
src/config | Server environment variable parsing and validation with Zod. |
src/mcp-server/tools/definitions | Tool definitions (*.tool.ts) and the buildToolDefinitions() registration list. |
src/mcp-server/resources/definitions | Resource definitions: XBRL concepts and filing types. |
src/mcp-server/prompts/definitions | Prompt definitions: company analysis. |
src/services/edgar | Paced SEC client, CIK resolution, XBRL concept mapping and series dedup, ownership / 13D / 13G / NPORT-P parsers, 8-K item tables, HTML-to-text. |
src/services/edgar/mirror | Opt-in local SQLite mirror of company tickers and XBRL company-facts. |
src/services/canvas-bridge | Adapter over the framework DataCanvas: df_<id> naming, per-table TTL, system-catalog SQL deny. |
scripts | Build, devcheck, and lint tooling, plus the mirror:* commands. |
tests | Unit and integration tests, mirroring the src/ structure. |
See CLAUDE.md and AGENTS.md for development guidelines and architectural rules. The short version:
try/catch in tool logicctx.log for logging, ctx.state for storagebuildToolDefinitions() (src/mcp-server/tools/definitions/index.ts), and resources and prompts in the createApp() arrays in src/index.tsgaps insteadIssues are welcome. Run checks and tests before submitting:
bun run devcheck
bun run testThis project is licensed under the Apache 2.0 License. See the LICENSE file for details.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.