@cyanheads/secedgar-mcp-server 0.15.6 → 0.15.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/AGENTS.md +35 -23
  2. package/CLAUDE.md +35 -23
  3. package/README.md +112 -159
  4. package/changelog/0.15.x/0.15.7.md +36 -0
  5. package/changelog/0.15.x/0.15.8.md +29 -0
  6. package/changelog/template.md +7 -7
  7. package/dist/config/server-config.d.ts +1 -0
  8. package/dist/config/server-config.d.ts.map +1 -1
  9. package/dist/config/server-config.js +8 -1
  10. package/dist/config/server-config.js.map +1 -1
  11. package/dist/index.js +5 -3
  12. package/dist/index.js.map +1 -1
  13. package/dist/mcp-server/resources/definitions/filing-types.resource.js +2 -2
  14. package/dist/mcp-server/resources/definitions/filing-types.resource.js.map +1 -1
  15. package/dist/mcp-server/tools/definitions/company-search.tool.d.ts +8 -1
  16. package/dist/mcp-server/tools/definitions/company-search.tool.d.ts.map +1 -1
  17. package/dist/mcp-server/tools/definitions/company-search.tool.js +39 -34
  18. package/dist/mcp-server/tools/definitions/company-search.tool.js.map +1 -1
  19. package/dist/mcp-server/tools/definitions/compare-companies.tool.d.ts +7 -0
  20. package/dist/mcp-server/tools/definitions/compare-companies.tool.d.ts.map +1 -1
  21. package/dist/mcp-server/tools/definitions/compare-companies.tool.js +8 -0
  22. package/dist/mcp-server/tools/definitions/compare-companies.tool.js.map +1 -1
  23. package/dist/mcp-server/tools/definitions/dataframe-query.tool.d.ts +31 -2
  24. package/dist/mcp-server/tools/definitions/dataframe-query.tool.d.ts.map +1 -1
  25. package/dist/mcp-server/tools/definitions/dataframe-query.tool.js +53 -5
  26. package/dist/mcp-server/tools/definitions/dataframe-query.tool.js.map +1 -1
  27. package/dist/mcp-server/tools/definitions/fetch-frames.tool.d.ts +7 -0
  28. package/dist/mcp-server/tools/definitions/fetch-frames.tool.d.ts.map +1 -1
  29. package/dist/mcp-server/tools/definitions/fetch-frames.tool.js +8 -0
  30. package/dist/mcp-server/tools/definitions/fetch-frames.tool.js.map +1 -1
  31. package/dist/mcp-server/tools/definitions/find-holders.tool.d.ts +7 -0
  32. package/dist/mcp-server/tools/definitions/find-holders.tool.d.ts.map +1 -1
  33. package/dist/mcp-server/tools/definitions/find-holders.tool.js +9 -1
  34. package/dist/mcp-server/tools/definitions/find-holders.tool.js.map +1 -1
  35. package/dist/mcp-server/tools/definitions/get-beneficial-owners.tool.d.ts +7 -0
  36. package/dist/mcp-server/tools/definitions/get-beneficial-owners.tool.d.ts.map +1 -1
  37. package/dist/mcp-server/tools/definitions/get-beneficial-owners.tool.js +9 -1
  38. package/dist/mcp-server/tools/definitions/get-beneficial-owners.tool.js.map +1 -1
  39. package/dist/mcp-server/tools/definitions/get-filing.tool.d.ts +7 -0
  40. package/dist/mcp-server/tools/definitions/get-filing.tool.d.ts.map +1 -1
  41. package/dist/mcp-server/tools/definitions/get-filing.tool.js +24 -11
  42. package/dist/mcp-server/tools/definitions/get-filing.tool.js.map +1 -1
  43. package/dist/mcp-server/tools/definitions/get-financials.tool.d.ts +7 -0
  44. package/dist/mcp-server/tools/definitions/get-financials.tool.d.ts.map +1 -1
  45. package/dist/mcp-server/tools/definitions/get-financials.tool.js +10 -0
  46. package/dist/mcp-server/tools/definitions/get-financials.tool.js.map +1 -1
  47. package/dist/mcp-server/tools/definitions/get-fund-holdings.tool.d.ts +8 -1
  48. package/dist/mcp-server/tools/definitions/get-fund-holdings.tool.d.ts.map +1 -1
  49. package/dist/mcp-server/tools/definitions/get-fund-holdings.tool.js +9 -1
  50. package/dist/mcp-server/tools/definitions/get-fund-holdings.tool.js.map +1 -1
  51. package/dist/mcp-server/tools/definitions/get-insider-transactions.tool.d.ts +14 -4
  52. package/dist/mcp-server/tools/definitions/get-insider-transactions.tool.d.ts.map +1 -1
  53. package/dist/mcp-server/tools/definitions/get-insider-transactions.tool.js +167 -34
  54. package/dist/mcp-server/tools/definitions/get-insider-transactions.tool.js.map +1 -1
  55. package/dist/mcp-server/tools/definitions/get-institutional-holdings.tool.d.ts +11 -4
  56. package/dist/mcp-server/tools/definitions/get-institutional-holdings.tool.d.ts.map +1 -1
  57. package/dist/mcp-server/tools/definitions/get-institutional-holdings.tool.js +113 -55
  58. package/dist/mcp-server/tools/definitions/get-institutional-holdings.tool.js.map +1 -1
  59. package/dist/mcp-server/tools/definitions/get-material-events.tool.d.ts +7 -0
  60. package/dist/mcp-server/tools/definitions/get-material-events.tool.d.ts.map +1 -1
  61. package/dist/mcp-server/tools/definitions/get-material-events.tool.js +47 -33
  62. package/dist/mcp-server/tools/definitions/get-material-events.tool.js.map +1 -1
  63. package/dist/mcp-server/tools/definitions/get-snapshot.tool.d.ts +7 -0
  64. package/dist/mcp-server/tools/definitions/get-snapshot.tool.d.ts.map +1 -1
  65. package/dist/mcp-server/tools/definitions/get-snapshot.tool.js +10 -0
  66. package/dist/mcp-server/tools/definitions/get-snapshot.tool.js.map +1 -1
  67. package/dist/mcp-server/tools/definitions/index.d.ts +141 -15
  68. package/dist/mcp-server/tools/definitions/index.d.ts.map +1 -1
  69. package/dist/mcp-server/tools/definitions/search-filings.tool.d.ts +14 -4
  70. package/dist/mcp-server/tools/definitions/search-filings.tool.d.ts.map +1 -1
  71. package/dist/mcp-server/tools/definitions/search-filings.tool.js +49 -38
  72. package/dist/mcp-server/tools/definitions/search-filings.tool.js.map +1 -1
  73. package/dist/services/canvas-bridge/canvas-bridge.d.ts +4 -1
  74. package/dist/services/canvas-bridge/canvas-bridge.d.ts.map +1 -1
  75. package/dist/services/canvas-bridge/canvas-bridge.js +4 -1
  76. package/dist/services/canvas-bridge/canvas-bridge.js.map +1 -1
  77. package/dist/services/edgar/edgar-api-service.d.ts +82 -51
  78. package/dist/services/edgar/edgar-api-service.d.ts.map +1 -1
  79. package/dist/services/edgar/edgar-api-service.js +332 -167
  80. package/dist/services/edgar/edgar-api-service.js.map +1 -1
  81. package/dist/services/edgar/filing-headers.d.ts +37 -9
  82. package/dist/services/edgar/filing-headers.d.ts.map +1 -1
  83. package/dist/services/edgar/filing-headers.js +40 -11
  84. package/dist/services/edgar/filing-headers.js.map +1 -1
  85. package/dist/services/edgar/filing-to-text.d.ts.map +1 -1
  86. package/dist/services/edgar/filing-to-text.js +24 -2
  87. package/dist/services/edgar/filing-to-text.js.map +1 -1
  88. package/dist/services/edgar/mirror/tickers-sync.d.ts +4 -2
  89. package/dist/services/edgar/mirror/tickers-sync.d.ts.map +1 -1
  90. package/dist/services/edgar/mirror/tickers-sync.js +4 -2
  91. package/dist/services/edgar/mirror/tickers-sync.js.map +1 -1
  92. package/dist/services/edgar/mirror/types.d.ts +6 -2
  93. package/dist/services/edgar/mirror/types.d.ts.map +1 -1
  94. package/dist/services/edgar/ownership-parser.d.ts +16 -0
  95. package/dist/services/edgar/ownership-parser.d.ts.map +1 -1
  96. package/dist/services/edgar/ownership-parser.js +19 -0
  97. package/dist/services/edgar/ownership-parser.js.map +1 -1
  98. package/dist/services/edgar/submissions-archive.d.ts +78 -0
  99. package/dist/services/edgar/submissions-archive.d.ts.map +1 -0
  100. package/dist/services/edgar/submissions-archive.js +87 -0
  101. package/dist/services/edgar/submissions-archive.js.map +1 -0
  102. package/package.json +7 -7
  103. package/server.json +17 -3
package/README.md CHANGED
@@ -13,7 +13,7 @@
13
13
 
14
14
  <div align="center">
15
15
 
16
- [![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)
16
+ [![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%2Fsecedgar-mcp-server%22%5D%2C%22env%22%3A%7B%22EDGAR_USER_AGENT%22%3A%22YourName%20your-email%40example.com%22%7D%7D)
17
17
 
18
18
  [![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)
19
19
 
@@ -29,244 +29,195 @@
29
29
 
30
30
  ## Overview
31
31
 
32
- SEC EDGAR filings, XBRL financials, and company ownership data, keyless aside from a required SEC User-Agent header. Resolve companies by ticker, name, or CIK, search filings back to 1993, pull XBRL financials and cross-company comparisons by concept, and trace ownership — insider transactions, 13F institutional holdings, 13D/13G blockholders, and fund holdings — from any MCP client. Runs as a stdio process, a local Streamable HTTP server, or the public hosted endpoint above.
32
+ SEC EDGAR filings, XBRL financials, and ownership data. No API key needed, only the User-Agent header SEC requires. Resolve companies by ticker, name, or CIK, search filings back to 1993, pull XBRL financials and cross-company comparisons by concept, and trace ownership through insider transactions, 13F holdings, 13D/13G blockholders, and fund portfolios. Runs as a stdio process, a local Streamable HTTP server, or the public hosted endpoint above.
33
33
 
34
34
  ### Tools
35
35
 
36
36
  | Tool | Description |
37
37
  |:---|:---|
38
38
  | `secedgar_company_search` | Find companies and retrieve entity info with optional recent filings |
39
- | `secedgar_search_filings` | Search EDGAR filings since 1993 — full-text (2001+) plus archive-backed browse for pre-2001 ranges |
40
- | `secedgar_get_filing` | Fetch a specific filing's metadata and document content |
41
- | `secedgar_get_financials` | Get historical XBRL financial data for a company |
42
- | `secedgar_get_snapshot` | One-call financial profile — the latest value of every supported concept, grouped by statement |
43
- | `secedgar_get_material_events` | 8-K filings with item codes decoded and filterable — earnings, officer changes, non-reliance |
44
- | `secedgar_get_insider_transactions` | Form 4 / 4-A insider transactions (buys, sells, grants, exercises) parsed from ownership XML |
45
- | `secedgar_get_institutional_holdings` | 13F-HR quarterly institutional holdings parsed from the information table |
46
- | `secedgar_find_holders` | Reverse 13F lookup — which institutional managers reported holding an issuer |
47
- | `secedgar_get_beneficial_owners` | 5%+ blockholders of an issuer, parsed from structured SCHEDULE 13D / 13G filings |
39
+ | `secedgar_search_filings` | Search EDGAR filings since 1993: full text from 2001, archive browse before that |
40
+ | `secedgar_get_filing` | Fetch a filing's metadata and document text, paged or by section |
41
+ | `secedgar_get_financials` | Get historical XBRL financial data for one company and concept |
42
+ | `secedgar_get_snapshot` | One-call financial profile: the latest value of every supported concept |
43
+ | `secedgar_get_material_events` | 8-K filings with item codes decoded and filterable |
44
+ | `secedgar_get_insider_transactions` | Form 4 / 4-A insider transactions parsed from ownership XML |
45
+ | `secedgar_get_institutional_holdings` | 13F-HR quarterly holdings of one institutional manager |
46
+ | `secedgar_find_holders` | Reverse 13F lookup: which managers reported holding an issuer |
47
+ | `secedgar_get_beneficial_owners` | 5%+ blockholders of an issuer from structured SCHEDULE 13D / 13G filings |
48
48
  | `secedgar_get_fund_holdings` | ETF and mutual fund portfolio holdings from the quarterly NPORT-P report |
49
- | `secedgar_fetch_frames` | Fetch SEC XBRL frames for one concept × one period across all reporting companies |
49
+ | `secedgar_fetch_frames` | One XBRL concept × one period across every reporting company |
50
50
  | `secedgar_compare_companies` | Compare named companies across several concepts, aligned on calendar periods |
51
51
  | `secedgar_search_concepts` | Discover supported XBRL concept names or reverse-lookup a raw tag |
52
52
  | `secedgar_dataframe_describe` | List canvas dataframes with provenance, TTL, and schema |
53
53
  | `secedgar_dataframe_query` | Run a single-statement SELECT across dataframes |
54
- | `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, and uncallable until the flag is set |
54
+ | `secedgar_dataframe_drop` | Drop a canvas dataframe by name; opt-in via `EDGAR_DATAFRAME_DROP_ENABLED=true` |
55
55
 
56
56
  ### Resources
57
57
 
58
58
  | Resource | Description |
59
59
  |:---|:---|
60
- | `secedgar://concepts` | Common XBRL financial concepts grouped by statement, mapping friendly names to XBRL tags |
61
- | `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 |
60
+ | `secedgar://concepts` | XBRL financial concepts grouped by statement, mapping friendly names to XBRL tags |
61
+ | `secedgar://filing-types` | Common SEC filing types, plus the 8-K item-code tables for both numbering regimes |
62
+
63
+ `secedgar_search_concepts` serves the same concept catalog to tool-only clients.
62
64
 
63
65
  ### Prompts
64
66
 
65
67
  | Prompt | Description |
66
68
  |:---|:---|
67
- | `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 |
69
+ | `secedgar_company_analysis` | Structured analysis of a company's SEC filings: financial trends, risk factors, material events |
68
70
 
69
71
  ## Capability reference
70
72
 
71
73
  ### `secedgar_company_search` <sub>tool</sub>
72
74
 
73
- - Resolves ticker symbols, company names, or CIK numbers; a multi-class share ticker matches either form (`BRK-B` or `BRK.B`), and current/former names both resolve (`Facebook` → Meta Platforms, `Square` → Block)
74
- - ETFs and mutual funds resolve by ticker via `company_tickers_mf.json`; fund results carry `series_id` and `class_id`
75
- - Corporate suffix form need not match the registry (`Beacon Financial Corporation` → `Beacon Financial Corp`), but `Corp`/`Inc`/`Co`/`Ltd` stay distinct — separate registrants can differ only by which one they use
76
- - Near-match suggestions on a zero-result name or ticker search (e.g. `Microsfot` → `MICROSOFT CORP / MSFT`)
77
- - Optional recent filings inline with form-type filtering; `filed_after`/`filed_before` and under-filled form filters page into the older submissions archive, reaching filings past the ~1000-entry recent window — `history_scanned_through` reports the scan depth, and the full filtered history stages as `df_<id>` when it exceeds `filing_limit`
78
- - Returns entity metadata — SIC code, exchanges, fiscal year end, state of incorporation
75
+ - `query` takes a ticker (equities, ETFs, and mutual funds; `BRK-B` or `BRK.B`), a current or former company name, or a CIK; `include_filings` (default on) adds up to `filing_limit` filings (1–50, default 10), filtered by exact `forms` match and `filed_after` / `filed_before`
76
+ - Returns SIC code, exchanges, fiscal year end, and state of incorporation, plus `series_id` / `class_id` for a fund ticker; fails as `no_match` (near matches in `data.suggestions`) or `multiple_matches`
77
+ - A date filter or an under-filled form filter scans past the recent window (the last year or 1,000 filings, whichever holds more) into the archive, up to 10 archive pages, and `history_scanned_through` reports how far it reached
79
78
 
80
79
  ---
81
80
 
82
81
  ### `secedgar_search_filings` <sub>tool</sub>
83
82
 
84
- - Full-text search (2001–present, the EFTS index floor) with exact phrases, boolean operators, wildcards, and inline entity targeting (`cik:320193` / `ticker:AAPL`, either share-class form for multi-class tickers) — server-side scoped by CIK, so former-name filings on the same entity are included
85
- - Browse mode (omit `query`) lists by form type and/or entity, optionally narrowed by date; a bare date range must pair with forms or entity targeting
86
- - Pre-2001 date ranges (back to 1993) route to the archives — entity-scoped reads the filer's full submissions history, unscoped browses the quarterly full-index; pre-2001 free-text search needs `ticker:`/`cik:` entity scope, since it works by reading up to 50 candidate documents (`scan` reports candidates/scanned/matched, costing ~5s for a full scan)
87
- - A range crossing 2001-01-01 is split at the boundary and merged, each row tagged with its `source` (`efts`/`submissions`/`full-index`); `period_ending`, `ticker`, `file_description`, `sic`, and `location` exist only on `efts` rows
88
- - Date range and form-type filtering, pagination up to 10,000 results; response includes form distribution for narrowing follow-up searches
89
- - The full result set stages as `df_<id>` when it exceeds the inline limit
83
+ - Full-text `query` (phrases, `OR`, `-exclusion`, `wildcard*`, `ticker:` / `cik:` scoping), or browse by `forms` and/or entity with no query; `filed_after` and `filed_before` must be given together; `limit` up to 100, and `offset` (up to 9,999) pages server-side only under `sort: "relevance"` on a 2001-onward search
84
+ - Full text covers 2001 onward. Earlier ranges, back to 1993, come from the archives, and pre-2001 free text needs `ticker:` / `cik:` scope and reads up to 50 documents (`scan` reports candidates, scanned, and matched)
85
+ - A range crossing 2001-01-01 is split and merged, each row tagged with `source` (`efts`, `submissions`, `full-index`); the response carries `total`, `total_is_exact`, and `form_distribution`
90
86
 
91
87
  ---
92
88
 
93
89
  ### `secedgar_get_filing` <sub>tool</sub>
94
90
 
95
- - Accepts accession numbers in dash or no-dash format; fetches the primary document or a specific exhibit by name
96
- - Converts HTML filings to plain text; pre-2005 filings produce noisier output
97
- - Configurable `content_limit` (1K–200K characters, default 50K)
98
- - Binary entries (scanned pages, PDF exhibits, packaged archives/spreadsheets) are marked `binary` in the document catalog and rejected with a `binary_document` error rather than returned as decoded bytes
99
- - Offset paging for large documents (10-K, S-1/A can exceed 1M chars) — pass a truncated response's `next_offset` as `offset` to continue; first-page truncated responses include a detected `outline` (headings + offsets)
100
- - `section` jumps directly to a named heading by substring match ignoring case, whitespace style, and quote style (`"risk factors"`, `"item 7"`) — a miss returns the detected outline; extracted text is cached per `accession + document` (bounded LRU, 8 entries) so paged calls are cheap
91
+ - `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)
92
+ - Page with `offset` / `next_offset` until `content_truncated` is false, or jump with `section` (substring match on detected headings); the first page of a truncated document carries an `outline` of up to 50 headings with offsets
93
+ - `documents` splits the filing into primary, exhibits, and auxiliary; entries marked `binary` (scans, PDFs, archives) fail as `binary_document`, and a `section` miss returns `section_not_found` with the outline
94
+ - `form`, `filing_date`, and `period_ending` come from the company's submissions feed for a recent filing and from the filing's own SEC header for an older one
101
95
 
102
96
  ---
103
97
 
104
98
  ### `secedgar_get_financials` <sub>tool</sub>
105
99
 
106
- - Friendly names (`"revenue"`, `"net_income"`, `"eps_diluted"`) auto-resolve to XBRL tags, including historical tag changes (e.g. ASC 606 revenue recognition) — see `secedgar://concepts` for the full mapping
107
- - Automatic deduplication to one value per standard calendar period; filter by `period_type` (`annual`/`quarterly`/`all`)
108
- - Optional `limit` caps the inline series to the most-recent N periods; the full series stays queryable via `df_<id>`
109
- - `caveats` names every calendar quarter absent from the frame-tagged series — SEC reports fiscal Q4 as the 10-K residual, so the calendar quarter it spans carries no discrete quarterly value (calendar-year filers included), and a filer whose other fiscal quarters span non-calendar durations can lose a second quarter the same way
110
- - A separate `caveats` entry appears 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 then stop years short
100
+ - `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`, defaulting to annual with a fallback to the full series for instant concepts; `limit` 1–100 trims the inline series
101
+ - A deduplicated series, newest first, one value per calendar period with its source `form`, `filed` date, and `accession_number`; `tags_tried` names the tags walked, and an empty result fails as `no_concept_data`, `no_frame_data`, or `no_period_data`
111
102
 
112
103
  ---
113
104
 
114
105
  ### `secedgar_get_snapshot` <sub>tool</sub>
115
106
 
116
- - Reads the filer's complete companyfacts payload once and resolves every supported concept against it — one call instead of a run of `secedgar_get_financials` calls
117
- - Same frame dedup and tag priority as `secedgar_get_financials`, so the two agree for any concept they both cover
118
- - 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
119
- - Concepts the filer does not report are listed under `gaps` with the XBRL tags that were tried — never zero-filled or interpolated
120
- - IFRS filers resolve through the mapped IFRS tag variants via `taxonomy: "ifrs-full"`, covering the income statement, balance sheet, cash flow, and per-share concepts; each line reports the taxonomy its value came from
121
- - Compact single-record profile, no dataframe — reach for `secedgar_get_financials` when a time series is needed
107
+ - `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 dataframe
108
+ - Each `lines` entry reports the latest `annual` and `quarterly` value for a duration concept, or the latest `instant` value for balance-sheet and entity-info concepts, with the `tag` and `taxonomy` it came from; concepts the filer doesn't report land in `gaps` with `tags_tried`
122
109
 
123
110
  ---
124
111
 
125
112
  ### `secedgar_get_material_events` <sub>tool</sub>
126
113
 
127
- - Filter with `items` (e.g. `["2.02"]` results of operations, `["5.02"]` officer departures, `["4.02"]` non-reliance) — the only surface that scopes by what the event actually was, since `secedgar_search_filings` and `secedgar_company_search` cannot see item codes
128
- - Two numbering regimes are both accepted and decoded: the dotted scheme in force since 2004-08-23, and 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
129
- - `item_distribution` counts every code across the scanned window before the filter, so a zero-hit filter still surfaces the items that are present
130
- - A date window pages into the older submissions archive, reaching 8-K filings older than the ~1000-filing recent window; `history_scanned_through` discloses the scan depth
131
- - The full decode table is in the `secedgar://filing-types` resource
132
- - The full filtered set materializes as `df_<id>` with item codes on every row — item frequency over time is one `secedgar_dataframe_query` away
114
+ - `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–100 (default 20)
115
+ - Each filing decodes its `items` to `code`, `label`, and `regime` (`current` / `legacy`); `item_distribution` counts every code in the window before the filter, and `total_8k_scanned` against `total_matched` shows what the filter removed
116
+ - A date window reads every archive page overlapping it, up to 10; without one, the archive is read only to fill `limit`, stopping on the page that fills it; `history_scanned_through` and `dataset.truncated` report what went unread
133
117
 
134
118
  ---
135
119
 
136
120
  ### `secedgar_get_insider_transactions` <sub>tool</sub>
137
121
 
138
- - Parses Form 4 / 4-A insider transactions from 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`
139
- - Reporting person, relationship to issuer (director, officer + title, 10% owner), and transaction date
140
- - 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
141
- - Filter by `transaction_type` (`purchase`, `sale`, `all`); scans newest filings first
142
- - The full set parsed from the scanned recent filings materializes as `df_<id>` (the inline list is a preview capped at `limit`) — query it to aggregate net buy/sell by insider
122
+ - `company` is the issuer; `transaction_type` `purchase` (code P), `sale` (code S), or `all` (default); `limit` 1–100 (default 20); does not cover Forms 3 or 5
123
+ - Without a date window it scans up to 100 of the newest Form 4 / 4-A filings; `filed_after` / `filed_before` (inclusive, either alone) read any period since mid-2003, paging into the archive (up to 10 pages) when the window predates the recent submissions window, and with a canvas every in-window filing is parsed, up to 100; `history_scanned_through` names the oldest filing parsed
124
+ - Each transaction carries the reporting person, relationship, `transaction_code` and `transaction_type`, `is_derivative`, unsigned `shares_traded` with `direction` (`acquire` / `dispose`), price per share, and shares owned after; `dataset.truncated` flags Form 4 filings beyond those parsed
143
125
 
144
126
  ---
145
127
 
146
128
  ### `secedgar_get_institutional_holdings` <sub>tool</sub>
147
129
 
148
- - 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
149
- - Each holding: issuer name, CUSIP, market value (whole USD), shares/principal, and put/call; raw rows also carry investment discretion
150
- - Sub-lines for the same security are consolidated into distinct positions sorted by value by default — pass `consolidate: false` for raw filing rows
151
- - Resolves the filing-manager name and reporting quarter from the cover page; target a specific quarter with `quarter` (e.g. `"2025-Q4"`)
152
- - `total_holdings_in_filing` counts raw info-table rows, `total_positions` counts distinct positions after consolidation (both before `limit`); page with `offset`, which returns `next_offset` while rows remain
153
- - The full parsed holdings set materializes as `df_<id>` for full-filing aggregation or cross-quarter joins on `cusip` + `reporting_period`
130
+ - `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) with `offset` / `next_offset`; `consolidate` (default true) merges sub-lines into positions sorted by value
131
+ - A `quarter` older than the recent submissions window is found in the archive, read forward from the quarter end (up to 10 pages); a quarter the manager covered with a 13F-NT notice — or, with no `quarter`, a manager whose recent filings are notices only — fails as `no_filings_found` naming the notice's accession number and period
132
+ - Holdings carry issuer, CUSIP, `market_value_usd` in whole USD, shares or principal, and `put_call`; `total_holdings_in_filing` counts raw rows and `total_positions` distinct positions; a shared legal name fails as `ambiguous_entity`
154
133
 
155
134
  ---
156
135
 
157
136
  ### `secedgar_find_holders` <sub>tool</sub>
158
137
 
159
- - 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); the name path both under-matches (managers write names differently) and over-matches (unrelated issuers sharing a word)
160
- - 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
161
- - `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
162
- - 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) don't land in the wrong quarter's holder list
163
- - Up to 500 filer rows are fetched per call; `total_filings` reports the full count and `dataset.truncated` flags when more exist
164
- - **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`
138
+ - `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) from up to 500 fetched filings
139
+ - Rows carry `filer_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 position
165
140
 
166
141
  ---
167
142
 
168
143
  ### `secedgar_get_beneficial_owners` <sub>tool</sub>
169
144
 
170
- - 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
171
- - 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 — filter with `form_kind`
172
- - 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, so summing those percentages double-counts the position
173
- - Coverage starts **2024-12-18**, when SEC replaced the legacy `SC 13D` / `SC 13G` text filings with structured XML; earlier stakes are readable but not parseable, and `legacy_filings_before_coverage` reports how many the issuer has
174
- - Amendments carry the current position and are included by default; `include_amendments=false` leaves only the filings that opened a position
175
- - The full parsed set registers as `df_<id>`, one row per reporting person, so it joins the insider and 13F dataframes on issuer CIK
145
+ - `issuer` is the company being held; `form_kind` `all` (default), `13D`, or `13G`; `include_amendments` (default true); `limit` 1–20 filings (default 10), each a separate document fetch
146
+ - Each filing lists `reporting_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 none
147
+ - Coverage starts 2024-12-18 with the structured XML schedules; `legacy_filings_before_coverage` counts the issuer's older `SC 13D` / `SC 13G` text filings
176
148
 
177
149
  ---
178
150
 
179
151
  ### `secedgar_get_fund_holdings` <sub>tool</sub>
180
152
 
181
- - 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`), a fund series ID (`S000002839`), or a CIK — name the registrant by CIK unless the fund itself trades under that name
182
- - 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 resolving to more than one series returns the series list with tickers, and one whose series carry no ticker is routed by reading the series off its newest report
183
- - Every result is dated to `report_period_date` — reports publish roughly two months after the period they cover, so holdings are the portfolio as of that date, not as of today; `publication_lag_days` states the gap, and `report_date` targets an earlier period from `available_report_periods`
184
- - 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
185
- - Positions come back largest-first by percent of net assets, one page of `limit` rows from `offset`; the full report registers as `df_<id>` for aggregation and for joining the 13F and insider dataframes on CUSIP
153
+ - `fund` as a ticker (`VOO`), series ID (`S000002839`), or CIK; `series_id` picks one fund of a multi-series trust, which otherwise fails as `series_required` with the series listed; `report_date` targets a period from `available_report_periods`; `limit` 1–100 (default 20) with `offset` / `next_offset`
154
+ - Positions come largest first by `percent_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
155
+ - Holdings are as of `report_period_date`, roughly two months before `filing_date`; `publication_lag_days` states the gap
186
156
 
187
157
  ---
188
158
 
189
159
  ### `secedgar_fetch_frames` <sub>tool</sub>
190
160
 
191
- - Same friendly concept names as `secedgar_get_financials`, or a raw XBRL tag
192
- - Supports annual (`CY2023`), quarterly (`CY2024Q2`), and instant (`CY2023Q4I`) periods
193
- - Inline response returns one page of the ranked companies (sort + limit), with ticker enrichment; walk further down the ranking with `offset`, which returns `next_offset` while companies remain
194
- - The full frames response (all reporters, typically 2k–10k rows) materializes as `df_<id>`
195
- - `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
196
- - One call hits one XBRL tag; when a friendly name maps to multiple same-meaning tags, `unqueried_tags` lists the others to query and combine with an analysis-specific priority
161
+ - `concept` as a friendly name or raw tag, `period` as `CY2023`, `CY2024Q2`, or `CY2023Q4I`, `unit` (default `USD`), `sort` `desc` / `asc`; `limit` 1–100 (default 25) with `offset` / `next_offset` down the ranking
162
+ - One call queries one tag: `unqueried_tags` lists same-meaning variants to fetch separately, and `related_tags` lists alternate-definition tags some filers report instead
163
+ - `value_distribution.max_to_p95_ratio` flags scale-factor outliers, `period_end_range` shows fiscal-year mixing, and `caveats` names the fiscal-Q4 gap in quarterly frames
197
164
 
198
165
  ---
199
166
 
200
167
  ### `secedgar_compare_companies` <sub>tool</sub>
201
168
 
202
- - Compares 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)
203
- - One companyfacts read per company, resolved through the same frame dedup and tag priority as `secedgar_get_financials`
204
- - 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
205
- - `periods` bounds the inline matrix (1-12, default 4), shrinking further when companies × concepts × periods is too large for one response; the full aligned series always materializes as `df_<id>`
206
- - A company that fails to resolve is reported in `failed_companies` 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
207
- - `caveats` surface a filer missing calendar quarters, a concept that resolved to a retired tag for one company, differing period ends inside one aligned period, and unit mismatches across companies
169
+ - 2–10 `companies` × 1–8 `concepts`; `taxonomy` `us-gaap` (default) or `ifrs-full`; `period_type` `annual` (default) or `quarterly`; `periods` 1–12 (default 4), trimmed further when the inline matrix gets too large
170
+ - `cells` align each value on a calendar `period` and keep its `frame` and `period_end`; `failed_companies` (reason `not_found`, `ambiguous`, or `no_company_facts`) and `gaps` report what's missing, and `caveats` flag differing period ends and unit mismatches
208
171
 
209
172
  ---
210
173
 
211
174
  ### `secedgar_search_concepts` <sub>tool</sub>
212
175
 
213
- - Search by friendly name, label, or raw XBRL tag; an empty search with no filters returns the full catalog
214
- - Filter by statement group (`income_statement`, `balance_sheet`, `cash_flow`, `per_share`, `entity_info`) or taxonomy
215
- - Reverse-lookup raw tags like `NetIncomeLoss` to the supported friendly names
216
- - 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
217
- - Filtering by `taxonomy: "ifrs-full"` narrows the catalog to concepts with an IFRS tag confirmed against live 20-F filers — a concept with no IFRS equivalent is left out rather than mapped to a guess
218
- - Returns the same catalog used by `secedgar_get_financials`, `secedgar_fetch_frames`, and `secedgar://concepts`
176
+ - `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 catalog
177
+ - Each concept lists `tags`, `ifrs_tags` (only where an IFRS element was confirmed in live 20-F filings), `related_tags`, `unit`, and `group`
219
178
 
220
179
  ---
221
180
 
222
181
  ### `secedgar_dataframe_describe` <sub>tool</sub>
223
182
 
224
- - Lists every dataframe (`df_XXXXX_XXXXX`) registered by the data-returning `secedgar_*` tools — any response carrying a `dataset` field holds one
225
- - Optional `name` describes a single dataframe; omit to list every dataframe for the tenant
226
- - Each entry surfaces source tool, query parameters, creation/expiry timestamps, row count, column schema, and whether the dataframe is truncated relative to the upstream source
227
- - Read the column schema here before writing SQL for `secedgar_dataframe_query`
183
+ - Optional `name` for one dataframe; omit it to list the tenant's active dataframes, newest first
184
+ - Each entry carries `source_tool`, `query_params`, `created_at` / `expires_at`, `row_count`, `truncated`, and the `column_schema` that SQL for `secedgar_dataframe_query` has to match
228
185
 
229
186
  ---
230
187
 
231
188
  ### `secedgar_dataframe_query` <sub>tool</sub>
232
189
 
233
- - Runs a single-statement SELECT (standard DuckDB SQL — joins, aggregates, window functions, CTEs) against the dataframes registered by the data-returning `secedgar_*` tools
234
- - Read-only: 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
235
- - `row_limit` caps rows materialized in the response (default 1000, max 10000); a capped result reports `row_count_capped: true` with `row_count` as that cap rather than a total — a SQL `LIMIT` exactly equal to the cap is indistinguishable from an exact result and reported as such
236
- - `register_as` persists the result as a new dataframe (`df_XXXXX_XXXXX`) with a fresh TTL, to chain analyses without re-running the source query
237
- - BIGINT columns (XBRL `value`, COUNT/SUM results) serialize as JSON strings to preserve precision past 2^53 — cast to `DOUBLE` in projections for inline arithmetic
190
+ - One DuckDB SELECT in `sql` (joins, aggregates, window functions, CTEs); `row_limit` 1–10,000 (default 1,000), `preview` for fewer inline rows, and `register_as` (`df_XXXXX_XXXXX`) to save the result as a new dataframe
191
+ - Returns `columns`, `rows`, `row_count`, and `row_count_capped`, which when true means `row_count` is the cap, not a total; BIGINT columns serialize as strings
192
+ - Writes, DDL, file-reading functions, multiple statements, and system catalogs are rejected with typed reasons (`non_select_statement`, `denied_function`, `multi_statement`, `system_catalog_access`, and others)
238
193
 
239
194
  ---
240
195
 
241
196
  ### `secedgar_dataframe_drop` <sub>tool</sub>
242
197
 
243
- - Drops a canvas dataframe by name; idempotent — returns `dropped: false` when nothing matched
244
- - Opt-in via `EDGAR_DATAFRAME_DROP_ENABLED=true` — off by default since the per-table TTL already reclaims canvas tables, and this is the only destructive tool on the server
245
- - Off, the tool is registered through `disabledTool()`: absent from `tools/list` and uncallable, but shown on the HTTP landing page in a `disabled` group naming the reason and the flag that enables it
198
+ - `name` of the dataframe to drop; idempotent, returning `dropped: false` when nothing matched
199
+ - Off unless `EDGAR_DATAFRAME_DROP_ENABLED=true`; disabled, it is absent from `tools/list` and uncallable, but still listed on the HTTP landing page with the flag that enables it
246
200
 
247
201
  ---
248
202
 
249
203
  ### `secedgar://concepts` <sub>resource</sub>
250
204
 
251
- - XBRL financial concepts grouped by statement (Income Statement, Balance Sheet, Cash Flow, Per Share, Entity Info), returned as `text/markdown`
252
- - Maps the friendly names accepted by `secedgar_get_financials` and `secedgar_fetch_frames` to their underlying XBRL tags
205
+ - The friendly-name catalog grouped by statement, as `text/markdown`, with the us-gaap, IFRS, and alternate-definition tags for each concept
206
+ - The names are what `secedgar_get_financials`, `secedgar_compare_companies`, and `secedgar_fetch_frames` accept as concepts
253
207
 
254
208
  ---
255
209
 
256
210
  ### `secedgar://filing-types` <sub>resource</sub>
257
211
 
258
- - Common SEC filing types with descriptions, cadence, and typical use cases, returned as `text/markdown`
259
- - Includes the full 8-K item-code decode tables for both numbering regimes (the current dotted scheme and the pre-2004-08-23 legacy integers)
260
- - Helps choose the `forms` parameter for `secedgar_search_filings`, the `form_types` filter for `secedgar_company_search`, or the `items` filter for `secedgar_get_material_events`
212
+ - Common SEC forms with cadence and use cases, as `text/markdown`
213
+ - Includes the 8-K item-code tables for both numbering regimes, the vocabulary of `secedgar_get_material_events`' `items` filter
261
214
 
262
215
  ---
263
216
 
264
217
  ### `secedgar_company_analysis` <sub>prompt</sub>
265
218
 
266
- - Arguments: `company` (name, ticker, or CIK) required; `focus_areas` free-text optional (e.g. `"revenue growth, debt levels, insider activity"`) — omitted, it performs a general analysis
267
- - Baseline workflow: company identification, financial trends, recent-filing review, and material events, each routed through the corresponding `secedgar_*` tool, closing with a peer comparison via `secedgar_fetch_frames`
268
- - `focus_areas` mentioning insider, institutional, or blockholder/activist terms adds the matching ownership step — insider transactions (Form 4/4-A), institutional holdings (`secedgar_find_holders` then `secedgar_get_institutional_holdings`), or 5%+ blockholders (13D/13G) — the generic word "ownership" adds all three
269
- - Returns a single user-role message carrying the numbered workflow and a findings template to fill in
219
+ - Arguments: `company` required; `focus_areas` optional free text
220
+ - Returns one user message with a numbered workflow (company search, financial trends, filing review, material events, peer comparison via `secedgar_fetch_frames`) and a findings template; insider, institutional, or blockholder terms in `focus_areas` add those ownership steps, and "ownership" adds all three
270
221
 
271
222
  ## Features
272
223
 
@@ -274,18 +225,18 @@ Built on [`@cyanheads/mcp-ts-core`](https://github.com/cyanheads/mcp-ts-core): s
274
225
 
275
226
  EDGAR-specific:
276
227
 
277
- - Rate-limited HTTP client honoring SEC's 10 req/s limit with automatic inter-request delay; a 429 fails fast with a cool-down hint instead of retrying, since SEC's own block window outlasts any retry budget
278
- - CIK resolution from tickers (including ETFs and mutual funds via `company_tickers_mf.json`), current and former company names, or raw CIK numbers, with local caching, corporate-suffix normalization, and near-match trigram suggestions on zero-result queries
279
- - Friendly XBRL concept name mapping with historical tag-change handling and a searchable, reverse-lookupable concept catalog
280
- - HTML-to-text conversion for filing documents, with heading detection and offset-based paging for oversized filings
281
- - Opt-in local SQLite mirror of `company_tickers` + XBRL company-facts (`EDGAR_MIRROR_ENABLED`) serves CIK resolution and financials from disk instead of the live API
228
+ - One process-wide queue paces SEC requests under the 10 req/s limit. A 429 is never retried: every SEC call is refused locally as `rate_limited` with a `retryAfter` countdown for `EDGAR_RATE_LIMIT_COOLDOWN_SECONDS`, then a single probe goes out. Reads served from the local mirror keep answering
229
+ - CIK resolution from tickers (fund tickers included), current and former company names, or raw CIKs, with corporate-suffix normalization and near-match suggestions on a miss
230
+ - Friendly XBRL concept names that handle historical tag changes. `secedgar_get_financials`, `secedgar_get_snapshot`, and `secedgar_compare_companies` share one frame dedup and tag priority, so their numbers agree, and each reports `caveats` for calendar quarters missing from the frame-tagged series (SEC files fiscal Q4 only as the 10-K residual) and for series that stop years short
231
+ - Filing documents converted from HTML to text, with heading detection and offset paging for oversized filings
232
+ - Opt-in local SQLite mirror of company tickers and XBRL company-facts (`EDGAR_MIRROR_ENABLED`) that serves CIK resolution and financials from disk
282
233
 
283
234
  Agent-friendly output:
284
235
 
285
- - In-conversation SQL analytics — data-returning tools materialize their full result as a DuckDB-backed canvas dataframe (`df_<id>`); inspect its columns with `secedgar_dataframe_describe`, then query with `secedgar_dataframe_query`
286
- - Discriminated outputs — `source` fields on merged filing-search rows (`efts`/`submissions`/`full-index`), typed `caveats` entries for series staleness and fiscal-Q4 gaps, and `gaps`/`failed_companies` rows instead of silent omission
287
- - Graceful partial failure — `secedgar_compare_companies` returns every resolved company alongside `failed_companies` and per-concept `gaps` rather than failing the whole request
288
- - Provenance on scan and staleness — `history_scanned_through`, `publication_lag_days`, and `dataset.truncated` let agents reason about scan depth, report lag, and completeness
236
+ - In-conversation SQL: any tool whose response carries a `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_query`
237
+ - Discriminated outputs and explicit gaps: `source` on filing-search rows, `search_mode`, 8-K item `regime`, typed `failed_companies` reasons, and `gaps` with `tags_tried` in place of zero-filled values
238
+ - Completeness disclosure: `history_scanned_through`, `total_is_exact`, `publication_lag_days`, and `dataset.truncated` tell agents how deep a scan went and what it left out
239
+ - One parameter name per concept: `company`, `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
289
240
 
290
241
  ## Getting started
291
242
 
@@ -370,8 +321,8 @@ MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http
370
321
 
371
322
  ### Prerequisites
372
323
 
373
- - [Bun v1.3.0](https://bun.sh/) or higher (or Node.js v24+).
374
- - A SEC EDGAR User-Agent string — any `"AppName contact@email.com"` format works; no account or key required.
324
+ - [Bun v1.4.0](https://bun.sh/) or higher (or Node.js v24+).
325
+ - A User-Agent string in SEC's `"AppName contact@email.com"` format; no account or key required.
375
326
 
376
327
  ### Installation
377
328
 
@@ -393,33 +344,32 @@ cd secedgar-mcp-server
393
344
  bun install
394
345
  ```
395
346
 
396
- 4. **Build:**
347
+ 4. **Configure environment:**
397
348
 
398
349
  ```sh
399
- bun run build
350
+ cp .env.example .env
351
+ # edit .env and set EDGAR_USER_AGENT
400
352
  ```
401
353
 
402
354
  ## Configuration
403
355
 
404
- All configuration is validated at startup via Zod schemas in `src/config/server-config.ts`. Key environment variables:
405
-
406
356
  | Variable | Description | Default |
407
357
  |:---|:---|:---|
408
- | `EDGAR_USER_AGENT` | **Required.** User-Agent header for SEC compliance. Format: `"AppName contact@email.com"`. SEC blocks IPs without a valid User-Agent. | — |
409
- | `EDGAR_RATE_LIMIT_RPS` | Max requests/second to SEC APIs. Do not exceed 10. | `10` |
410
- | `EDGAR_TICKER_CACHE_TTL` | Seconds to cache the company tickers lookup file. | `3600` |
411
- | `EDGAR_DATASET_TTL_SECONDS` | Per-table TTL for canvas-registered dataframes. Sliding window touched on every dataframe op. | `86400` |
412
- | `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, and the tool is still listed on the HTTP landing page as disabled, with the flag that enables it. | `false` |
413
- | `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` |
414
- | `EDGAR_MIRROR_PATH` | Directory holding the mirror SQLite databases. | `./data/edgar-mirror` |
415
- | `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`. | — |
416
- | `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` |
417
- | `CANVAS_PROVIDER_TYPE` | Canvas engine. Defaults to `duckdb`; set to `none` to disable the canvas. | `duckdb` |
418
- | `MCP_TRANSPORT_TYPE` | Transport: `stdio` or `http` | `stdio` |
419
- | `MCP_HTTP_PORT` | HTTP server port | `3010` |
420
- | `MCP_AUTH_MODE` | Authentication: `none`, `jwt`, or `oauth` | `none` |
421
- | `MCP_LOG_LEVEL` | Log level (`debug`, `info`, `warning`, `error`, etc.) | `info` |
422
- | `LOGS_DIR` | Directory for log files (Node.js only). | `<project-root>/logs` |
358
+ | `EDGAR_USER_AGENT` | **Required.** User-Agent sent to SEC, as `"AppName contact@email.com"`. SEC blocks IPs without one. | — |
359
+ | `EDGAR_RATE_LIMIT_RPS` | Max requests per second to SEC (1–10). | `10` |
360
+ | `EDGAR_RATE_LIMIT_COOLDOWN_SECONDS` | Seconds to refuse calls locally after a 429 before one probe goes out. SEC lifts a block only after ten quiet minutes, so a shorter value probes into it. | `600` |
361
+ | `EDGAR_TICKER_CACHE_TTL` | Seconds to cache the company and fund ticker files. A failed fund-file load is retried after a minute (or the rate-limit cool-down) instead of standing for the whole TTL. | `3600` |
362
+ | `EDGAR_DATASET_TTL_SECONDS` | Per-table TTL for canvas dataframes, a sliding window renewed on every dataframe operation. | `86400` |
363
+ | `EDGAR_DATAFRAME_DROP_ENABLED` | Set `true` to expose `secedgar_dataframe_drop`, the only destructive tool. | `false` |
364
+ | `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` |
365
+ | `EDGAR_MIRROR_PATH` | Directory holding the mirror databases. | `./data/edgar-mirror` |
366
+ | `EDGAR_MIRROR_REFRESH_CRON` | In-process refresh cron (HTTP transport only), e.g. `0 9 * * *`. Omit to refresh with `bun run mirror:refresh`. | — |
367
+ | `EDGAR_MIRROR_FALLBACK_LIVE` | Fall back to the live SEC API on a mirror miss. Set `false` for mirror-only reads. | `true` |
368
+ | `CANVAS_PROVIDER_TYPE` | Canvas engine; `none` disables dataframes. | `duckdb` |
369
+ | `MCP_TRANSPORT_TYPE` | Transport: `stdio` or `http`. | `stdio` |
370
+ | `MCP_HTTP_PORT` | HTTP server port. | `3010` |
371
+ | `MCP_AUTH_MODE` | Authentication: `none`, `jwt`, or `oauth`. | `none` |
372
+ | `MCP_LOG_LEVEL` | Log level (`debug`, `info`, `warning`, `error`, etc.). | `info` |
423
373
 
424
374
  See [`.env.example`](./.env.example) for the full list of optional overrides.
425
375
 
@@ -447,25 +397,28 @@ docker build -t secedgar-mcp-server .
447
397
  docker run -e EDGAR_USER_AGENT="MyApp my@email.com" -p 3010:3010 secedgar-mcp-server
448
398
  ```
449
399
 
450
- The image ships the mirror CLI, so the local mirror (`EDGAR_MIRROR_ENABLED`) can be bootstrapped, inspected, and refreshed inside a running container:
400
+ The image defaults to HTTP on port 3010 and ships the mirror CLI, so a running container can bootstrap and refresh its own mirror:
451
401
 
452
402
  ```sh
453
403
  docker exec <container> bun run mirror:verify # sync status + sample reads
454
- docker exec <container> bun run mirror:init # one-time bootstrap (downloads the SEC bulk archive)
455
- docker exec <container> bun run mirror:refresh # re-ingest when the archive has been rebuilt
404
+ docker exec <container> bun run mirror:init # one-time bootstrap from the SEC bulk archive
405
+ docker exec <container> bun run mirror:refresh # re-ingest after SEC rebuilds the archive
456
406
  ```
457
407
 
458
408
  ## Project structure
459
409
 
460
410
  | Directory | Purpose |
461
411
  |:---|:---|
462
- | `src/mcp-server/tools/definitions/` | Tool definitions (`*.tool.ts`). Fourteen SEC EDGAR tools plus three `dataframe_*` tools for SQL analytics. |
463
- | `src/mcp-server/resources/definitions/` | Resource definitions. XBRL concepts and filing types. |
464
- | `src/mcp-server/prompts/definitions/` | Prompt definitions. Company analysis prompt. |
465
- | `src/services/edgar/` | SEC EDGAR API client, XBRL concept mapping, HTML-to-text conversion. |
466
- | `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. |
467
- | `src/config/` | Server-specific environment variable parsing and validation with Zod. |
468
- | `tests/` | Unit and integration tests, mirroring the `src/` structure. |
412
+ | `src/index.ts` | `createApp()` entry point: registers resources and prompts, starts the SEC client, canvas, and optional mirror. |
413
+ | `src/config` | Server environment variable parsing and validation with Zod. |
414
+ | `src/mcp-server/tools/definitions` | Tool definitions (`*.tool.ts`) and the `buildToolDefinitions()` registration list. |
415
+ | `src/mcp-server/resources/definitions` | Resource definitions: XBRL concepts and filing types. |
416
+ | `src/mcp-server/prompts/definitions` | Prompt definitions: company analysis. |
417
+ | `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. |
418
+ | `src/services/edgar/mirror` | Opt-in local SQLite mirror of company tickers and XBRL company-facts. |
419
+ | `src/services/canvas-bridge` | Adapter over the framework `DataCanvas`: `df_<id>` naming, per-table TTL, system-catalog SQL deny. |
420
+ | `scripts` | Build, devcheck, and lint tooling, plus the `mirror:*` commands. |
421
+ | `tests` | Unit and integration tests, mirroring the `src/` structure. |
469
422
 
470
423
  ## Development guide
471
424
 
@@ -473,7 +426,7 @@ See [`CLAUDE.md`](./CLAUDE.md) and [`AGENTS.md`](./AGENTS.md) for development gu
473
426
 
474
427
  - Handlers throw, framework catches — no `try/catch` in tool logic
475
428
  - Use `ctx.log` for logging, `ctx.state` for storage
476
- - Register new tools and resources in the `createApp()` arrays
429
+ - Register new tools in `buildToolDefinitions()` (`src/mcp-server/tools/definitions/index.ts`), and resources and prompts in the `createApp()` arrays in `src/index.ts`
477
430
  - Wrap external SEC EDGAR calls: validate the raw response → normalize to a domain type → return the output schema; never fabricate a missing XBRL field — report it under `gaps` instead
478
431
 
479
432
  ## Contributing
@@ -0,0 +1,36 @@
1
+ ---
2
+ summary: "After a 429, calls are refused locally for SEC's cool-down instead of each reaching SEC and restarting its block. company/filed_after/filed_before/forms replace ticker_or_cik/start_date/end_date/form_types, old spellings accepted as aliases. secedgar_dataframe_query fixes a backslash-escaping bug and gains a fuller SQL error contract."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.15.7 — 2026-09-21
8
+
9
+ ## Added
10
+
11
+ - **SEC rate-limit block gate** ([#116](https://github.com/cyanheads/secedgar-mcp-server/issues/116)) — after SEC answers a 429, nothing more is sent to SEC for `EDGAR_RATE_LIMIT_COOLDOWN_SECONDS` (default `600`): calls fail locally with reason `rate_limited` and `retryAfter` set to the seconds remaining, the first call after the cool-down goes out alone as a probe, and mirror-served reads keep answering throughout. A 429 is still never retried. `rate_limited` is declared, `thrownBy: 'service'`, on every tool that reaches SEC.
12
+ - **`EDGAR_RATE_LIMIT_COOLDOWN_SECONDS`** env var (default `600`) — documented in `.env.example`, `manifest.json`, and `server.json`.
13
+
14
+ ## Changed
15
+
16
+ - **One parameter name per concept, with the other spellings accepted as aliases** ([#115](https://github.com/cyanheads/secedgar-mcp-server/issues/115)) — `company` replaces `ticker_or_cik` on `secedgar_get_insider_transactions` and `secedgar_get_institutional_holdings`; `filed_after`/`filed_before` replace `start_date`/`end_date` on `secedgar_search_filings`; `forms` replaces `form_types` on `secedgar_company_search`. Every tool carrying one of those keys declares `inputAliases` for the retired spelling, each sibling tool's spelling, and `ticker`/`cik`/`date_from`/`date_to`, so a name carried over from another tool is never rejected. Dataframe provenance (`query_params` in `secedgar_dataframe_describe`) now records the canonical names.
17
+ - **Server `instructions` rewritten**, shorter and less repetitive across tool mentions.
18
+ - **mcp-ts-core 0.13.3–0.13.6 adoption**: an argument rejection carries reason `invalid_arguments` and a `Recovery:` line derived from the schema, naming the accepted keys when a key is unknown (0.13.3, [cyanheads/mcp-ts-core#445](https://github.com/cyanheads/mcp-ts-core/issues/445)); `_meta`, `tool_call_description`, and `toolCallId` are dropped from arguments rather than rejected, a case-style key variant (e.g. `Company`) resolves to its declared key, and a JSON-stringified array is repaired against the schema (0.13.4, [cyanheads/mcp-ts-core#453](https://github.com/cyanheads/mcp-ts-core/issues/453), [cyanheads/mcp-ts-core#452](https://github.com/cyanheads/mcp-ts-core/issues/452), [cyanheads/mcp-ts-core#234](https://github.com/cyanheads/mcp-ts-core/issues/234)); tool error text now closes with `(reason <reason>)`, adding `· retryable` or `· not retryable` when the error carries a `retryable` flag (0.13.5, [cyanheads/mcp-ts-core#458](https://github.com/cyanheads/mcp-ts-core/issues/458)).
19
+ - `.env.example`'s framework block synced with the template — comments reworded, newly documented variables (`MCP_HTTP_RESUMABILITY*`, `MCP_GC_PRESSURE_INTERVAL_MS`, `MCP_LOG_RATE_LIMIT_*`) added; `MCP_SESSION_MODE` stays `stateless`.
20
+ - `devcheck.config.json` gains a `lint` block; `scripts/lint-mcp.ts` reads `lint.truncationAllowlist` from it (a declared value wins over `MCP_LINT_TRUNCATION_ALLOWLIST`), and now guards its `main()` behind an entrypoint check so it's importable without executing.
21
+ - Added `.github/workflows/codeql.yml` — the one GitHub Actions workflow this project carries; CodeQL is GitHub-owned end to end and the file only runs while the repo's CodeQL default setup is off.
22
+ - Framework skills (`framework-skills/`), `changelog/template.md`, and the issue-report forms synced to the current framework template.
23
+
24
+ ## Fixed
25
+
26
+ - **`secedgar_dataframe_query` table cells now escape backslashes before pipes** ([#114](https://github.com/cyanheads/secedgar-mcp-server/issues/114)) — a backslash already in a cell value was left unescaped, so a Markdown renderer read it as an escape and `content[]` rendered `x\|y` as `x|y` and `a\*b` as `a*b`; the header row gets the same escaping as data cells, which it previously skipped entirely.
27
+ - **A SELECT that prepares and then fails on the data it reads** (a bad cast, an out-of-range value) now returns `ValidationError` with reason `sql_execution_error` instead of an unreasoned `DatabaseError` (mcp-ts-core 0.13.4, [cyanheads/mcp-ts-core#451](https://github.com/cyanheads/mcp-ts-core/issues/451)). `multi_statement`, `denied_function`, and `plan_operator_not_allowed` are now declared on the tool's error contract alongside the framework's own recovery hints for each.
28
+
29
+ ## Dependencies
30
+
31
+ - `@cyanheads/mcp-ts-core` ^0.13.2 → ^0.13.6
32
+ - `@duckdb/node-api` ^1.5.5-r.4 → ^1.5.5-r.5
33
+ - `zod` ^4.6.4 → ^4.6.5
34
+ - `@biomejs/biome` ^2.5.13 → ^2.5.14 (dev)
35
+ - `@types/node` ^26.5.1 → ^26.6.2 (dev)
36
+ - `vitest` ^5.0.0 → ^5.0.1 (dev)