sec-edgar-mcp
This server lets an LLM access official, verifiable SEC financial data in real time instead of relying on memory, with every number traceable to a specific filing, US-GAAP tag, and filing date.
Company lookup:
sec_edgar_get_company_profileresolves a ticker to CIK, registrant name, SIC industry, and fiscal year end.Filing discovery:
sec_edgar_list_filingsretrieves recent filings (10-K, 10-Q, 8-K, etc.) with links, filterable by form type.Financial time series:
sec_edgar_get_concept_seriesreturns historical values for concepts like revenue, net income, total assets, EPS, and operating cash flow, using aliases that merge multiple tags to avoid history truncation and handle tag changes and fiscal-year determination.Concept discovery:
sec_edgar_list_available_conceptslists US-GAAP tags a company reports, searchable and ordered by data richness.Fact revisions/restatements:
sec_edgar_get_fact_revisionsshows how a reported figure changed across filings by accession number.Filing narrative text:
sec_edgar_read_filing_textextracts MD&A, risk factors, tax notes, segment notes, and exhibit text, with pagination and search.Cross-company comparison:
sec_edgar_compare_companiesranks companies by a concept for a period, accounting for period differences and missing tickers.Dimensional/segment data:
sec_edgar_list_fact_dimensionsdiscovers segment, geographic, and product-line breakdowns;sec_edgar_get_dimensional_factsretrieves numbers behind consolidated totals, with totals shown alongside and no assumption of summation.Accuracy and compliance: self-limits requests, requires User-Agent, and avoids common pitfalls such as fiscal-year mislabeling and period mismatches.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@sec-edgar-mcpWhat was Apple's revenue for fiscal year 2023?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
edgar-audit-mcp
A Model Context Protocol server for SEC EDGAR: XBRL financials, filing text and tables, ownership — with the provenance of every figure attached.
A Model Context Protocol server that lets an LLM read official SEC financial data instead of recalling it from training. Every number an agent returns through these tools can be traced back to a specific SEC filing, a specific US-GAAP tag and a specific filing date.
Built against the 2026-07-28 MCP specification using the Python SDK v2.0.0.
Start here: what this gets wrong that other tools get wrong silently · measured on 50 expert-written questions: the server corrected 32 answers and broke none · 40 failures that shipped here, each with the test that guards it
An independent project. This server was written without reading the code of
stefanoamorelli/sec-edgar-mcp, the largest project in this niche, and shares no code with it. It carried the namesec-edgar-mcpuntil 19 August 2026 and was renamed because that project holds the name on PyPI. Every design decision here is recorded inCLAUDE.mdwith the live measurement that produced it.What is different is not coverage. It is that every figure carries the filing, the US-GAAP tag and the filing date it came from; that a point-in-time cutoff (
as_of) is available on every tool that selects by recency, so a question about the past is answered from what was actually filed by then; and that the effect of the server on an agent's answers has been measured against a third-party benchmark and published with its raw data.
Before quoting the benchmark number. The questions and the expected answers
are not ours — they are the public fifty of the
Vals AI Finance Agent Benchmark
(CC BY 4.0), written by domain experts with no connection to this project. The
run and the grading are ours: the grader is a language model, and no one
outside this project has audited the result. Both arms' raw answers, the grader
input, the grades, a measurement of how much two graders disagree (±2 points),
and every known weakness are in
evaluation/benchmark.md. The durable finding there
is the paired one — 32 questions the server got right and the control arm did
not, none the other way round — not the 90%, which is a point estimate on fifty
questions with a 95% interval of 79–96%.
What this does not do
Stated here rather than discovered later:
No price or market data. It is not in SEC filings.
No write access of any kind. Read-only by design, and a test asserts that no write path exists in the source.
US registrants only.
No XBRL before 2009 — tagged data does not exist for those filings. The answer for older periods is entirely in the text tools.
Full-text search is practically limited to 2001 onward. Measured, not assumed: a 1996–2000 search for a word as common as "revenue" returns 14 filings, so an empty result for older periods proves nothing. Responses that reach before 2001 carry a
coverage_notesaying so.Nested breakdown levels on one axis are not resolved. Which member is the parent of which lives in the definition linkbase, and this server does not read it. Rather than guess, the response shows every member separately.
Related MCP server: SEC EDGAR MCP
Why this exists
Ask a language model for a company's revenue and it will answer from memory. The answer is often close, sometimes wrong, and never verifiable. For financial work that is unusable.
This server replaces recall with a lookup. But "just call the SEC API" is not enough either — SEC's XBRL data has several traps that produce silently wrong answers. The interesting part of this project is handling them.
Tools
Tool | Purpose |
| Ticker → CIK, registrant name, SIC industry, fiscal year end |
| A company's filings with links, filterable by form type; reaches past SEC's ~1000-entry recent feed with |
| Full-text search across every filer — finds the filings that contain a phrase, down to the exhibit that carries it |
| Time series for one financial concept |
| How a reported figure changed across filings — restatements, with the accession number of each change |
| The narrative XBRL does not carry: MD&A, risk factors, the tax and segment notes — including 8-K exhibits and in-filing search |
| Which tags a company actually reports, in any taxonomy it uses |
| One concept across every company that reported it for a period, ranked |
| Which breakdowns a filing contains — segments, geographies, product lines |
| The numbers behind a consolidated total, with the total shown next to them |
| Form 4 — what directors and officers did with the shares, grouped by transaction code rather than netted |
| 13F — an investment manager's quarter-end positions, with the 2023 unit change handled |
Every tool returns a Pydantic model, so MCP outputSchema is generated
automatically and clients consume the results type-safely. Every list-returning
tool reports how much it did not return, so the model can tell a complete
answer from a truncated one. The field name follows the unit rather than a
single convention — total_matching for filings, total_periods for a series,
total_companies for a comparison, total_characters for filing text — and
each is paired with returned / has_more.
Concepts are addressed by alias (revenue, net_income, public_float, ...)
or by raw tag. A tag may be qualified with its taxonomy — dei:EntityPublicFloat
— and defaults to us-gaap when it is not. sec_edgar_list_available_concepts
reports which taxonomies a company actually files under, so the model discovers
them instead of guessing: financial statements live in us-gaap, while public
float and shares outstanding live in dei.
The eight tools that can take several seconds — listing filings, full-text
search, reading a filing, parsing an XBRL instance, ranking a frame of
thousands of companies, reading Form 4s and a 13F table — report progress
while they work, which the 2026-07-28 spec defines for exactly this case. The
rest do not: progress on a call that returns in milliseconds is noise. Nothing
else beyond tools is implemented, deliberately — for a read-only data server
resources, prompts and completions would be decoration, and logging,
sampling and roots were deprecated in this very spec revision.
Every tool is annotated readOnlyHint: true. That annotation is a hint,
not a guarantee — the guarantee is that the package contains no write path at
all, which a test enforces.
Reading the filing text
XBRL carries the numbers, not the reasons. sec_edgar_read_filing_text reads
the filing itself and hands back a named section — MD&A, risk factors, the
income-tax note.
Three things make that harder than it sounds, and each is guarded by tests:
The table of contents says the same thing as the section. "Item 7. Management's Discussion and Analysis" appears at least twice in a 10-K: once as a contents entry, once as the section. Taking the first match hands the model two lines of navigation and an apparently empty section. A heading only counts when real text follows it, and when a heading still appears twice, the longer block wins.
Filings are millions of characters. Text comes back in bounded chunks with
offset/has_more. The converted text is cached, not the raw HTML: converting 2.2 MB of HTML measured at 0.61 s, so re-parsing on every page turn wasted seconds, and the text is some twenty times smaller than the markup it came from.The document a filing points to is not always the one with the content. SEC names one primary document per filing; on an 8-K that is the cover page and the substance is in an exhibit. Measured on Tesla's Q2 2026 delivery release (
0001628280-26-046717): the cover is 26,572 bytes and carries no figures, the exhibit is 13,243 bytes and carries all of them. Every readable file in the filing is listed on every call, with the primary one flagged, anddocumentreads any of them.
Calling the tool without a section returns the headings the filing actually
has, so the next call names one instead of guessing. When the right heading is
not obvious, search reports how many times a phrase occurs and where; each
position can be passed straight back as offset.
Addressing a filer that has no ticker
Every tool that takes a ticker also takes a CIK — 320193, 0000320193 or
CIK0000320193 all work. That is not a convenience: SEC's ticker file lists
only symbols that trade, so funds and foreign private issuers have none, and
full-text search returns those filers with a null ticker. Without CIK
addressing the server could find a document it could not then open.
A symbol asked for is echoed back as asked — GOOGL stays GOOGL rather than
becoming the alphabetically first symbol on the same CIK. Ask by CIK and the
symbol is looked up in SEC's file, and stays null when there is none rather
than being invented.
Reading the tables, not just the text
Filings put their most quotable figures in tables — the financial statements,
the tax reconciliation, the production and delivery release that XBRL never
tags. Converted to plain text a table becomes cells joined by |, and
whoever reads it has to align the columns by eye.
Passing tables to sec_edgar_read_filing_text returns the tables that begin
inside the text being returned, as rows and cells. Some care is in it:
A table's
text_offsetis in the same coordinate space asoffset, so a table can be matched to the passage it belongs to rather than to the document as a whole. Ask for a section and the offsets move with it.EDGAR filings use tables for page layout as much as for data. Tables with a single row or a single column are left out — and counted in
layout_tables_skipped, because silence about them would read as "this filing has no tables".Nested tables come back separately rather than merged, and the inner table's text is not copied into the outer cell: the same figures returned twice would invite a model to count them twice.
Row and cell limits exist, and each says so:
total_rowsagainstrow_count, plusrows_truncatedandcells_truncated.
Nothing here is new data — the same numbers are in the text. It is the structure, returned so the model does not have to reconstruct it.
Comparing across companies
The other tools answer about one company. sec_edgar_compare_companies reads
SEC's frames endpoint, which holds one period's value for every company that
reported a tag — 2,543 companies in the CY2025Q1 revenue frame, measured.
A frame looks like a like-for-like ranking and is not quite one, so the response says so in data rather than in a footnote:
The periods inside one frame are not the same period. SEC assigns each company's nearest fiscal period to a calendar frame. In CY2025Q1 the period ends run from 2025-02-23 to 2025-05-04 — seventy days apart. Apple appears there with its own fiscal second quarter, 2024-12-29 to 2025-03-29. Every row carries its own
period_end, and the response reports the spread across the whole frame.A missing company has not necessarily failed to report the concept. It may tag it differently, or have no period that fits. Requested tickers that the frame does not hold come back in
missing_tickersinstead of being dropped.A balance-sheet tag has no duration frame.
AssetsinCY2025Q1is a 404;CY2025Q1Iis the frame that exists. Both are tried and the one that answered is named in the response.
Rank is always computed against the whole frame, so asking about three companies does not make one of them "first".
Who owns the company, and what the insiders did
Two filings answer questions the financial statements never touch, and both are XML rather than XBRL.
sec_edgar_get_insider_transactions reads Form 4 - what directors, officers
and ten percent owners did with the company's shares, due within two business
days of the trade. The tool refuses to compute a single "net insider buying"
figure, and that refusal is the point: code A is a grant from the company at a
stated price of zero, code F is shares withheld to pay tax on a vesting grant,
and only P and S are decisions to buy or sell in the market. Adding them
produces a number that measures nothing, so share counts come back grouped by
code with each code's meaning next to it. Derivative lines - the restricted
units that later become those shares - are excluded by default, because a
single vesting event appears on both sides and counting both counts the shares
twice.
sec_edgar_get_institutional_holdings reads 13F - the US-listed equity
positions an investment manager held at a quarter end. Managers rarely have a
ticker, so they are addressed by CIK. A manager files one row per sub-manager,
so a single holding is spread over several rows (Berkshire's Apple position ran
to twelve in one filing); rows for the same security are combined and
rows_combined says how many.
One measured trap deserves its own paragraph. SEC changed 13F value reporting from thousands of dollars to whole dollars for filings from January 2023. Berkshire reported the identical 669,429,166 Apple shares in two consecutive quarters:
Filed | Shares | Value as filed | Implied price |
14 Nov 2022 | 669,429,166 | 92,515,111 | $0.14 if read as dollars |
14 Feb 2023 | 669,429,166 | 86,841,985,318 | $129.72 |
Apple closed 2022-12-30 at about $129.93. Read literally, a $92 million
position became an $87 billion one without a share changing hands. Both filings
are returned in whole dollars here, with value_basis naming the convention
the filing itself used.
Segment data, and why the REST API does not have it
SEC describes companyconcept, companyfacts and frames as aggregating
facts that "apply to the entire filing entity". A segment figure applies to a
part of it, so those endpoints do not carry breakdowns. (SEC does not use the
word "dimensional"; that reading of the sentence is inference, and measurement
agrees with it — Tesla's segment split is absent from companyfacts and
present in the filing's XBRL.)
sec_edgar_list_fact_dimensions reads the filing's XBRL instance and reports
the axes and members it actually contains, so the next call names them instead
of guessing. sec_edgar_get_dimensional_facts returns the facts, each with its
context id, unit, period and the axes qualifying it.
The part worth reading twice. A breakdown and its total are two separate claims, and this tool refuses to turn them into one equation:
Some filings report no entity-wide total for a concept at all; some tag the total on a parent member, so it is itself dimensional.
The members do not always sum to the total. XBRL US publishes a data-quality rule (DQC_0150) specifically to catch filings where they do not — which means filings where they do not exist.
A figure carrying two axes at once, say segment and geography, is an intersection, not one segment's share. Adding it to a segment sum counts part of the business twice.
A total tagged
xsi:nilis not zero.
So nothing is summed silently. Ask for one axis and the response carries the
member sum and the entity-wide total side by side, with the difference, and
names what it excluded from the sum and why — members_counted and
excluded_from_sum carry the counts, by reason. The sum is computed over every
matching fact in the filing, not over the page returned, so changing limit
never moves it. Deciding which number is right is left to the reader, who is
the only one who can.
A note on provenance: the file being read, <name>_htm.xml, is SEC's
extraction from the filer's inline XBRL document — SEC's own dissemination
spec lists it among EDGAR-generated outputs. The values and contexts are the
filer's; the container is SEC's. The chain reaches further than that, and it
was measured rather than assumed: every one of the first 200 fact ids taken
from Tesla's FY2025 instance also appears in the 2.4 MB inline document the
company filed, so the fact_id on a returned figure locates the tagged span
in the filer's own document, not just a row in SEC's extraction. Filings from before inline XBRL was phased in
(fiscal periods ending 2019-06-15 for large accelerated filers, 2020 and 2021
for smaller ones) carry a filer-submitted instance instead, and that is read
instead.
That fallback used to be described here as untested, which was honest but
unsatisfying. It was measured on 15 Aug 2026 against Tesla's 10-K for fiscal
2011, filed February 2012: the filing has no _htm.xml at all, carries a
769 KB instance the company submitted itself, and that instance holds
dimensional facts in the same shape as a modern one — xbrldi:explicitMember
inside entity/segment, including contexts qualified by two axes at once. Its
label linkbase resolved too, and doing so proved a design decision right: the
2011 file declares no namespace prefix and names its locators
us-gaap_Assets against label resources named us-gaap_Assets_lbl, so a
reader that matched labels by naming convention rather than by following the
arc would have returned nothing at all for that filing.
The real boundary is earlier and it is not ours to move: XBRL was phased in from fiscal periods ending after 15 June 2009, and nothing before that carries tagged data in any form. For those filings the figures exist only as text, and the text tools are the whole answer.
Searching the text of every filer
sec_edgar_search_filings goes the other way round from the tools above: it
starts from a phrase, not from a company. It queries EDGAR's full-text index,
so a question like "which filers discussed a tariff in an annual report" has an
answer that does not depend on knowing the company first.
Three things about it are worth stating plainly, because all three change how the result should be read:
A hit is a document, not a filing. Measured on 15 Aug 2026: the oldest match for tariff in Tesla's annual reports is not a 10-K at all but an exhibit inside one, a supply agreement. Results therefore carry both the accession number and the file name, and both go straight into
sec_edgar_read_filing_text.The total can be a lower bound. SEC reports large result counts with a
gterelation rather than a count;total_is_exactsays which one arrived.Coverage starts around 2001, and an empty result proves little before that. SEC's own page says the index holds filings "since 2001". A measured search of 1996-2000 annual reports for a word as common as revenue returned 14 filings, the oldest dated 1999-03-31 — so a few older documents are indexed and most are not. The response says so itself: a search that returns nothing, or that reaches before 2001, comes back with a
coverage_noterather than a bare zero.
The endpoint refuses to page past 10000 ranked results, which the schema
declares as a bound on offset instead of leaving the model to discover it
through an error.
One more measured quirk, found in live use a day after this tool shipped: SEC
drops a one-sided date range silently. Asking for filings from 2026 with no
end date returned 162 matches reaching back to 2009 — a filtered-looking answer
that was not filtered. The missing bound is now filled in (EDGAR's own
beginning, or today) and the range actually sent comes back in
date_range_applied.
Filings older than the recent feed
SEC's submissions endpoint caps its recent-filings feed at roughly a thousand
entries and moves the rest into separate files. For an active filer that cap is
not a long history: Tesla's recent feed holds 1,053 filings and reaches back
only to May 2018, while its one older file holds 1,096 more going back to
February 2005 (measured 15 Aug 2026).
sec_edgar_list_filings reads the recent feed by default and says whether more
exists. Pass include_older and it reads the older feeds too, merges them and
sorts by date. It reads at most four of them and reports how many it skipped,
because a bound nobody mentions reads as completeness. Older feeds do not
always name a primary document, so primary_document_url can be null; when
the filing is then opened by accession number, the tool picks the largest
readable file and marks the choice as a guess with primary_document_known: false rather than presenting it as SEC's designation.
Human-readable names for tags and members
tsla:OperatingLeaseVehiclesMember is a name a filing uses, not a name a person
would write. The filing itself carries the translation, in its label linkbase
(*_lab.xml), and the dimension tools read it: axes, members and tags come back
with axis_label, member_label and tag_label next to the tags themselves.
The linkbase links a name to a label through an arc, and the code follows the
arc rather than the loc_/lab_ naming convention that generators happen to
use — a fault injection takes the shortcut and the test turns red. Where an
element carries labels in several roles the standard one wins, and the
documentation role, which is a definition paragraph rather than a name, is
never used as one. Nothing is invented: an element the filing does not label
comes back as its tag, and label_source names the file the labels came from,
or is null when the filing has no linkbase at all.
Labels cost one extra download — 1.21 MB against a 2.68 MB instance on Tesla's
FY2025 annual report — so include_labels turns them off.
Reading the filings as they stood on a past date
Every "most recent" carries a hidden as of now. Ask for the latest 10-K and the answer changes when the next one is filed; ask for FY2023 revenue and the figure is whatever the newest filing says, which is not necessarily what anyone could have read in January 2024. For a historical question — reproducing an analysis, testing a rule against what was knowable at the time, answering a question written a year ago — that assumption is silently wrong. Finance calls it look-ahead bias.
Every tool that selects by recency takes as_of, a date, and every response
repeats the cutoff in as_of_applied. Setting SEC_AS_OF in the environment
applies it to a whole process; where a call and the environment disagree the
earlier date wins, because letting one call reach past a session-wide cutoff
would make the cutoff meaningless.
The cut is on the filing date, not the period. A restated figure has the same period end as the original — only the filing date separates them, so a filter on the period would let every restatement through while appearing to work.
Measured live against SEC on Tesla's FY2017 revenue, which was restated two years after the fact:
call | FY2017 revenue | from a filing dated |
|
| 2018-02-23 |
no cutoff |
| 2020-02-13 |
Same period, same company, same concept. The first is what an analyst reading Tesla's accounts in 2018 saw; the second is the number that replaced it. Without a cutoff a tool answers a 2018 question with a 2020 filing and nothing marks the substitution.
Three details follow from taking the guarantee seriously:
A record whose filing date is unknown counts as after the cutoff. Keeping it would answer "what was known then" with a record whose date is not known.
A filing named explicitly by accession number is still refused when it postdates the cutoff. The error gives both dates, so the caller can decide rather than guess.
sec_edgar_compare_companiescannot honour it and says so. SEC's frame endpoint reports no filing date per row, so a restatement is indistinguishable from an original; under a cutoff the tool refuses the call and namessec_edgar_get_concept_series, which does honour it. A tool that cannot keep the promise should not pretend to.
Three traps this server handles
1. fy is the filing's year, not the data's year
SEC's companyconcept API attaches fy and fp to every fact. It is tempting
to read fy as the fiscal year of the value. It is not — it is the fiscal year
of the filing the value appeared in. A 10-K contains three years of
comparatives and all three carry the filing's fy.
Using fy naively shifted Apple's revenue series by two years without raising
an error. Periods here are determined only from start/end dates: annual is
300–400 days, quarterly is 60–120.
2. Fiscal year naming has no universal rule
Walmart's fiscal year ending 2026-01-31 is FY2026. Target's fiscal year ending 2026-01-31 is FY2025. Same end date, different label — Walmart names a fiscal year after the calendar year it ends in, Target after the year it starts in. No fixed rule gets both right.
So no rule is used. Takvim builds a list of (period end, fiscal year)
anchors from the company's own 10-K rows — SEC's fy field is correct for a
filing's own period, wrong only for the comparative years it carries — and a
period belongs to the fiscal year of the first anchor at or after it.
A single global offset was tried first and measured wrong on 18 Aug 2026: on US Foods it labelled two different fiscal years 2016 and dropped FY2015 and FY2020 from the series entirely, and on a company that moved its year end (Perrigo, June to December) it shifted every label in one regime by a year. Anchoring per period rather than per company handles a regime change without a special case, because each period looks at the anchor in its own regime.
Each point says where its label came from: fiscal_year_source is reported
when SEC stated that year, derived or extrapolated when this server counted
it. When no anchor exists at all the response sets fiscal_year_derived: false
rather than guessing silently.
3. Tag changes truncate history
Apple reported revenue under SalesRevenueNet before ASC 606 and under
RevenueFromContractWithCustomerExcludingAssessedTax after. Stopping at the
first tag that returns data silently dropped ten years of history.
Aliases merge every candidate tag. Where periods overlap, the most recently
filed value wins. Each point carries a source_tag so the provenance of every
number stays visible — different tags may not measure a concept identically,
and that difference is surfaced rather than hidden.
Usage
Concepts are requested by alias, not by raw XBRL tag:
sec_edgar_get_concept_series(ticker="MSFT", concept="revenue", limit=5)Available aliases: capex, cash, eps_diluted, gross_profit, net_income, operating_cash_flow, operating_income, public_float, revenue, rnd_expense, shares_diluted, shares_outstanding, stockholders_equity, total_assets, total_liabilities.
Raw US-GAAP tags are accepted too. When a concept is not found the error
message names the valid aliases and points at the discovery tool — errors are
written for the model to act on, not just to report failure.
Install
uv sync --extra dev # or: pip install -e ".[dev]"
cp .env.example .env # set SEC_USER_AGENT to your name and emailThe SEC requires automated clients to identify themselves with a contact email
in the User-Agent header and to stay under 10 requests per second
(SEC Webmaster FAQ). This
server self-limits to 8 req/s and refuses to start without SEC_USER_AGENT.
Where the variable comes from. The MCP server takes its environment from
whatever launches it — the env block in a Claude Desktop config, --env-file
in Docker, or your shell. The core package deliberately does not read .env;
that would add a runtime dependency that buys nothing on those paths. The local
scripts (dene.py, dogrula.py) do read .env, via python-dotenv from the
[dev] extra, so you don't have to export the variable in every new terminal.
Run
uv run mcp dev src/edgar_mcp/server.py # MCP Inspector
uv run edgar-audit-mcp # stdio, for Claude Desktop etc.
docker build -t edgar-audit-mcp . && docker run --env-file .env -p 8000:8000 edgar-audit-mcpClaude Desktop config:
{
"mcpServers": {
"sec-edgar": {
"command": "/absolute/path/to/.venv/bin/python",
"args": ["-m", "edgar_mcp.server"],
"env": { "SEC_USER_AGENT": "Your Name you@example.com" }
}
}
}Tests
pytest -q # HTTP layer mocked; never calls sec.gov
python arac/enjeksiyon.py # fault injection
python arac/sir_tarama.py --gecmis # secret scan, working tree + git history
python dogrula.py # live verification against real SEC data
python arac/tani.py KO Assets # inspect one raw SEC concept response
python arac/tani.py KO Assets --matris # same data under varied conditions, to isolate a cause
python arac/tani.py --tarama # how many companies are affected
python arac/tani.py TSLA --etiket # does the label parser resolve a real linkbaseFault injection
A test that has never been observed to fail is not evidence. arac/enjeksiyon.py
deliberately breaks each protection in turn and asserts that the matching test
turns red, then restores the file and verifies the restore by hash.
This is not decorative. It caught two tests in this repo that passed while protecting nothing — in both cases the mock did not reproduce the real API's contract, so the code path under test was never exercised. It also catches injections that have gone stale after a refactor, which is why it runs in CI.
Secret scanning
arac/sir_tarama.py scans the working tree; --gecmis additionally scans git
history. The distinction matters: a secret that was committed and then removed
is gone from the files but still readable in history, and a working-tree-only
scan reports "clean" while the secret is public.
The scanner refuses to report clean when it cannot see full history — a shallow
clone returns exit code 2, not 0. CI therefore checks out with fetch-depth: 0.
A check that silently does nothing is worse than no check.
Transport verification
The README's own instructions are executed by the suite, not just written down.
One test boots the streamable-HTTP transport on a free port and asks for
tools/list over real HTTP with no handshake, which is also what the
2026-07-28 stateless core requires. Another launches python -m edgar_mcp.server over stdio through the SDK's own client — the exact path a
desktop MCP client uses. A CI job builds the Docker image and queries the
running container from outside it.
That job exists because of a real defect: the SDK binds 127.0.0.1 by default,
which inside a container leaves the published port dead. The image had never
been run, so nothing had noticed.
Live verification
Mocks cannot prove behaviour against the real system. dogrula.py checks the
fiscal-year derivation and the tag-merging logic against live SEC data for
companies with calendar-year, ending-year and starting-year fiscal conventions.
Benchmark
evaluation/benchmark.md reports what changes when a
model has this server: the same model answered the 50 public questions of the
Vals AI Finance Agent Benchmark
twice — once with no tools, once with only these.
correct | partial | wrong | no answer | |
With this server | 45 (90%) | 4 | 0 | 1 |
Without tools | 13 (26%) | 17 | 3 | 17 |
The run is point-in-time to 2025-05-16, the date the dataset was published, so the questions are answered against the filings that existed when they were written. Every accession number the tool arm read was checked against SEC afterwards: none postdates the cutoff, and the latest one used was filed 2025-05-09.
Neither answering arm saw the expected answers; a separate grader saw the two
answers without being told which produced which — though not in random order,
and the report says so and says what that costs. Every
artifact — both runs' raw answers, the grading inputs, the grades, the arm key,
the compliance audit — is in evaluation/benchmark/, and the limits of the
number are written out in the report rather than left for the reader to find,
including a direct measurement of how much the grader itself moves it (two
graders agreed on 86% of the same fifty answers).
Evaluation set
evaluation/questions.xml holds twenty-two questions that
can only be answered by calling the tools: cross-company fiscal year labels,
tag merges across an accounting standard change, ratios that need two series,
and the pagination fields. Every answer was produced by running the tools
against live SEC data and reading the result — none is written from memory —
and each question records the exact calls used, so the measurement can be
repeated instead of trusted.
A test keeps the file structurally honest: twenty-two pairs, every pair carrying a question, an answer and a verification block, every tool it names actually existing on the server, and no question anchored to "the latest" period.
Failure patterns
PATTERNS.md catalogues every bug that actually shipped in this
repository — symptom, root cause, how it is detected now, and the incident that
produced it — together with the specific test that guards each one. The entries
that have no automated guard say so in both the checklist and the entry body,
because claiming otherwise would be worse than the gap.
A test suite keeps that document honest: every test, tool and CI job it names must exist, every entry must carry all four fields, and every incident must be dated. Rename a test and the document fails CI rather than quietly lying.
Project layout
src/edgar_mcp/server.py MCP tools and schemas
src/edgar_mcp/client.py SEC HTTP client, rate limiter, caching
tests/ mocked unit tests
tests/dil.py language gate for the outward-facing surface
tests/test_http_tasima.py runs the documented HTTP and stdio transports
evaluation/questions.xml twenty-two measured questions with their tool calls
arac/enjeksiyon.py fault-injection harness
arac/sir_tarama.py secret scanner
arac/tani.py raw single-response diagnostic for one SEC concept
dogrula.py live verification against SEC
CLAUDE.md decision records - why things are the way they are (Turkish)
PATTERNS.md failure patterns - what to watch out forCode comments and decision records are in Turkish; the public interface — tool descriptions, schemas, error messages, this README — is in English.
License
MIT
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- AlicenseAqualityBmaintenanceMCP server providing read-only access to SEC EDGAR filings, allowing LLMs to look up companies, search filings, and retrieve securities offering data.31MIT
- AlicenseCqualityBmaintenanceMCP server for accessing SEC EDGAR filings. Connects AI assistants to company filings, financial statements, and insider trading data with exact numeric precision.21353AGPL 3.0
- AlicenseNot gradedqualityBmaintenanceHosted MCP server that gives AI agents real-time access to SEC EDGAR filings search, 10-K/8-K reading, XBRL financial facts, and insider-trade (Form 4) alerts.101MIT
- AlicenseNot gradedqualityCmaintenanceAn MCP server that provides deterministic finance tools for SEC filing analysis, enabling LLMs to compute financial ratios, fetch filings, and perform equity research without hallucinated numbers.MIT
Related MCP Connectors
The financial MCP for AI agents - 90+ financial tables, SEC filings, signals, alt-data.
Real SEC, 13F, insider, congress & macro data your AI agent can cite. Hosted MCP, 24 tools.
Query SEC EDGAR filings, XBRL financials, and company data through MCP. STDIO & Streamable HTTP.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/belermirzaa7-ops/edgar-audit-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server