Insider trading MCP server

Nine Model Context Protocol tools over public disclosure data: SEC Form 4 filings, corporate insider identities, U.S. Congress STOCK Act trades, tracked portfolios, cluster and streak digests, and European PDMR filings. One HTTP endpoint, streamable HTTP transport, no key, no local process to install.

The server returns what was filed with the regulator. It does not rate securities or recommend action. Not investment advice.

Connect a client

Add this to your MCP client configuration and restart it. There is no key field because there is no key.

{
  "mcpServers": {
    "tracefour": {
      "type": "http",
      "url": "https://tracefour.com/v1/mcp"
    }
  }
}

In Claude Code, the equivalent one-liner:

claude mcp add --transport http tracefour https://tracefour.com/v1/mcp

Anonymous callers get 60 requests per hour per IP. If you already have a free Tracefour API key, send it as Authorization: Bearer tf_YOUR_KEY and the limit becomes 600 per hour. The key is forwarded to the underlying REST endpoint unchanged.

The nine tools

Each tool maps one-to-one onto a REST endpoint documented elsewhere in these docs, so a tool result is the same envelope you would get from curl, returned as JSON text inside the MCP content block.

Tool Arguments What it returns
get_catalognoneEvery endpoint, every tracker slug, every Congress member slug, the four European markets, the rate limits and the licence, in one call. Call this first.
get_latest_filingssince, direction, min_value, ticker, limit (all optional)Open-market U.S. SEC Form 4 transactions from the rolling 365-day window. direction accepts P for purchases, S for sales, or all for both, and defaults to all. Awards, option exercises and gifts are not in this feed; get_insider returns those for one person. limit runs 1 to 500 and defaults to 100.
search_insidersq (required), limitResolve a person name to an insider slug. Necessary because slugs are EDGAR name order, surname first, and cannot be guessed.
get_insiderid (required)One insider: identity, transactions in the window, archived career lots, co-filers. Take id from search_insiders.
get_congress_membermember (required)STOCK Act trades for one member, newest first. The slug is the first-name-first form, for example nancy-pelosi.
get_trackerslug (required)One tracked portfolio: holdings, changes, and a benchmark comparison. The slug is the short curated form, for example pelosi, and it is a different namespace from get_congress_member.
get_clustersnoneTickers where 3 or more distinct insiders filed in the same direction inside a 60-day window, built from open-market purchases and sales only.
get_streaksnoneInsiders who filed in the same direction for two or more consecutive weeks, with the track record of their previous runs.
get_eu_filingscountry (required)Recent European PDMR filings for one market: de for BaFin, uk for the FCA, se for Finansinspektionen, nl for the AFM.

get_catalog is the right first call. It returns every tracker slug and every Congress member slug, which is what stops an agent guessing /v1/congress/pelosi when the member slug is nancy-pelosi and the tracker slug is pelosi.

A worked example

The interesting part of this server is what an agent has to do before it can answer anything about a named person: resolve the name to a slug. Insider slugs are EDGAR name order, surname first, so a model that constructs one from a common name will get a 404 every time.

# A model is asked: "What has NVIDIA's CEO filed recently?"
# It cannot guess the slug, so it searches first, then reads.

# 1. Orient. Every slug the server knows, in one call.
{"jsonrpc":"2.0","id":1,"method":"tools/call",
 "params":{"name":"get_catalog","arguments":{}}}

# 2. Resolve the person. "jensen huang" is not a slug; "huang" finds one.
{"jsonrpc":"2.0","id":2,"method":"tools/call",
 "params":{"name":"search_insiders","arguments":{"q":"huang","limit":3}}}
# -> huang-jen-hsun, tickers ["NVDA"]

# 3. Read the filings for that slug.
{"jsonrpc":"2.0","id":3,"method":"tools/call",
 "params":{"name":"get_insider","arguments":{"id":"huang-jen-hsun"}}}

# 4. Or go the other way: everything NVDA insiders bought this quarter.
{"jsonrpc":"2.0","id":4,"method":"tools/call",
 "params":{"name":"get_latest_filings",
           "arguments":{"ticker":"NVDA","direction":"P","since":"2026-04-01"}}}

Transcripts

Every block below was captured by POSTing to a running server. Step 2 of the worked example, sent as plain curl:

curl -X POST "https://tracefour.com/v1/mcp" \
  -H "content-type: application/json" \
  -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"search_insiders","arguments":{"q":"huang","limit":2}}}'

tools/call result

The tool result is the REST envelope, JSON-encoded as a string inside the content block. The meta object is abbreviated here; over the wire it carries the same attribution and licence keys every REST response does.

{
  "id": 3,
  "jsonrpc": "2.0",
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\"data\":[{\"slug\":\"chuang-tzu-sou\",\"name\":\"Chuang Tzu-Sou\",\"tickers\":[\"TSM\"],\"lastSeen\":\"2026-06-24T10:43:03.000Z\"},{\"slug\":\"huang-jen-hsun\",\"name\":\"HUANG JEN HSUN\",\"tickers\":[\"NVDA\"],\"lastSeen\":\"2026-06-23T10:00:38.000Z\"}],\"meta\":{...}}"
      }
    ]
  }
}

tools/list result

Trimmed from nine tools to one, so the inputSchema shape is visible.

{
  "id": 2,
  "jsonrpc": "2.0",
  "result": {
    "tools": [
      {
        "name": "search_insiders",
        "description": "Find a corporate insider by name. Returns matching slugs for use with get_insider. Insider slugs are EDGAR name-order and cannot be guessed from a person name.",
        "inputSchema": {
          "type": "object",
          "properties": {
            "q": { "type": "string", "description": "Name fragment, e.g. huang." },
            "limit": { "type": "number", "description": "1-100, default 20." }
          },
          "required": ["q"]
        }
      }
    ]
  }
}

A failing tool call

Captured by calling get_congress_member with a slug that has no disclosures on file. Note that this is a result, not an error: the model can read it and say so.

{
  "id": 4,
  "jsonrpc": "2.0",
  "result": {
    "content": [
      { "type": "text", "text": "HTTP 404: Member not found." }
    ],
    "isError": true
  }
}

The endpoint

POST /v1/mcp

The whole server. One HTTP endpoint speaking JSON-RPC 2.0 over the streamable HTTP transport, protocol version 2025-03-26. No key, no session handshake beyond initialize, no websocket.

Supported methods are initialize, ping, tools/list and tools/call. Anything else comes back as JSON-RPC error -32601. A notification, meaning a request with no id whose method starts with notifications/, gets an empty 202. Batch requests are rejected: send one request per POST.

Every tool call is dispatched by forwarding to the matching /v1 REST endpoint in the same process, carrying your Authorization header through. Rate limiting, validation and payload shape are therefore identical to the documented REST API by construction, and the two cannot drift apart.

A failing tool comes back as a successful JSON-RPC result carrying isError true and an HTTP status in the text, not as a protocol error. That is deliberate: the model reads the message and can tell the user "no disclosures on file for that slug" instead of the client swallowing a transport failure.

Parameter Type Accepts Default
jsonrpcstring"2.0". Up to 8 characters.optional
idnumber, string or nullEchoed back on the response. Strings up to 256 characters.null
methodstringinitialize, ping, tools/list, tools/call, or notifications/*.required in practice
paramsobjectFor tools/call: name plus arguments.empty object

curl

curl -X POST "https://tracefour.com/v1/mcp" \
  -H "content-type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26"}}'

Python

import requests

def call(method, params=None, request_id=1):
    response = requests.post(
        "https://tracefour.com/v1/mcp",
        json={"jsonrpc": "2.0", "id": request_id, "method": method, "params": params or {}},
        timeout=30,
    )
    response.raise_for_status()
    return response.json()

print(call("initialize", {"protocolVersion": "2025-03-26"})["result"]["serverInfo"])
print([tool["name"] for tool in call("tools/list", request_id=2)["result"]["tools"]])

JavaScript

async function call(method, params = {}, id = 1) {
  const response = await fetch("https://tracefour.com/v1/mcp", {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify({ jsonrpc: "2.0", id, method, params }),
  })
  return response.json()
}

const { result } = await call("tools/list", {}, 2)
console.log(result.tools.map((tool) => tool.name))

Response

Captured verbatim from the curl above. The instructions string is what an agent reads before its first tool call, which is why it names the rate limits and says get_catalog first.

{
  "id": 1,
  "jsonrpc": "2.0",
  "result": {
    "capabilities": { "tools": {} },
    "instructions": "Read-only public insider-trading disclosure data (SEC Form 4, 13F/Congress/OGE trackers, EU PDMR filings). No API key needed. Anonymous callers get 60 requests/hour; a free key at https://tracefour.com/settings raises it to 600. Call get_catalog first to discover every available slug. Not investment advice.",
    "protocolVersion": "2025-03-26",
    "serverInfo": { "name": "tracefour", "version": "1.0.0" }
  }
}

License and attribution

Tool results carry the same licence block as the REST API. The underlying filings are public-domain government works and Tracefour claims nothing over them. The Tracefour compilation is CC BY 4.0, whose one condition is attribution with a link. When an agent surfaces one of these numbers to a person, link the page named in meta.attribution.page, or the url on the record it used. That link is also what lets the reader check the original filing.

A companion skill file for agents lives at /skill.md, and a site overview at /llms.txt.

Related

Frequently asked questions

Is there an MCP server for insider trading data?

Yes. POST https://tracefour.com/v1/mcp is a Model Context Protocol server over the streamable HTTP transport, exposing nine tools across SEC Form 4 filings, corporate insider identities, U.S. Congress STOCK Act trades, tracked portfolios, cluster and streak digests, and European PDMR filings. It is free and needs no API key.

Does the Tracefour MCP server need an API key?

No. Every method, including tools/call, answers unauthenticated requests at 60 requests per hour per IP address. A free API key raises that to 600 requests per hour and is passed through as an Authorization bearer header if you have one, but nothing is gated behind it.

How do I add the Tracefour MCP server to my client?

Add an entry under mcpServers with type http and url https://tracefour.com/v1/mcp, then restart the client. In Claude Code the one-line equivalent is: claude mcp add --transport http tracefour https://tracefour.com/v1/mcp. There is no key to configure and no local process to run.

Which MCP tool should an agent call first?

get_catalog. It returns every endpoint, every tracker slug, every Congress member slug, the four European market codes, the rate limits and the licence in a single call, which removes the guessing that produces 404s. The two slug namespaces in particular, trackers and Congress members, are both listed there.

What happens when an MCP tool call fails?

It returns a successful JSON-RPC result whose content carries the HTTP status and message, with isError set to true, rather than a JSON-RPC protocol error. A request for a Congress member with no disclosures on file comes back as "HTTP 404: Member not found." so the model can say so plainly. Protocol-level errors are reserved for an unknown method, an unparseable body, or a batch request, which is not supported.

What data can the MCP server actually answer questions about?

Public regulatory disclosures only: SEC Form 4 insider filings on a rolling 365-day window, corporate insider identity and history, U.S. Congress STOCK Act periodic transaction reports, tracked portfolios built from 13F-HR and OGE Form 278-T filings, and European PDMR notifications from BaFin, the FCA, Finansinspektionen and the AFM. It reports what was filed and when. It is not investment advice.

Data sourced from public regulatory filings. Not investment advice.