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
Available Tools
4 toolssec_edgar_get_company_profileA
Resolves a ticker symbol to the company's SEC identity: CIK, official registrant name, industry classification and fiscal year end. Usually the first tool to call, since the other tools key off the company.
| Name | Required | Description | Default |
|---|---|---|---|
| ticker | Yes | Stock ticker symbol, e.g. AAPL |
Output Schema
| Name | Required | Description |
|---|---|---|
| cik | Yes | 10-digit zero-padded SEC Central Index Key |
| name | Yes | Official registrant name as filed with the SEC |
| ticker | Yes | |
| fiscal_year_end | No | Fiscal year end as MMDD, e.g. 0927 |
| sic_description | No | Standard Industrial Classification description |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description must carry the full burden of behavioral disclosure. It clearly explains the core lookup behavior and adds usage ordering, but it does not mention error behavior (e.g., invalid ticker), rate limits, or explicitly confirm it is read-only. This is a moderate level of transparency for a simple lookup tool.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is two sentences long. The first sentence front-loads the action and outputs, and the second sentence gives a crucial usage guideline. Every word is purposeful, with no redundancy or filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given that this tool has only one parameter, an output schema, and sibling tools that depend on its output, the description is complete. It states what the tool does, what it returns, and when to use it, which is sufficient for an agent to call it correctly without needing further details.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The input schema already provides a clear description for 'ticker' ('Stock ticker symbol, e.g. AAPL') with 100% coverage, so the baseline is 3. The description reinforces the ticker's role but does not add significant new semantic detail beyond what the schema already provides.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses the specific verb 'resolves' and clearly states the resource ('ticker symbol' to 'company's SEC identity'), listing concrete outputs such as CIK and registrant name. It also differentiates from sibling tools by positioning this as the entry point tool, making its purpose unmistakable.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly says 'Usually the first tool to call, since the other tools key off the company.' This gives clear when-to-use guidance and explains why it should be called before the sibling tools, which is excellent contextual direction.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
sec_edgar_get_concept_seriesA
Returns the reported time series for one financial concept (revenue, net income, total assets, ...). Use this for trend and ratio analysis. The most reliable way to call it is with one of THESE ALIASES: capex, cash, eps_diluted, gross_profit, net_income, operating_cash_flow, operating_income, revenue, rnd_expense, stockholders_equity, total_assets, total_liabilities. An alias merges every US-GAAP tag the company has used for that concept over time, so history is not silently truncated when a company switches tags after an accounting standard change. A raw US-GAAP tag is also accepted. If nothing is found, the error message explains how to proceed.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Maximum number of periods to return, most recent last | |
| period | No | annual = periods spanning roughly one year; quarterly = roughly one quarter | annual |
| ticker | Yes | Stock ticker symbol, e.g. AAPL | |
| concept | Yes | Takma ad (revenue, net_income, total_assets, ...) veya ham US-GAAP etiketi (orn. NetIncomeLoss) |
Output Schema
| Name | Required | Description |
|---|---|---|
| cik | Yes | |
| label | No | |
| points | Yes | |
| taxonomy | Yes | |
| requested_concept | Yes | The alias or tag the caller supplied |
| resolved_concepts | Yes | US-GAAP tags that returned data and were MERGED into this series |
| fiscal_year_derived | Yes | True: fiscal year labels were derived from the company's own SEC filings. False: no anchor filing was found, so the calendar year of the period end was used instead - treat these labels as less reliable. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden. It discloses an important behavioral trait: aliases merge US-GAAP tags to avoid silent history truncation. It also explains that if nothing is found, the error message guides the user. This is meaningful beyond the schema, though it does not explicitly state read-only behavior or other safety aspects, hence a 4.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is organized into three short paragraphs, each with a clear purpose: what it does, when to use it, and how aliases work. Every sentence adds value, but it is slightly verbose with the alias list and explanation; still, it remains appropriately sized for the complexity.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's complexity (4 params, output schema exists), the description covers all essential context: purpose, usage context, alias details, and error handling. It does not need to describe return values because an output schema is present. The guidance on error recovery is especially helpful, making the description complete for an agent.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema already covers 100% of parameters, so the baseline is 3, but the description greatly enhances the concept parameter by listing all valid aliases, explaining the merging behavior, and noting that raw US-GAAP tags are also accepted. This goes well beyond the schema's minimal description and gives the agent concrete, usable knowledge.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb+resource: 'Returns the reported time series for one financial concept' and gives concrete examples (revenue, net income, total assets). This clearly distinguishes it from siblings like list_available_concepts (which lists concepts) and get_company_profile (which returns company info).
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description states 'Use this for trend and ratio analysis,' providing a clear use case. It also instructs that using aliases is the most reliable approach, which is practical guidance. However, it does not explicitly mention when not to use it or point to alternatives like list_available_concepts for discovering concepts, so it falls short of a full 5.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
sec_edgar_list_available_conceptsA
Lists the US-GAAP tags a company actually reports to the SEC. Call this when sec_edgar_get_concept_series cannot find a concept: the returned 'tag' values can be passed straight back into that tool. Narrow the result with search (case-insensitive substring match on tag and label). Results are ordered by how many data points each tag has, so the company's primary line items come first.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Maximum number of tags to return; check has_more in the response | |
| search | No | Substring to match against tag names and labels, e.g. 'revenue' | |
| ticker | Yes | Stock ticker symbol, e.g. AAPL |
Output Schema
| Name | Required | Description |
|---|---|---|
| cik | Yes | |
| concepts | Yes | |
| has_more | Yes | True if more tags matched than were returned; narrow with search |
| returned | Yes | Number of tags returned in this response |
| total_matching | Yes | Total number of tags matching the filter |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries full disclosure burden. It reveals ordering by data point count, case-insensitive substring search on tag and label, and that results are limited to what the company actually reports. It doesn't cover error cases or rate limits, but for a read-only list tool this is strong behavioral context.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Four dense, purposeful sentences, each adding new information: purpose, usage guidance, search behavior, and ordering. No fluff or redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given an output schema exists (so return values are already documented), the description covers purpose, usage, search, and ordering. It is fully self-sufficient for an agent to decide when and how to invoke the tool, with no obvious gaps.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema already provides 100% description coverage for all three parameters. The description adds a useful detail about search being case-insensitive, but otherwise relies on the schema's parameter descriptions, matching the baseline for high schema coverage.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool 'Lists the US-GAAP tags a company actually reports to the SEC.' This is a specific verb-resource pair and distinguishes the tool from siblings like sec_edgar_list_filings and sec_edgar_get_concept_series by focusing on available concepts.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly instructs when to use: 'Call this when sec_edgar_get_concept_series cannot find a concept.' It also explains how the output integrates with that sibling tool, leaving no ambiguity about alternatives.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
sec_edgar_list_filingsA
Lists a company's most recent SEC filings with links to the primary document. Pass form_type to restrict to one kind of filing, e.g. '10-K' for annual reports or '10-Q' for quarterly reports.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Maximum number of filings to return, newest first | |
| ticker | Yes | Stock ticker symbol | |
| form_type | No | Restrict to one filing type, e.g. 10-K. Omit to return all types. |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full burden of behavioral disclosure. It mentions that results include links to the primary document and implies read-only behavior with 'Lists', but does not disclose potential limper or access restrictions. Since the tool is a simple read, this is adequate but not rich.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is two sentences with zero redundant content. It leads with the action, includes a concrete example, and conveys both purpose and the key optional parameter behavior efficiently.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a low-complexity tool with a full output schema and complete parameter descriptions, the description adequately covers purpose, usage, and return characteristics. It does not mention edge cases like invalid tickers, but such details are not necessary for a simple listing function.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema provides descriptions for all three parameters, so the description does not need to add much. It does offer an example for form_type ('10-K', '10-Q') which slightly reinforces the schema's meaning, but adds no new semantics beyond what the schema already states.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool's purpose: 'Lists a company's most recent SEC filings with links to the primary document.' This uses a specific verb ('Lists') and a distinct resource ('company's most recent SEC filings'), which differentiates it from sibling tools like get_company_profile or get_concept_series.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives clear context for when to use the tool (to retrieve recent filings) and explains how to narrow results with form_type. It does not explicitly exclude alternative uses or name sibling alternatives, but the siblings address different data, so the guidance is sufficient.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.
4 tool updates
v0.1.0- First observed
sec_edgar_get_company_profile - First observed
sec_edgar_get_concept_series - First observed
sec_edgar_list_available_concepts - First observed
sec_edgar_list_filings
TDQS
Scored across 4 tools
Each tool has a clearly distinct purpose: company profile resolution, filing listing, financial concept series retrieval, and concept discovery. No overlap or ambiguity between them.
All tools follow a consistent 'sec_edgar_' prefix with verb_noun structure (get_company_profile, list_filings, get_concept_series, list_available_concepts). The pattern is uniform and predictable.
Four tools is well-scoped for a focused SEC EDGAR server. Each tool covers a necessary step in the workflow without redundancy, and the count is appropriate for the domain.
The tool set covers the core workflow: identify a company, list its filings, retrieve financial concepts, and discover additional available concepts. There are no obvious gaps for the intended purpose.
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 Connectors
SEC & financial-data MCP: filings, financials, ownership, factors, fund letters, prompts.
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.
The Octagon MCP server provides specialized AI-powered financial research and analysis by integrating with the Octagon Market Intelligence API. It enables users to analyze public market data (SEC filings, earnings transcripts, financial metrics, and stock data for 8000+ companies), private market data (3M+ companies, 500k+ funding rounds, 2M+ M&A/IPO transactions), and conduct deep research including web scraping capabilities. The server also features autonomous research agents that search hundreds of sources and return fully cited reports in approximately one minute.
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.21355AGPL 3.0
- AlicenseNot gradedqualityDmaintenanceHosted 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.151MIT
- 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
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