EDGAR + Entity: US Company Research
SEC EDGAR filings, XBRL financials, Form 4 insider trades + state business-entity registry lookups
Install
Add this to your client's MCP configuration (Claude Desktop: claude_desktop_config.json; Cursor: .cursor/mcp.json; VS Code and others use the same "mcpServers" shape).
claude mcp add --transport http edgar-entity-us-company-research 'https://edgar-entity-mcp.vercel.app/mcp'
{
"mcpServers": {
"edgar-entity-us-company-research": {
"type": "http",
"url": "https://edgar-entity-mcp.vercel.app/mcp"
}
}
}
edgar-entity-mcpclaude mcp add edgar-entity-us-company-research -e 'SEC_USER_AGENT=<SEC_USER_AGENT>' -e 'SOCRATA_APP_TOKEN=<your socrata_app_token>' -e 'UPSTASH_REDIS_REST_URL=<UPSTASH_REDIS_REST_URL>' -e 'UPSTASH_REDIS_REST_TOKEN=<your upstash_redis_rest_token>' -- npx -y edgar-entity-mcp@0.1.0
{
"mcpServers": {
"edgar-entity-us-company-research": {
"command": "npx",
"args": [
"-y",
"edgar-entity-mcp@0.1.0"
],
"env": {
"SEC_USER_AGENT": "<SEC_USER_AGENT>",
"SOCRATA_APP_TOKEN": "<your socrata_app_token>",
"UPSTASH_REDIS_REST_URL": "<UPSTASH_REDIS_REST_URL>",
"UPSTASH_REDIS_REST_TOKEN": "<your upstash_redis_rest_token>"
}
}
}
}
Environment variables
SEC_USER_AGENTrequired Required by SEC fair-access policy: '<Name or Company> <email>'SOCRATA_APP_TOKENsecret Optional Socrata app token for higher state-registry rate limitsUPSTASH_REDIS_REST_URLOptional Upstash Redis REST URL for a shared cacheUPSTASH_REDIS_REST_TOKENsecret Optional Upstash Redis REST token
Documentation
From the project's README on GitHub, refreshed weekly. Links open on GitHub.
Contents
edgar-entity-mcp
Source: https://github.com/pgeneralpl/edgar-entity-mcp · Issues: https://github.com/pgeneralpl/edgar-entity-mcp/issues · npm: https://www.npmjs.com/package/edgar-entity-mcp · MCP Registry: io.github.pgeneralpl/edgar-entity-mcp
US company research for AI agents, from official public data. One MCP server that covers SEC EDGAR (filings, XBRL financials, Form 4 insider trades, cross-company comparisons) and state Secretary of State business-entity registries (NY, CO, OR, CT, PA). Responses are compact JSON with source URLs, sized to keep token use low.
- Analysts: "Compare TSLA, F and GM revenue for CY2023", "What did insiders sell at Apple this month?", "Summarize the latest 10-K risk factors."
- Founders / sales / diligence: "Is Acme Widgets LLC an active Colorado entity? Who is its registered agent?"
- Researchers: clean 10-K/10-Q sections as text, normalized financial time series with the XBRL tag used for each metric.
No API keys are needed for the data. SEC asks you to identify yourself with a User-Agent (see SEC User-Agent).
npx -y edgar-entity-mcp # stdio MCP server
Tools
| Tool | What it does | Source |
|---|---|---|
search_company | Ticker/name → CIK matches, fuzzy-ranked (share classes merged) | company_tickers_exchange.json |
get_company_profile | Name, tickers, exchanges, SIC + industry, state of incorporation, FYE, EIN, addresses, former names, latest 10-K/10-Q | data.sec.gov/submissions |
list_filings | Filings by form type + date range, with accession, dates, primary-document and index URLs; reads older history pages when needed | data.sec.gov/submissions |
get_filing_document | Primary document converted to clean text (tables kept as rows); 10-K/10-Q item extraction (1, 1A, 7, 7A, 8…); max_chars/offset paging | www.sec.gov/Archives |
get_financials | Normalized revenue, gross profit, operating income, net income, EPS (basic/diluted), assets, liabilities, equity, cash, long-term debt, operating cash flow, capex, FCF, shares outstanding; annual or quarterly; deduped by fiscal period; reports the tag used | XBRL companyfacts |
get_xbrl_concept | Full history of any XBRL concept/unit for one company | XBRL companyconcept |
compare_companies | One concept across all filers for a calendar period (rank or filter by tickers) | XBRL frames |
get_insider_trades | Form 4/4A parsed: reporter, role/title, code + plain meaning, shares, price, value, A/D, owned after, derivative details; buy/sell summary | Form 4 XML |
lookup_business_entity | Search SoS registries by name or entity id; normalized status, type, formation date, jurisdiction, addresses, registered agent | NY/CO/OR/CT/PA open data (Socrata) |
list_supported_states | Coverage, dataset ids, update cadence and license per state | — |
Also included: a company_research_brief prompt that runs the whole workflow, and an edgar-entity://data-sources resource (licenses).
Every tool takes company as a ticker (AAPL), CIK (320193) or unambiguous name. Errors come back as {"error":{"code","message","hint"}} with isError: true. Ambiguous names return candidate CIKs.
Example calls and real outputs (live data, trimmed)
get_financials {"company":"AAPL","limit":1}
{"cik":"0000320193","name":"Apple Inc.","period_type":"annual","currency":"USD",
"periods":[{"fy":2025,"fp":"FY","period_end":"2025-09-27","period_start":"2024-09-29","form":"10-K","filed":"2025-10-31",
"accession":"0000320193-25-000079","revenue":416161000000,"gross_profit":195201000000,"operating_income":133050000000,
"net_income":112010000000,"eps_basic":7.49,"eps_diluted":7.46,"operating_cash_flow":111482000000,"capex":12715000000,
"total_assets":359241000000,"total_liabilities":285508000000,"stockholders_equity":73733000000,"cash":35934000000,
"long_term_debt":78328000000,"shares_outstanding":14776353000,"free_cash_flow":98767000000}],
"tags_used":{"revenue":["us-gaap:RevenueFromContractWithCustomerExcludingAssessedTax"],"net_income":["us-gaap:NetIncomeLoss"],"...":[]},
"source_url":"https://data.sec.gov/api/xbrl/companyfacts/CIK0000320193.json"}
period_type:"quarterly" returns 3-month values. 10-Q cash-flow lines are year-to-date, so quarters are derived by differencing, and Q4 is derived as FY minus 9M. For example, MSFT FY25 Q4 revenue comes out at $76.441B, which matches the reported figure. Each derived value is listed in notes.
get_insider_trades {"company":"TSLA","limit":5}
{"cik":"0001318605","name":"Tesla, Inc.","filings_returned":5,
"summary":{"open_market_buys":{"count":0,"shares":0,"value":0},"open_market_sales":{"count":19,"shares":34620.25,"value":13321733}},
"code_legend":{"F":"Shares withheld to pay exercise price or taxes","M":"Exercise or conversion of derivative (exempt)","S":"Open market or private sale"},
"filings":[{"form":"4","filed":"2026-09-09","accession":"0001104659-26-106432","reporter":"Taneja Vaibhav","roles":"Officer",
"officer_title":"Chief Financial Officer","transactions":[
{"security":"Common Stock","date":"2026-09-08","code":"S","shares":2605.75,"price":360.134,"value":938419.17,
"acquired_disposed":"D","shares_owned_after":25972.25,"ownership":"D"}],
"url":"https://www.sec.gov/Archives/edgar/data/1318605/000110465926106432/tm2625055d1_4seq1.xml"}],"has_more":true}
get_filing_document {"url":"https://www.sec.gov/Archives/edgar/data/320193/000032019325000079/aapl-20250927.htm","section":"1A","max_chars":3000}
{"url":"…/aapl-20250927.htm","kind":"html","section":{"id":"1A","title":"Risk Factors"},"total_chars":68118,"offset":0,"next_offset":3000,
"text":"Item 1A. Risk Factors\nThe following summarizes factors that could have a material adverse effect on the Company’s business…"}
compare_companies {"tag":"Revenues","period":"CY2023","companies":["TSLA","F","GM"]}
{"concept":"us-gaap:Revenues","unit":"USD","period":"CY2023","companies_reporting":2679,"results":[
{"rank":12,"cik":"0000037996","name":"Ford Motor Co","val":176191000000,"end":"2023-12-31"},
{"rank":13,"cik":"0001467858","name":"GENERAL MOTORS COMPANY","val":171842000000,"end":"2023-12-31"},
{"rank":23,"cik":"0001318605","name":"Tesla, Inc.","val":96773000000,"end":"2023-12-31"}],
"source_url":"https://data.sec.gov/api/xbrl/frames/us-gaap/Revenues/USD/CY2023.json"}
Balance-sheet (instant) concepts need an I suffix, e.g. {"tag":"Assets","period":"CY2024Q4I"}.
lookup_business_entity {"name":"Newmont Corporation","states":["CO"]}
{"results":[{"state":"CO","entity_id":"20021056790","name":"Newmont Corporation","status":"Good Standing",
"type":"Foreign Profit Corporation (FPC)","formed":"2002-03-08","jurisdiction":"DE",
"principal_address":"6900 East Layton Ave, Suite 700, Denver, CO, 80237, US",
"registered_agent":{"name":"CORPORATION SERVICE COMPANY","address":"1900 W Littleton Blvd, Littleton, CO, 80120"},
"details_url":"https://www.coloradosos.gov/biz/BusinessEntityDetail.do?masterFileId=20021056790",
"source_url":"https://data.colorado.gov/resource/4ykn-tg5h.json?entityid=20021056790"}],
"by_state":{"CO":{"count":1,"query_url":"…"}}}
Other quick examples:
search_company {"query":"berkshire"}→BERKSHIRE HATHAWAY INC, CIK0001067983, tickersBRK-B, BRK-Alist_filings {"company":"AAPL","forms":["10-K"],"date_to":"2010-12-31","limit":2}→ the 2010 10-K and 10-K/A, read from older history pagesget_xbrl_concept {"company":"TSLA","tag":"ResearchAndDevelopmentExpense","forms":["10-K"],"framed_only":true}lookup_business_entity {"entity_id":"0783036","states":["CT"]}→ THE TRAVELERS COMPANIES, INC. (Active, foreign stock corp, MN)
Install
Three ways to connect: the hosted endpoint https://edgar-entity-mcp.vercel.app/mcp (no install, see Remote below), the npm package edgar-entity-mcp over stdio (Node.js ≥ 22.23.4 or ≥ 24.22.1), or any client that installs from the official MCP Registry entry io.github.pgeneralpl/edgar-entity-mcp.
Quick check: npx -y edgar-entity-mcp --version
Node.js version (stdio users): run the package on a patched Node.js — 22.23.4+ (22.x) or 24.22.1+ (24.x, recommended), or 26.12.1+. These releases (2026-10-21) fix CVE-2026-111497, a Brotli decoder out-of-bounds write that a malicious server response decoded by
fetch()could trigger, plus TLS hostname-verification bypasses. Node.js 20 is end-of-life and will not get the fix. Check withnode -v; the server prints a warning on stderr when started on an unpatched Node.js. The hosted endpoint runs on Vercel's managed Node.js 24.x runtime, which Vercel patches itself.engines.nodeis24.x(it pins the Vercel runtime), so npm prints anEBADENGINEnotice on 22.x — that is expected and harmless on 22.23.4+.
Claude Desktop / Claude Code / Cursor / Windsurf / VS Code (stdio via npx)
{
"mcpServers": {
"edgar-entity": {
"command": "npx",
"args": ["-y", "edgar-entity-mcp"],
"env": { "SEC_USER_AGENT": "Your Name or Company you@example.com" }
}
}
}
- Claude Desktop:
~/Library/Application Support/Claude/claude_desktop_config.json(macOS) or%APPDATA%\Claude\claude_desktop_config.json(Windows). - Cursor:
~/.cursor/mcp.json(global) or.cursor/mcp.json(project). - Claude Code:
claude mcp add edgar-entity -e SEC_USER_AGENT="Your Name you@example.com" -- npx -y edgar-entity-mcp - VS Code:
.vscode/mcp.jsonusing"servers"in place of"mcpServers".
Remote (Streamable HTTP)
Hosted endpoint (free, no key needed): https://edgar-entity-mcp.vercel.app/mcp · health: https://edgar-entity-mcp.vercel.app/health. The hosted instance uses Upstash Redis for a shared cache, a fleet-wide SEC budget of 6 req/s, and per-client quotas (see Hosted quotas).
{ "mcpServers": { "edgar-entity": { "url": "https://edgar-entity-mcp.vercel.app/mcp" } } }
For clients that only speak stdio: "command": "npx", "args": ["-y", "mcp-remote", "https://edgar-entity-mcp.vercel.app/mcp"]. With an API key, send Authorization: Bearer <key> or x-api-key: <key> (mcp-remote: --header "Authorization: Bearer <key>").
Self-host the HTTP server
npm run build && PORT=3000 SEC_USER_AGENT="Acme you@acme.com" npm run start:http # → http://127.0.0.1:3000/mcp
The node server binds 127.0.0.1 by default and only answers requests whose Host is localhost (DNS-rebinding protection) and whose Origin, if any, is local. To expose it, set HOST=0.0.0.0, ALLOWED_HOSTS=mcp.example.com, optionally ALLOWED_ORIGINS, and TRUST_PROXY=1 only when a reverse proxy sets X-Forwarded-For/X-Real-IP.
Deploy to Vercel
The repo already contains api/mcp.ts and api/health.ts (Vercel Functions with a Web-standard handler built on mcp-handler) and vercel.json, which runs npm run build and rewrites /mcp to /api/mcp and /health to /api/health. npm run smoke:remote -- https://<project>.vercel.app/mcp runs a live check against a deployment.
vercel linkthenvercel env add SEC_USER_AGENT(plusHEALTH_SECRET,IP_HASH_SALT, optionallySOCRATA_APP_TOKEN/MCP_API_KEYS).- Strongly recommended: add the Upstash Redis integration. The cache, the SEC/state rate budgets and the per-client quotas then become fleet-wide; without it they are per instance. In the Upstash console enable eviction (
allkeys-lru) and usage alerts. vercel deploy --prod. Point clients athttps://<project>.vercel.app/mcp.
Environment variables
| Var | Default | Purpose |
|---|---|---|
SEC_USER_AGENT | – (required) | Set this to your own "<Name or Company> <email>", e.g. "Acme Research jane@acme.com". SEC tools refuse to run (CONFIG_ERROR) while it is unset or a placeholder; state-registry tools still work. |
SEC_MAX_RPS | 6 (max 8) | SEC requests per second: per process, and fleet-wide when Redis is configured |
SOCRATA_MAX_RPS | 5 | State-portal requests per second (same two-stage limiter) |
DISTRIBUTED_RATE_LIMIT | 1 | 0 disables the Redis token buckets (per-process limits only) |
SOCRATA_APP_TOKEN | – | App token for the state portals (higher throttling limits; free at each portal's developer settings) |
UPSTASH_REDIS_REST_URL / UPSTASH_REDIS_REST_TOKEN | – | Enables Redis (Vercel's KV_REST_API_URL/KV_REST_API_TOKEN also work). Without them cache and quotas are in-memory. |
QUOTA_ANON_PER_MINUTE / QUOTA_ANON_PER_DAY | 30 / 60 | HTTP quota for anonymous clients, in units, per hashed IP (per-minute limits are enforced per instance, in memory) |
QUOTA_ANON_NETWORK_PER_DAY | 10% of the global cap (60) | Shared daily cap for all anonymous clients in one /24 (IPv6 /48), against address rotation; never more than 10% of QUOTA_ANON_GLOBAL_PER_DAY |
QUOTA_ANON_GLOBAL_PER_DAY / QUOTA_ANON_GLOBAL_HARD_PER_DAY / QUOTA_ANON_TRICKLE_PER_DAY | 600 / 900 / 10 | Soft daily cap for all anonymous clients together; past it each IP still gets the trickle allowance, up to the hard ceiling. API-key callers are not affected. |
QUOTA_KEY_PER_MINUTE / QUOTA_KEY_PER_DAY | 120 / 500 | HTTP quota per API key (per-key overrides via scripts/create-key.ts) |
SEC_KEYED_RESERVED_RPS | 2 | Part of SEC_MAX_RPS reserved for API-key callers (anonymous traffic shares the rest) |
MAX_CONCURRENT_UPSTREAM_PER_CLIENT | 4 | Concurrent upstream fetches per client (hashed IP or key); more wait, then get RATE_LIMITED |
DEGRADED_SEC_RPS | 0.2 (max 2) | Per-instance SEC rate for API-key callers while Redis is unavailable or over its plan limit; 0 = refuse SEC calls. Anonymous callers are cache-only in that state. |
MCP_API_KEYS | – | Comma-separated static keys (accepted alongside hashed keys stored in Redis) |
MCP_REQUIRE_API_KEY | – | 1 rejects anonymous HTTP calls with 401 (0.1.x required a key whenever MCP_API_KEYS was set) |
KEY_REQUEST_URL | GitHub issue link | Shown in RATE_LIMITED hints as the place to request a free key |
IP_HASH_SALT | – (required with Redis) | Secret (≥32 random chars, e.g. openssl rand -hex 32) for the IP hash. The salt actually used is HMAC-SHA256(IP_HASH_SALT, UTC date), so it rotates daily. With Redis configured and this missing, set to the old default edgar-entity-mcp, or shorter than 32 chars, the server refuses to start (node: exit 1; Vercel: every request gets 503 and /health says misconfigured). |
API_KEY_SECRET | – (required with Redis) | Secret (≥32 random chars, different from IP_HASH_SALT) used to sign API keys (eem_<id>_<HMAC>). Rotating it invalidates every minted key at once. Without Redis, minted keys are disabled. |
REVOKED_KEY_IDS | – | Comma-separated key ids (or full keys) refused immediately on every instance, without Redis — use for urgent revocation, including during degraded mode |
HEALTH_SECRET | – | Enables /health?deep=1 (Redis ping) for requests sending x-health-secret: <value> |
SUPPRESSION_FILE | – | JSON array of suppression entries (see Privacy) |
MAX_BODY_BYTES / MAX_DOCUMENT_BYTES | 65536 / 15 MB | HTTP request body cap / max filing document size |
USAGE_LOG | – | 1 logs per-call usage JSON (tool, latency, ok, response size, hashed principal) to stderr |
MEMORY_CACHE_MB / REDIS_MAX_VALUE_BYTES / HTTP_TIMEOUT_MS | 128 / 900000 / 25000 | Tuning |
HOST / ALLOWED_HOSTS / ALLOWED_ORIGINS / TRUST_PROXY | 127.0.0.1 / – / – / – | Self-hosted node server only |
Hosted quotas and API keys
The hosted endpoint charges each tool call in units that approximate how many upstream requests it can trigger:
| Tool | Units |
|---|---|
list_supported_states | 0 |
search_company, get_company_profile, get_xbrl_concept | 1 |
list_filings | 1 (6 with date filters or offset, which may page through up to 4 older-history files) |
get_financials | 2 |
compare_companies | 3 |
get_filing_document | 4 by url, 10 by company + accession |
get_insider_trades | 6 + limit (max 20 filings → 26) |
lookup_business_entity | number of states (max 5) |
| Client | Per minute | Per day (UTC) |
|---|---|---|
| Anonymous (per IP, IPv6 per /64) | 30 (per instance) | 60 |
| All anonymous clients of one network (IPv4 /24, IPv6 /48) | – | 60 (10% of the global cap) |
| All anonymous clients together | – | 600 soft; then 10 per IP, up to 900 hard |
| Free API key | 120 (per instance) | 500 |
Reserve, then refund. The table above is the estimate reserved before a call. After the call the server charges the actual cost — the number of upstream requests it made, at least 1 — and refunds the rest. A call answered from cache costs 1 unit, so cheap cached calls cannot exhaust the shared anonymous budget. Refunds are kept locally and folded into the next counter update, so they cost no Redis commands.
Over-quota calls return a RATE_LIMITED tool error with retry_after_seconds, the scope (minute, day, network, global), the limit, and how to get a key. Running locally via npx -y edgar-entity-mcp has no quota.
Fair share of the upstream budget: SEC_KEYED_RESERVED_RPS (2 of the 6 SEC requests/s by default) is reserved for API-key callers, and each client may have at most MAX_CONCURRENT_UPSTREAM_PER_CLIENT (4) upstream fetches in flight. The SEC token bucket refills smoothly (one token every ~167 ms at 6 rps, burst 2).
API keys
Free keys: open an issue at github.com/pgeneralpl/edgar-entity-mcp. Keys look like eem_<12-char id>_<24-hex HMAC> and are self-verifying: the server checks the HMAC (keyed with API_KEY_SECRET) locally, so random or forged keys are rejected without touching Redis, and a client sending more than 10 bad keys in 10 minutes gets HTTP 429. A valid key is then looked up once (eem:v1:apikey:<id>, to honour deletion and per-key limits) and remembered for 5 minutes.
- Mint:
npx tsx scripts/create-key.ts [--per-day N] [--per-minute N]. The key is printed once. The record holds only the creation date and optional limits: no owner name, email or other contact is collected (the key request lives in the issue tracker; note the key id there if you may need it later). - Delete:
npx tsx scripts/delete-key.ts <key-or-id>removes the record and all of the key's live counters; running instances stop accepting it within 5 minutes. For immediate revocation everywhere (also while Redis is unavailable), add the id toREVOKED_KEY_IDS. RotatingAPI_KEY_SECRETrevokes every key at once. - Both scripts need the deployment's
UPSTASH_REDIS_REST_*,IP_HASH_SALTandAPI_KEY_SECRET. include_agent_detailsonlookup_business_entityonly takes effect for API-key callers (see Privacy).
Fitting the Upstash free plan (and what happens when it runs out)
Upstash bills every command, including each redis.call inside a Lua script. The counts below are billed commands, measured with an exact emulator of the scripts in tests/budget.test.ts (and the REST-visible part checked against real Upstash by scripts/live-upstash.ts):
| Event | Billed commands |
|---|---|
Quota check, anonymous, allowed (EVALSHA + INCRBY on IP-day, /24-day, global-day) | 4 (+1 EXPIRE per counter the first time it is created that day: up to 7) |
Quota check, API key, allowed (EVALSHA + INCRBY) | 2 (+1 the first call of the day) |
| Refused because a known counter has no room for this call's cost (own day, /24, global) | 0 |
| Refused inside the script (counter filled by another instance) | anonymous 7, key 3 (INCRBY + DECRBY rollback), then 0 |
| Refund of an unused estimate | 0 (folded into the next INCRBY) |
| Per-minute limit | 0 (in memory) |
Random / forged API key, or key in REVOKED_KEY_IDS | 0 |
| MAC-valid key, first use (then memoised 5 min) | 1 (GET) |
SEC / state-portal request, fleet token granted (EVALSHA + HMGET + HSET + PEXPIRE) | 4 per upstream request; keyed callers whose reserved bucket is empty: 2 + 4 |
| Shared cache: L1 (process memory) hit / Redis hit / miss + store | 0 / 1 / 2 |
| Any call in degraded mode | 0 |
Whole call: warm search_company (process cache) | 4 |
| Whole call: cold call with 1 SEC request (quota 4, cache GET + SET 2, SEC token 4) | 10 (13 on the first call of the day) |
Worst case per UTC day with the defaults: anonymous traffic is bounded by the 900-unit hard ceiling (≤ 900 calls, ≤ ~900 upstream requests): about 900 × 4 + 900 × 6 ≈ 9K commands. Each API key at 500 units/day adds at most ≈ 4K. Upstash's free tier (about 500K commands a month, roughly 16K a day) therefore covers the anonymous tier plus a couple of active keys; size QUOTA_ANON_GLOBAL_PER_DAY and QUOTA_KEY_PER_DAY to your plan. When the plan does run out, the server degrades instead of failing:
Degraded mode. If Redis errors, times out or reports its plan limit (max requests limit), the instance switches for 60 s at a time to:
- local in-memory quotas (same daily limits, per instance);
- anonymous callers: cache only — anything not already in this instance's memory is refused with a clear
RATE_LIMITED(scope: "degraded") message; - API-key callers: SEC at
DEGRADED_SEC_RPS(0.2/s) per instance (0= refuse); MAC-valid keys are accepted without the Redis lookup but treated as unverified (noinclude_agent_details); keys inREVOKED_KEY_IDSare still refused; - state-registry lookups refused if the suppression list was never loaded on that instance.
/health reports status: "degraded" with the reason.
Other limits on the HTTP endpoint: 64 KB request bodies, no JSON-RPC batches (one message per request), at most 20 Form 4 filings per insider call and 5 states / 25 results per state per entity lookup, filing documents up to 15 MB.
Privacy and personal data
- Registered agents and service-of-process contacts are treated as individuals unless the name ends with a legal-form suffix (Inc, Corp, LLC, LLP, LP, Ltd, Company, "& Co", Bank, N.A., …) or is a known registered-agent service (CT Corporation, CSC, Cogency Global, …). "JOHN CHURCH", "JUDE LAW", "DAVID CO" and "JOHN SMITH PA" are individuals; ambiguous CO/PA/PC endings count as individual.
- Individual agents are returned as
{ "kind": "individual", "withheld": true }, and the entity's principal/mailing addresses (and NY location name) are withheld too (addresses_withheld: true), since for one-person businesses these are often a home address.include_agent_details: truereveals them only for API-key callers (and local stdio use); anonymous HTTP callers get a privacy note instead. Organizational agents are always shown. - Erasure/objection requests: add
entity:<STATE>:<id>oragent:<name>withnpx tsx scripts/suppress.ts add ...(Redis set, picked up within 10 minutes),SUPPRESSION_FILE, orsrc/states/suppression-list.ts. Entity ids are normalized (case, punctuation, leading zeros). A suppressed name is matched as whole words in agent names and addresses and in the entity name, NY location name and principal/mailing addresses; any hit removes every personal field of that record, and a hit in the entity name (also matched with spaces removed, e.g. "JANEDOE HOLDINGS LLC") replaces the name with[withheld]. If Redis is configured but the suppression list cannot be loaded on a cold instance, state lookups are refused (before any registry request) rather than served without it. - Form 4 reporting-owner names are statutory public disclosures and are returned by
get_insider_trades.
What the hosted server stores, and for how long
Rate limiting keeps only integer counters in Redis, under hashed identifiers, with a TTL equal to the window being enforced:
| Redis key | Value | TTL |
|---|---|---|
eem:v1:q:d:ip:<hash>:<YYYYMMDD> | units used today (UTC) by one IP (IPv6: /64) | 25 h (the day + 1 h slack) |
eem:v1:q:d:net:<hash>:<YYYYMMDD> | units used today by all anonymous clients of one /24 (IPv6: /48) | 25 h |
eem:v1:q:d:anon:<YYYYMMDD> | units used today by all anonymous clients together | 25 h |
eem:v1:q:d:key:<id>:<YYYYMMDD> | units used today by one API key | 25 h |
<hash>for IPs isHMAC-SHA256(daily salt, IP or prefix)truncated to 80 bits, where the daily salt isHMAC-SHA256(IP_HASH_SALT, YYYYMMDD). Hashes from different days can't be linked, and once a day's counters expire nothing derived from that day's IPs is left. A request is hashed and counted against the same UTC day, so requests that straddle midnight are counted consistently. The per-minute counter for an IP restarts at midnight because its hash changes, so a client can get at most one extra minute's worth of quota at that moment.- For keys,
<id>is the key's public id (the secret part never reaches Redis). API key records (eem:v1:apikey:<id>) hold only the creation date and optional per-key limits, until deleted withscripts/delete-key.ts(which also deletes the key's counters). The suppression set (eem:v1:suppress) holds erasure-request entries. - Not stored anywhere in Redis: raw IPs, user agents, request paths, tool arguments, per-tool usage history. Shared cache entries for public SEC and state data are stored under
eem:v1:c:<sha256 of the upstream request>, so key names don't reveal what was searched. The upstream fan-out budget (eem:rl:*) is a global token bucket with no per-client data. - Logs: errors (and, with
USAGE_LOG=1, one line per call with tool name, latency, outcome, response size and the daily-rotating principal hash) go to stderr, then to Vercel runtime logs, which expire under Vercel's plan retention (limits). Error text has URL query strings, IP addresses, email addresses and credential-like values masked. Tool arguments, API keys and raw IPs are never logged by this server. Vercel's own platform request logs (outside this code) record request metadata such as path and client IP under Vercel's retention. - Per-minute limits are kept in process memory only (70 s) and never written to Redis.
/healthreports this configuration underquotas.state.
Required configuration before resuming the hosted deployment
Set these in the Vercel project (Production and Preview) before taking the deployment out of pause. With Redis configured, the server refuses to serve (HTTP 503, /health → misconfigured) until IP_HASH_SALT and API_KEY_SECRET are valid.
| Variable | Value |
|---|---|
SEC_USER_AGENT | "<Operator name> <contact email>" (SEC fair-access policy) |
UPSTASH_REDIS_REST_URL + UPSTASH_REDIS_REST_TOKEN (or Vercel's KV_REST_API_URL + KV_REST_API_TOKEN) | the Upstash database |
IP_HASH_SALT | ≥ 32 random chars (openssl rand -hex 32) |
API_KEY_SECRET | ≥ 32 random chars, different from IP_HASH_SALT |
HEALTH_SECRET | random; enables /health?deep=1 |
SOCRATA_APP_TOKEN | free app token from the state portals (higher throttling limits) |
KEY_REQUEST_URL | where users request a free key (default: GitHub issue link) |
DEGRADED_SEC_RPS | 0.2 (or 0 to stop SEC calls entirely while Redis is out) |
MCP_REQUIRE_API_KEY | optional: 1 to disable anonymous use |
Also recommended: Upstash usage alerts, and REVOKED_KEY_IDS kept empty unless needed. Mint keys only after API_KEY_SECRET is final (rotating it invalidates them).
Security notes
- Filing text from
get_filing_documentis untrusted, filer-authored content. It is returned between<<<BEGIN UNTRUSTED SEC FILING TEXT id=<nonce>>>>/<<<END UNTRUSTED SEC FILING TEXT id=<nonce>>>>markers, where the nonce is random per response and repeated inuntrusted_marker_id, so filer text cannot fake a closing marker. Zero-width, bidi control, Unicode tag characters (U+E0000–E007F) and variation selectors (U+FE00–FE0F, U+E0100–E01EF) are stripped. Other third-party text (entity names and addresses, Form 4 reporter names, remarks and footnotes, XBRL labels and descriptions) is cleaned the same way and listed in each response'suntrusted_fields. Agents should treat all of it as data, never as instructions. - Only
https://www.sec.gov/Archives/edgar/data/<CIK>/<accession>/<file>URLs are accepted as document input; redirects are followed manually and re-checked against the host allowlist. - Error messages returned to clients are generic; details go to server logs with a reference id.
SEC User-Agent and rate limits
SEC's fair-access policy caps clients at 10 requests/second and requires a declared User-Agent. SEC may block clients without one (HTTP 403 "Undeclared Automated Tool"). This server:
- sends
SEC_USER_AGENTon every SEC request; - spaces requests through a two-stage limiter: per-process spacing plus, with Redis, atomic fleet-wide token buckets (6 req/s default, 2 of them reserved for API-key callers; smooth refill, burst 2). If no token is granted within ~5 s the call fails with
RATE_LIMITED; if Redis itself is unavailable or over its plan, anonymous callers are served from cache only and API-key callers at 0.2 req/s per instance (degraded mode); - retries 429/5xx with exponential backoff and respects
Retry-After; - deduplicates identical in-flight requests and caches with TTLs: tickers 24h, submissions 10 min, companyfacts/concepts 6h, frames 12h, filing documents 30 days in process memory (raw documents are never written to Redis; processed text under 256 KB and parsed Form 4s are shared for 7 days), state searches 1h.
Monetization hooks (no billing yet)
- HTTP gate: self-verifying keys registered in Redis (
scripts/create-key.ts/scripts/delete-key.ts),MCP_API_KEYSstatic keys, orcreateHttpHandler({ verifyKey: async (k) => principalOrNull })for keys stored elsewhere. Invalid keys get 401. - Per-call metering:
setUsageHooks({ beforeCall, afterCall })(exported from the package).beforeCallreceives{tool, transport, apiKey, clientId, principal, args}; throwing aToolErrorrejects the call with that code (anything else becomesUNAUTHORIZED). The built-in quota hook runs first.afterCallreceives{ok, ms, errorCode, responseChars}, which is enough for pay-per-call or per-token pricing. - Suggested tiers: free (stdio or keyless HTTP, cached), Pro (API key, higher limits, bulk/paginated exports), Team (webhooks for new filings or insider trades).
Data sources & licenses
The code is MIT-licensed. The server ships no data and fetches everything live. Full details are in DATA_SOURCES.md and LICENSES/.
| Source | Dataset | Coverage | License (commercial use) |
|---|---|---|---|
| SEC EDGAR | submissions, XBRL APIs, Archives | All EDGAR filers | US gov public information; SEC policy (yes) |
| New York DOS | n9v6-gdp6 Active Corporations | Active entities only, monthly | OPEN-NY Terms (yes) |
| Colorado SOS | 4ykn-tg5h Business Entities | All entities + status, daily | Public Domain + CO ToS disclaimer (yes) |
| Oregon SOS | tckn-sxa6 Active Businesses | Active only, weekly | Public domain by statute, ORS 276A.368 (yes) |
| Connecticut SOTS | n7gp-d28j Business Master | All + status, daily | Public Domain, CT terms (yes) |
| Pennsylvania DOS | xvd7-5r2c Registered Businesses | Current registrations, monthly | Public Domain U.S. Government (yes) |
Colorado disclaimer: The data made available here has been modified for use from its original source, which is the State of Colorado. THE STATE OF COLORADO MAKES NO REPRESENTATIONS OR WARRANTY AS TO THE COMPLETENESS, ACCURACY, TIMELINESS, OR CONTENT OF ANY DATA MADE AVAILABLE THROUGH THIS SITE. THE STATE OF COLORADO EXPRESSLY DISCLAIMS ALL WARRANTIES, WHETHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. The data is subject to change as modifications and updates are complete. It is understood that the information contained in the Web feed is being used at one's own risk.
Delaware, the most common state of incorporation, has no open bulk dataset, so it is not covered. CA and FL are on the roadmap (see list_supported_states). This server provides informational data only and is not investment, legal or credit advice. It is not a consumer reporting agency, and its output must not be used for FCRA-regulated decisions.
Architecture
src/
core/ config, cache (memory LRU | Upstash | tiered), rate limiter, fetch+retry, usage hooks, errors
sec/ tickers (fuzzy), submissions, documents (HTML→text, item extraction), financials, xbrl (concept/frames), insider (Form 4)
states/ registry (per-state dataset config + normalizers), socrata (SoQL builder, ranking)
tools.ts zod schemas + tool handlers (transport-agnostic)
server.ts registers tools/prompt/resource on an McpServer (shared by both transports)
stdio.ts npm bin → serveStdio()
http/ handler.ts (mcp-handler + API-key auth) · node-server.ts (local/self-host)
api/mcp.ts Vercel Function
Built on the official MCP TypeScript SDK v2 (@modelcontextprotocol/server, the successor to the @modelcontextprotocol/sdk v1 package, and a requirement of mcp-handler 2.x). It serves the 2026-07-28 protocol and falls back automatically for 2025-era clients, over both stdio and HTTP.
Development
npm install
npm run check # typecheck + eslint + vitest (offline unit tests)
npm run smoke:e2e # build + live smoke over stdio (all tools, real SEC/state data) + HTTP + Vercel-function e2e
npm run validate:registry # validate server.json against the MCP Registry schema
npx @modelcontextprotocol/inspector --cli node dist/stdio.js --method tools/list
Roadmap
- More states: FL (Sunbiz bulk files), CA (bizfile API, needs a key), WA/TX where open data allows; NY inactive entities (the "All Filings" dataset)
- Officers/directors (CT principals dataset; PA officer rows; DEF 14A)
- EDGAR full-text search (
efts.sec.gov) and 8-K item-level event feed - 13F holdings, 13D/G ownership, S-1/IPO tracking
- Webhooks/subscriptions for new filings and insider trades (MCP events)
- Segment and geographic revenue (XBRL dimensions via the Financial Statement datasets)
- MCPB bundle for one-click Claude Desktop install (a
manifest.jsonis included and validated) - Hosted plan: API keys, quotas and usage dashboard (the hooks already exist)
License
MIT for the code (see LICENSE). Data remains subject to each source's terms (see DATA_SOURCES.md).
Frequently asked questions
What is EDGAR + Entity: US Company Research?
SEC EDGAR filings, XBRL financials, Form 4 insider trades + state business-entity registry lookups
How do I install EDGAR + Entity: US Company Research?
It runs as a remote server. In Claude Code: claude mcp add --transport http edgar-entity-us-company-research 'https://edgar-entity-mcp.vercel.app/mcp'. For other clients, paste the JSON configuration from this page into their MCP settings.
Does EDGAR + Entity: US Company Research need an API key?
Yes. It expects SEC_USER_AGENT, SOCRATA_APP_TOKEN, UPSTASH_REDIS_REST_TOKEN, which you set in its configuration.
Is EDGAR + Entity: US Company Research open source?
Yes. Its code is on GitHub under the MIT license.
Who maintains EDGAR + Entity: US Company Research?
It is published by pgeneralpl. AI.tn lists it but does not maintain or endorse it.
From the official MCP Registry. Listing is not an endorsement: review a server's code and permissions before giving it access to your data.