Tool contract
Exact names, inputs, outputs, and citation rules for every Signals MCP tool.
Signals exposes 18 model-facing, read-only tools. Every descriptor includes a JSON Schema input contract, an output contract, and MCP annotations marking the operation read-only, non-destructive, idempotent, and closed-world.
Naming rules
Tool names describe cardinality and intent:
search_*finds candidates or evidence from a query.list_*returns a bounded collection and usually supplies IDs for a fetch.get_*returns one resource, one series, one bundle, or one coverage record.
This is why KPI access is a pair: list_company_kpis discovers valid
metric_key values, while get_kpi_series fetches exactly one cited series.
Names are exact: the server rejects any name not returned by tools/list.
There are no hidden aliases.
Company and coverage
| Tool | Required inputs | Result | Typical next call |
|---|---|---|---|
search_companies | query | Matching company identity records | get_company or get_coverage |
get_company | symbol | One company identity and profile | get_coverage |
get_coverage | symbol | exists plus record counts by dataset | A dataset tool with non-zero coverage |
In get_coverage, a dataset count of null means the count could not be
taken — not that the count is zero. The reason appears under unavailable,
keyed by dataset. Treat null as unknown coverage and say so; treat 0 as
genuinely no rows.
Use exchange-qualified symbols such as AAPL:US when the ticker could be
ambiguous. A bare US ticker such as AAPL resolves to AAPL:US.
Financials and KPIs
| Tool | Required inputs | Optional inputs | Result |
|---|---|---|---|
get_financial_statements | symbol | statement, period, limit | Reported financials as a period series, each cited to its SEC filing |
list_company_kpis | symbol | limit (1–50) | KPI metadata in the company's judged order: metric_key, display name, unit, definition, an 8-point uncited preview, and — once the company is judged — tier, family, polarity, stale_quarters, plus a top-level headline list |
get_kpi_series | symbol, metric_key | limit (1–50) | One KPI definition (with the same judgment fields) plus cited observations; a point may carry approximate: true |
get_financial_statements is the tool for reported financials. Pass
statement as one of:
statement | Covers |
|---|---|
income (default) | Revenue, gross profit, operating expenses, EBITDA, operating income, tax, net income, EPS basic and diluted, share counts |
balance_sheet | Cash, receivables, inventory, PP&E, goodwill, total assets, payables, short- and long-term debt, total liabilities, equity, net debt |
cash_flow | Operating, investing and financing cash flow, capex, buybacks, dividends, free cash flow |
key_metrics | Market cap, enterprise value, P/E, P/S, P/B, EV/EBITDA, FCF yield, debt/equity, current ratio, margins, ROIC, ROE, ROA, working-capital days |
period is annual (default) or quarter; limit is the number of periods,
newest first. A line item that was not reported is null, never 0.
The two layers are intentionally different, and picking the wrong one is the most common mistake:
get_financial_statements— what the company reported on its face financials. This is where revenue, EPS, margins and ratios live.list_company_kpis/get_kpi_series— issuer-specific operating measures that appear in no standard statement: gross bookings, NRR, deliveries, data-center revenue, segment revenue.
What the judgment fields mean
Every company's KPI list is laid out once by a model reading the company's own metric labels and definitions, and the verdict is cached. When that judgment exists the tools carry it; when it does not, the fields are simply absent — never a made-up tier.
| field | on | meaning | what to do with it |
|---|---|---|---|
headline | the listing | the metric_keys the company is judged on, in order | the answer to "what matters for this company" |
tier | each metric | 1 = a headline metric the company leads its releases with; 2 = supporting; 3 = a minor line or a part of a breakdown; 4 = treasury / balance-sheet footnote | lead with tier 1 |
family | a metric that is one part of a breakdown | {id, label, part} — e.g. Revenue by segment / Mobility | never sum parts; never present a part as the total |
polarity | each metric | inverse when a rise is bad (costs, churn, losses) | do not describe a rising cost as growth |
stale_quarters | each metric | how many quarters the series trails the company's newest quarter | treat > 1 as not current — usually our extraction gap, not the company's |
approximate | a point | the issuer stated the figure as a rounding in prose ("surpassed 25 million") | do not compute a growth rate from two of them |
Rows already arrive in judged order with duplicate labels hidden; the fields say
why. Metrics that pair a family share one unit and one measure by
construction.
Real KPI response
Request:
{
"jsonrpc": "2.0",
"id": 4,
"method": "tools/call",
"params": {
"name": "get_kpi_series",
"arguments": {
"symbol": "UBER",
"metric_key": "total_company_gross_bookings",
"limit": 4
}
}
}Structured result, shortened to one observation:
{
"symbol": "UBER:US",
"metric": {
"metric_key": "total_company_gross_bookings",
"display": "Total Company Gross Bookings",
"unit": "USD"
},
"points": [
{
"period": {
"calendar": "FY2026-Q1",
"fiscal": null,
"end_date": "2026-03-31",
"kind": "quarter"
},
"value": {
"raw": 53720000000,
"normalized": 53720000000,
"unit": "USD"
},
"provenance": {
"src_id": "src_kpi2_be16edbfefe50d35",
"source_url": "https://www.sec.gov/Archives/edgar/data/1543151/000154315126000019/0001543151-26-000019-index.htm",
"quote": "Gross Bookings grew 25% YoY to $53.7 billion, and 21% on a constant currency basis."
}
}
]
}The value, quote, and SEC URL above are from live corpus data verified on July
22, 2026. Treat metric_key as an opaque identifier returned by discovery; do
not guess one from a display label.
Ownership and positioning
| Tool | Required inputs | Optional inputs | Result |
|---|---|---|---|
list_insider_transactions | symbol | limit | Form 4 transactions with SEC citations |
list_congress_trades | symbol | limit | STOCK Act trades with disclosure citations |
list_institutional_holders | symbol | limit | Latest-quarter 13F holders and position values |
get_short_interest_series | symbol | limit | FINRA short position, volume, days-to-cover, and change history |
These tools return public, impersonal disclosure data. They do not know a user's holdings and should not be used to imply personalized advice.
Investor-relations documents
| Tool | Required inputs | Optional inputs | Result |
|---|---|---|---|
list_ir_documents | symbol | doc_type, fiscal_year, fiscal_period, limit | Earnings decks, investor presentations, press releases, annual reports and supplements, each with the issuer's own document URL |
get_ir_document | document_id | include_text, include_outline, summary | One document; page outline, and extracted text when include_text is true |
get_ir_document_file | document_id | — | A 15-minute direct-download URL for the raw bytes |
Pass the id field of a list_ir_documents row as document_id — the row
names it id.
Every document carries a provenance object. Cite provenance.citation_url, a
permanent Signals page that always resolves; provenance.source_url is the
address on the issuer's own IR site, for a reviewer verifying against the
original. Both are present whether or not we hold the document's text.
Extraction coverage is uneven and you must check it, not assume it. The
list returns has_text per document and the fetch returns text_available;
when text is absent, text is null (never "") and
text_unavailable_reason says why. Roughly 83% of press releases carry
extracted text versus about 15% of slide decks, so for decks the normal
outcome is metadata plus the issuer's PDF link. Treat a missing text as "we
have not extracted this document", never as "this document is empty".
Reading a document yourself
When the extracted text is not enough — a deck whose text_available is false,
or a PDF you want to parse or view directly — get_ir_document_file returns a
short-lived URL to the bytes as the issuer published them.
- The URL expires in 15 minutes and carries no credentials. Anyone holding it can fetch that object until it expires, so fetch it promptly and do not store or forward it.
- Decide before you fetch. The list rows and the response both carry
mime_typeandfile_size_bytes. Sizes are long-tailed — half the corpus is under ~160 KB, but the 95th percentile is 3.7 MB and the largest document is 152 MB. Ranged GETs are supported, so you can read a header without pulling the whole file. - You can verify what you got. The SHA-256 of the fetched bytes equals the
content_hashin the response. - Not for SEC filings. A filing's
provenance.source_urlis already a publicsec.govaddress you can fetch directly; filings have no stored blob.
Transcripts and filings
| Discovery tool | Fetch tool | Join key | Fetch result |
|---|---|---|---|
list_transcripts | get_transcript | transcript_id | Full, prepared-only, or Q&A transcript segments |
list_filings | get_filing | accession | Filing metadata plus mirrored Ex-99.1/99.2 text when present |
Fetch only after discovery. IDs are corpus identifiers, not values a model should invent.
Every tool is available on every plan. Plans differ only in how much you may call them — there is no dataset a paid plan unlocks. If a request is refused it is because you have reached a rate or quota limit, and the error says which; it is never because the data is withheld from your plan.
To assemble one quarter — the call, the deck and the press release together —
filter list_transcripts and list_ir_documents by the same fiscal_year and
fiscal_period. That is the join key, and it is the reliable one: the fiscal
stamp is populated on 92% of transcripts.
The event-bundle tools were removed on 2026-08-26. Their cross-linked bundle
carried both a document and a transcript on only 17% of earnings events, so the
fiscal-stamp pairing above supersedes them. An event_id still rides out on IR
documents and filings for anyone joining on it directly.
Search
Company discovery is search_companies. Cross-corpus text search
(search_corpus) is not currently exposed; reach evidence through the
company-scoped list and fetch tools instead: resolve a symbol, then
list_filings / list_ir_documents / list_transcripts and fetch by ID.
Citation contract
Financial observations and evidence rows are provenance-gated. A citation normally contains:
| Field | Meaning |
|---|---|
src_id | Stable Signals evidence identifier |
citation_url | Cite this. A permanent Signals page that always resolves |
source_url | Where the issuer or regulator published it, for verification |
quote | Verbatim supporting text when the datum was extracted from prose |
accession | SEC accession when applicable |
content_hash | sha256: digest of the source bytes we read |
source_provider is not returned. It is an internal routing marker; the
public citation is source_url / citation_url / accession, from which
first-party versus regulatory origin is self-evident.
Which URL to cite
The two URLs answer different questions, and both are present on IR documents:
| Dataset | source_url | citation_url |
|---|---|---|
| IR documents | the issuer's own copy on their site | https://signals.ai/d/{id} |
| Transcripts | the issuer's webcast address where we can disclose it, otherwise null | https://signals.ai/t/{hash} |
| Filings | the SEC EDGAR URL | not set — EDGAR is already permanent |
Cite citation_url. It is permanent and it always resolves. source_url
is the source of record and is the right link for a reviewer verifying against
the company's own server, but roughly a third of issuer hosts refuse
non-browser clients, so an agent that fetches it directly will sometimes fail.
A null source_url on a transcript means we hold no disclosable issuer
address for that call. It never means the quote cannot be cited.
Preserve citation_url and quote when presenting a claim. An empty data or
points array means no matching cited row was returned. It does not prove
that the real-world quantity is zero; check get_coverage and state the gap.
See protocol and errors for response envelopes and retry behavior.