loading…
Search for a command to run...
loading…
An MCP server that exposes 16 free investment-research signals (insider trades, SEC filings, short data, and live quotes) to any MCP-compatible LLM.
An MCP server that exposes 16 free investment-research signals (insider trades, SEC filings, short data, and live quotes) to any MCP-compatible LLM.
npm version npm downloads CI License: MIT Buy Me a Coffee
An MCP server that exposes 16 free-data investment-research signals to any MCP-compatible LLM client — Form 4 insider trades, SEC corporate-event filings, FINRA / SEC short data, and live Yahoo Finance quotes. Drop it into your MCP client (Cursor, Claude Desktop, VS Code, Claude Code, Codex, etc.) and your LLM can query these signals directly during long research sessions, without burning context on web browsing.
The server exposes 16 tools across four free public data sources: OpenInsider (Form 4 insider trades), SEC EDGAR (8-K material events, late-filing notices, 13D activist filings, S-3 / 424B5 dilution), FINRA / SEC (short interest, daily short volume, failures-to-deliver), and Yahoo Finance (live quote: price, valuation, dividend, earnings calendar).
The server is positioned as a pure data layer: no scoring, no compositing, no editorialization. Each tool returns clean, well-typed observations with citations and gotchas baked into the tool descriptions. The orchestrator LLM decides what is significant.
What it won't do: recommend buys / sells, combine signals into a score, run scheduled jobs, or persist anything between sessions. The MCP gives your LLM raw observations; you and the LLM reason from there.
Be polite. This scrapes a free public site and uses public SEC / FINRA / Yahoo endpoints. The server identifies itself with a
User-Agent(override viaOPENINSIDER_MCP_UAenv var) and caches every response in memory for 5 minutes (60 seconds for Yahoo Finance quotes, which are time-sensitive). Repeated queries in a research session don't re-fetch.
OpenInsider MCP is a stdio server distributed on npm — pick your client below.
Install in Cursor Install in VS Code
claude mcp add openinsider -- npx -y openinsider-mcp
Edit ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):
{
"mcpServers": {
"openinsider": {
"command": "npx",
"args": ["-y", "openinsider-mcp"]
}
}
}
Restart Claude Desktop after saving.
codex mcp add openinsider -- npx -y openinsider-mcp
Or edit ~/.codex/config.toml directly:
[mcp_servers.openinsider]
command = "npx"
args = ["-y", "openinsider-mcp"]
Most clients share the same generic stdio config — paste this into your client's MCP config (path varies by client; check their docs):
{
"mcpServers": {
"openinsider": {
"command": "npx",
"args": ["-y", "openinsider-mcp"]
}
}
}
That's it — npx fetches and runs the server on demand.
Quick start. Once installed, ask your LLM something like "What's recent insider activity at NVDA?" — it calls search_by_ticker and returns the last 90 days of Form 4 filings. Follow up with "Why did Mark Stevens sell on March 20?" and your LLM pulls the relevant 8-K via recent_sec_filings. The in-memory cache makes follow-ups instant.
You don't call these tools directly — your LLM does, based on what you ask. Representative prompts:
Single-source:
search_by_tickercluster_buysrecent_sec_filings (8-K item codes)dilution_filings (S-3 / 424B5 takedowns)short_interest (with delta and pctOfFloat)daily_short_volume (daily flow, not standing position)get_quote (price + valuation + dividend in one call)Multi-tool (the LLM composes these automatically):
short_interest + failures_to_deliver + search_by_tickerrecent_sec_filings + dilution_filings + search_by_tickerget_quote (P/E, market cap) + search_by_ticker (recent insider buys/sells)The more specific the question, the better the tool selection. See the Quick reference table below for the full mapping.
| Question | Tool | Output type |
|---|---|---|
| Insider trades for one company | search_by_ticker |
Trade[] |
| One insider's trades across companies | search_by_insider |
Trade[] |
| Market-wide insider firehose | latest_trades |
Trade[] |
| Biggest insider buys/sells per period | top_buys, top_sells |
Trade[] |
| Multiple insiders buying same name | cluster_buys |
Trade[] (with industry, insiderCount) |
| Officer buys ≥$25k | officer_buys |
Trade[] |
| Custom multi-filter screener | screen |
Trade[] |
| Recent 8-K material events | recent_sec_filings |
EdgarFiling[] |
| NT-10K / NT-10Q late-filing notices | late_filings |
EdgarFiling[] |
| Schedule 13D activist filings | activist_filings |
EdgarFiling[] |
| S-3 / 424B5 dilution / shelf takedowns | dilution_filings |
EdgarFiling[] |
| Bi-monthly short interest snapshot + delta + % of shares | short_interest |
ShortSnapshot[] |
| Daily short-sale flow (different from above!) | daily_short_volume |
{date, shortVolume, totalVolume, shortRatio}[] |
| SEC failures-to-deliver + Reg SHO threshold list | failures_to_deliver |
{date, ftdShares, ftdValue, onThresholdList}[] |
| Live stock quote (price, valuation, dividend, earnings) | get_quote |
Quote |
Each tool returns JSON with a typed payload. OpenInsider tools return { count, trades: Trade[] }; SEC EDGAR tools return { count, filings: EdgarFiling[] }; FINRA / SEC short-data tools return { count, snapshots: ShortSnapshot[] } or { count, rows: ... } depending on cadence; Yahoo Finance returns a single Quote object (no array, no count). See output types below for the exact field shapes.
search_by_tickerAll insider trades for one company.
| Param | Type | Required | Notes |
|---|---|---|---|
ticker |
string | yes | Stock symbol, e.g. "NVDA". Case-insensitive. |
daysBack |
int | no | Only return trades filed within the last N days. |
Example call:
{ "ticker": "NVDA", "daysBack": 90 }
search_by_insiderAll trades by a specific insider across all companies, identified by their SEC CIK.
| Param | Type | Required | Notes |
|---|---|---|---|
cik |
string | yes | Numeric SEC CIK of the insider. See Finding a CIK below. |
daysBack |
int | no | Filter by filing date. |
Example call:
{ "cik": "1214156", "daysBack": 365 }
latest_tradesMost recent insider filings across the whole market — the live firehose.
| Param | Type | Notes |
|---|---|---|
daysBack |
int (optional) | |
transactionType |
"all" | "buys" | "sells" (optional) |
"buys" keeps only P- rows, "sells" keeps S- rows. |
Use when: you want a market-wide pulse. Pair with daysBack: 1 to see what was filed today.
top_buysLargest insider purchases by dollar value over a fixed window.
| Param | Type | Notes |
|---|---|---|
period |
"day" | "week" | "month" | "quarter" | "year" |
Required. |
Example call:
{ "period": "week" }
top_sellsLargest insider sales by dollar value over a fixed window. Same period argument as top_buys.
Note: most large sales are routine 10b5-1 plans or option exercises. The signal in
top_sellsis usually pattern (multiple insiders, late filings) more than absolute size.cluster_buyshas historically been the higher-signal page.
cluster_buysCompanies where multiple insiders bought stock in a short window. The strongest open-market signal on OpenInsider.
| Param | Type | Notes |
|---|---|---|
daysBack |
int (optional) |
Trades returned by this tool include two extra fields: industry (SIC industry name) and insiderCount (how many insiders are clustered).
officer_buysRecent purchases of ≥$25k by company officers (CEO, CFO, COO, etc.). Filters out 10%-owner buys, which are often institutional and lower-signal.
| Param | Type | Notes |
|---|---|---|
daysBack |
int (optional) |
screenGeneric OpenInsider screener with flexible filters. Use this when none of the named tools fit.
| Param | Type | Notes |
|---|---|---|
ticker |
string | Filter to one symbol. |
insiderCik |
string | Filter to one insider by CIK. |
daysBack |
int | |
transactionTypes |
Array<"P"|"S"|"A"|"D"|"M"> |
Applied client-side after fetch. See transaction codes. |
minTradeValue / maxTradeValue |
number (USD) | Trade value range. |
minPrice / maxPrice |
number (USD) | Per-share price range. |
isCeo, isCfo, isDirector, isOfficer, isTenPercentOwner |
boolean | Role filters. |
excludeDerivativeRelated |
boolean | Hide option-exercise / award-related rows. |
limit |
int | Max rows (capped at 1000). |
Example call — CEO open-market buys over $500k in the last 30 days:
{
"isCeo": true,
"transactionTypes": ["P"],
"minTradeValue": 500000,
"daysBack": 30
}
These four tools are ticker-scoped and source from the SEC EDGAR submissions API plus filing-body parsing. They return EdgarFiling[].
recent_sec_filingsRecent 8-K material event filings for a ticker, with parsed item codes (e.g. 1.02 contract terminated, 4.02 restatement, 5.02 officer change, 2.06 impairment).
| Param | Type | Notes |
|---|---|---|
ticker |
string | yes |
daysBack |
int (optional) | default 30 |
itemCodes |
string[] (optional) | filter to specific 8-K items, e.g. ["4.02", "5.02"] |
late_filingsNT-10K / NT-10Q late filing notices, with parsed reason text and a heuristic category (accounting / corporate / multiple / unspecified). Accounting reasons are the strongest bearish variant.
| Param | Type | Notes |
|---|---|---|
ticker |
string | yes |
daysBack |
int (optional) | default 365 |
activist_filingsSchedule 13D activist filings (initial + amendments). Returns filer name, ownership pct, and Item 4 'Purpose of Transaction' excerpt when parseable.
| Param | Type | Notes |
|---|---|---|
ticker |
string | yes |
daysBack |
int (optional) | default 365 |
includeAmendments |
bool (optional) | default true |
dilution_filingsS-3 shelf registrations and 424B5 takedowns, with parsed shelf amount and use-of-proceeds excerpt.
| Param | Type | Notes |
|---|---|---|
ticker |
string | yes |
daysBack |
int (optional) | default 365 |
These three tools are ticker-scoped and source from FINRA's CDN plus SEC bi-monthly FTD files.
short_interestFINRA bi-monthly short interest snapshots with delta vs prior period. Returns ShortSnapshot[].
| Param | Type | Notes |
|---|---|---|
ticker |
string | yes |
periodsBack |
int (optional) | default 6 (~3 months of bi-monthly periods) |
Bi-monthly cadence: settlement on 15th + last business day, published ~7 business days later — most recent snapshot may lag spot price by 1-2 weeks.
daily_short_volumeFINRA Reg SHO daily short-sale volume — daily flow (not standing position; for that, use short_interest). Aggregated across CNMS, FNRA, FNYX, FNQC venues. Returns {date, shortVolume, totalVolume, shortRatio}[].
| Param | Type | Notes |
|---|---|---|
ticker |
string | yes |
daysBack |
int (optional) | default 30 |
failures_to_deliverSEC failures-to-deliver per bi-monthly period plus current Reg SHO threshold-list flag (>10K shares + >0.5% of TSO failed for 5 consecutive settlement days). Returns {date, ftdShares, ftdValue, onThresholdList}[].
| Param | Type | Notes |
|---|---|---|
ticker |
string | yes |
periodsBack |
int (optional) | default 4 (~2 months) |
ETF FTDs are largely market-maker operational; post-T+1 settlement (May 2024) aggregate FTD volumes have decreased. Interpret with caution.
Single tool, single round-trip per ticker. Sourced by scraping the structured JSON Yahoo embeds in its public HTML quote page (the same page your browser loads at finance.yahoo.com/quote/<TICKER>/). No api keys, no auth, no third-party brokers — direct fetch from your machine to Yahoo's web frontend.
get_quoteLive stock quote for a ticker — price, previous close, 52-week range, today's volume + 3-month average volume, market cap, beta, trailing/forward P/E, dividend yield, ex-dividend date, next earnings date, currency, exchange, ISO timestamp.
| Param | Type | Required | Notes |
|---|---|---|---|
ticker |
string | yes | Stock ticker, e.g. "AAPL", "BRK.B" (normalized to "BRK-B"). Case-insensitive. Must match /^[A-Z0-9.\-]{1,10}$/ after normalization; tickers with other characters are rejected. |
Example call:
{ "ticker": "AAPL" }
The tool throws a clean ticker not found error for tickers that don't resolve, and a generic Yahoo Finance: error response for <ticker> when Yahoo returns an error (Yahoo's verbatim error text is intentionally not passed through, as a prompt-injection defense in an LLM tool surface).
OpenInsider tools return { count, trades: Trade[] }. Key fields: filingDate / tradeDate (ISO), ticker, insiderName, insiderCik, title, transactionType (SEC code + label like "P - Purchase"), price, quantity (signed), value (signed), formUrl. cluster_buys adds industry + insiderCount.
{
filingDate: string; // ISO 8601, with time, e.g. "2026-04-24T21:35:59"
tradeDate: string; // ISO 8601 date, e.g. "2026-04-22"
ticker: string;
companyName: string;
insiderName: string;
insiderCik: string | null; // SEC CIK of the insider, when present
title: string; // role(s), e.g. "CEO", "Dir", "10%", "CEO, Pres"
transactionType: string; // SEC code + label, e.g. "P - Purchase", "S - Sale+OE"
price: number | null; // $/share, null on award/grant rows
quantity: number; // signed: positive = acquired, negative = disposed
sharesOwnedAfter: number | null;
ownershipDelta: number | null; // % change in stake, signed (e.g. -10 means -10%)
value: number; // signed dollar value (matches quantity sign)
formUrl: string | null; // direct link to the SEC Form 4 XML
industry?: string; // cluster_buys only
insiderCount?: number; // cluster_buys only — how many insiders clustered
}
The transactionType string is the SEC's two-character code plus its label. Common ones:
| Code | Meaning | Signal |
|---|---|---|
P |
Open-market purchase | Strongest bullish signal — insider used real cash. |
S |
Open-market sale | Sell signal, but often diluted by 10b5-1 plans. |
S - Sale+OE |
Sale tied to an option exercise | Mostly mechanical; weaker signal than a clean S. |
A |
Grant / award | Compensation, not a trade. Ignore for sentiment. |
M |
Option exercise (no open-market component) | Mechanical. |
F |
Tax withholding | Mechanical. |
D |
Disposition (non-open-market) | Usually a transfer, not a trade. |
When filtering for "real" buying or selling activity, prefer P and unqualified S.
search_by_insider needs a numeric SEC CIK (Central Index Key). Two easy ways:
latest_trades or cluster_buys results includes insiderCik directly. Pull a few trades, grab the CIK, then call search_by_insider.In practice, when you ask your LLM something like "Has Tim Cook sold any AAPL recently?", it will look up the CIK on the web and then call search_by_insider itself — you don't need to do this manually.
The 4 EDGAR-sourced tools return { count, filings: EdgarFiling[] }. Common fields: ticker, cik (zero-padded), formType, filingDate, accessionNumber, primaryDocUrl. Each tool also populates form-specific optional fields (itemCodes for 8-K, reasonCategory for NT, filerName / pctOwned / purposeExcerpt for 13D, shelfAmount / useOfProceedsExcerpt for S-3).
{
ticker: string;
cik: string; // 10-digit zero-padded
formType: string; // "8-K", "13D", "NT-10Q", "S-3", etc.
filingDate: string; // ISO YYYY-MM-DD
acceptanceDateTime: string;
accessionNumber: string; // canonical "0000XXXXXX-YY-NNNNNN"
primaryDocUrl: string; // direct link to the filing body
// Form-specific (optional, populated by the relevant tool):
itemCodes?: string[]; // 8-K item codes
reasonText?: string | null; // NT-10K/Q narrative excerpt
reasonCategory?: "accounting" | "corporate" | "multiple" | "unspecified";
filerName?: string; // 13D
pctOwned?: number | null; // 13D, % of class
purposeExcerpt?: string | null; // 13D Item 4
isAmendment?: boolean; // 13D, S-3
shelfAmount?: number | null; // S-3, dollars
useOfProceedsExcerpt?: string | null; // S-3
}
Sample output — recent_sec_filings ticker=NVDA daysBack=180:
[
{
"ticker": "NVDA",
"cik": "0001045810",
"formType": "8-K",
"filingDate": "2026-03-06",
"acceptanceDateTime": "2026-03-06T16:11:25.000Z",
"accessionNumber": "0001045810-26-000024",
"primaryDocUrl": "https://www.sec.gov/Archives/edgar/data/1045810/000104581026000024/nvda-20260302.htm",
"itemCodes": ["5.02", "9.01"]
}
]
short_interest returns { count, snapshots: ShortSnapshot[] } — fields: ticker, reportDate, sharesShort, pctOfFloat (via SEC XBRL), daysToCover, optional delta block. daily_short_volume returns { count, rows: { date, shortVolume, totalVolume, shortRatio }[] }. failures_to_deliver returns { count, rows: { date, ftdShares, ftdValue, onThresholdList }[] }.
{
ticker: string;
reportDate: string; // ISO YYYY-MM-DD
sharesShort: number;
pctOfFloat: number | null; // sharesShort / sharesOutstanding via SEC XBRL (dei:EntityCommonStockSharesOutstanding); null when the company has no XBRL filings
daysToCover: number | null;
delta?: { // present for entries that have a prior period
sharesShortDelta: number;
pctDelta: number; // decimal, e.g. 0.12 = +12%
};
}
Sample output — short_interest ticker=NVDA periodsBack=2:
[
{
"ticker": "NVDA",
"reportDate": "2026-03-31",
"sharesShort": 280872588,
"pctOfFloat": 0.01156,
"daysToCover": 1.51,
"delta": {
"sharesShortDelta": 32531716,
"pctDelta": 0.131
}
},
{
"ticker": "NVDA",
"reportDate": "2026-02-27",
"sharesShort": 248340872,
"pctOfFloat": 0.01022,
"daysToCover": 1.31
}
]
get_quote returns a single Quote object directly (no array, no count wrapper). Always populated: identity (ticker, currency, timestamp, dataAsOf, marketState), price, previousClose, 52-week range, volume. timestamp is the time of the last actual tick (Yahoo's regularMarketTime); dataAsOf is when the MCP assembled the response — comparing the two reveals tick lag for illiquid securities or sessions outside regular hours. marketState is one of regular | pre | post | prepre | postpost | closed. Nullable in cases where the metric doesn't apply: exchange (null if Yahoo returned an unexpected exchange code), averageVolume (null for illiquid issues without a 3-month average), marketCap / beta / trailingPE / forwardPE (null for ETFs and instruments where N/A), dividendYield / exDividendDate (null for non-dividend payers), earningsDate (null when no upcoming consensus).
{
ticker: string;
exchange: string | null;
currency: string; // ISO-4217, e.g. "USD" — local for foreign tickers
timestamp: string; // ISO 8601 of regularMarketTime — last actual tick
dataAsOf: string; // ISO 8601 of when the MCP assembled this object
marketState: string; // regular | pre | post | prepre | postpost | closed
price: number;
previousClose: number;
fiftyTwoWeekHigh: number;
fiftyTwoWeekLow: number;
volume: number; // today's regular-session volume (shares)
averageVolume: number | null; // 3-month average; null for illiquid issues
marketCap: number | null;
beta: number | null; // 5y monthly
trailingPE: number | null;
forwardPE: number | null;
dividendYield: number | null; // decimal (0.0042 = 0.42%); null for non-payers
exDividendDate: string | null; // ISO YYYY-MM-DD; null for non-payers
earningsDate: string | null; // ISO YYYY-MM-DD; null when no upcoming consensus
}
daysBack filters by filing date, not trade date. Insiders have up to 2 business days to file (sometimes longer when they file late). For "what trades happened in the last week," use daysBack: 14 to be safe.P-Purchase rows are the highest signal. S-Sale+OE ("sale on option exercise") is mostly mechanical and dominates the sales firehose.value field is signed. Negative for sales, positive for buys. When summing portfolios, this gives you net flow for free.industry and insiderCount. These don't appear in the standard Trade shape — they're optional fields specific to that tool.top_sells is mostly noise. For real sell-side signal, use screen with transactionTypes: ["S"] plus role filters (e.g., isCeo: true) and a dollar threshold.daysBack, confirm the ticker exists in SEC EDGAR (some OTC names don't), or check timing — FINRA bi-monthly files lag ~7 business days after settlement, so the most recent period may not be published yet.short_interest is bi-monthly with a publication lag. FINRA settles on the 15th + last business day of each month and publishes ~7 business days later. The most recent snapshot may lag spot price by 1–2 weeks.daily_short_volume ≠ short_interest. The first is daily flow (shares sold short that day, summed across CNMS/FNRA/FNYX/FNQC venues); the second is a standing position snapshot. Numbers are not directly comparable — they measure different things.pctOfFloat is sharesShort / sharesOutstanding from SEC XBRL. Commonly conflated with "public float" in retail data feeds; true public float requires restricted-share data not available via free SEC data. Returns null when the company doesn't file XBRL or uses a non-standard tag.1.02 contract terminated, 2.02 earnings, 2.06 impairment, 4.01 auditor changed, 4.02 non-reliance / restatement, 5.02 officer/director departure or appointment.includeAmendments: false to get only the initial filing — that's the highest-impact event-day per the Brav-Jiang-Partnoy-Thomas literature.limit: 50 filings by default. recent_sec_filings, late_filings, activist_filings, and dilution_filings each accept a limit arg. Body-fetching tools (late_filings / activist_filings / dilution_filings) fetch one filing body per result — banks and other very-active 424B-prospectus filers can have hundreds of dilution filings per year and would exceed the MCP client's request timeout if all bodies were fetched. If you set limit higher than what's available, the tool just returns what's there (no padding, no error).get_quote returns dividendYield as a decimal, not a percent. 0.0042 means 0.42%, matching Yahoo's wire format. Multiply by 100 if you want to display it as "0.42%".trailingPE / forwardPE — those metrics don't apply to fund structures. Many ETFs still expose dividendYield.get_quote timestamp is the last actual tick — not "now". For illiquid securities (low daily volume, after-hours, weekends, halted) it can be hours or days behind wall clock. To detect this, compare timestamp to dataAsOf (when the MCP served the object) and check marketState (regular means the session is open, so a large gap there is the "this just hasn't traded recently" case; closed / pre / post explain expected gaps).get_quote is hardened against prompt injection through the response. Every string field surfaced to the LLM is validated: ticker comes from the validated input (never from Yahoo's response), exchange must match /^[A-Z0-9_-]{1,12}$/ or returns null, currency must be a 3-letter ISO-4217 code or the call throws, marketState must match a known enum or the call throws, dates are constrained to YYYY-MM-DD, and numerics must be finite. Yahoo's own error text is never passed through verbatim. Numeric fields can't carry text instructions; the only strings reachable from a Yahoo response are tightly-bounded short codes.git clone https://github.com/btopn/OpenInsider-MCP
cd openinsider-mcp
npm install
npm run build
npm test # unit tests against checked-in fixtures (offline)
SMOKE=1 npm test # live tests against OpenInsider, SEC EDGAR, FINRA
node dist/index.js # run the MCP server on stdio
To exercise the server interactively, use the official MCP inspector:
npx @modelcontextprotocol/inspector node dist/index.js
For a one-shot dump of representative output from every tool against a known ticker (useful for verifying a deployment or eyeballing what each tool returns):
npm run build
node scripts/exercise-tools.mjs NVDA # or any ticker
For targeted queries against a specific tool (full output, no truncation; pipe through jq for date or field filtering):
node scripts/run-tool.mjs <tool-name> [key=value ...]
# Examples:
node scripts/run-tool.mjs search_by_ticker ticker=NVDA daysBack=7 \
| jq '.[] | select(.tradeDate == "2026-04-22")'
node scripts/run-tool.mjs daily_short_volume ticker=GME daysBack=10
node scripts/run-tool.mjs recent_sec_filings ticker=AAPL daysBack=730 itemCodes=5.02 \
| jq '.[] | {filingDate, primaryDocUrl}'
node scripts/run-tool.mjs short_interest ticker=NVDA periodsBack=6 > nvda_si.json
Status messages go to stderr; tool output goes to stdout.
To identify your deployment to SEC EDGAR with your own contact info (polite practice), set the OPENINSIDER_MCP_UA env var:
OPENINSIDER_MCP_UA="my-app 1.0 [email protected]" node dist/index.js
SMOKE=1 npm test to confirm whether the parser, URL pattern, or upstream availability is the problem, then open an issue or PR.MIT
Выполни в терминале:
claude mcp add openinsider-mcp -- npx Безопасность
Низкий рискАвтоматическая эвристика по публичным данным — не гарантия безопасности.