Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,20 @@ This ensures clients can always see what tools are available in a given version

---

## [1.15.0] — 2026-09-24

**Tool count: 37** (+1: `list_schedule13_dg`). Free tools unchanged at 23; gated 13 → 14.

### Added
- `list_schedule13_dg` (generated) — Schedule 13D/13G >5% beneficial-ownership crossings (Business plan+). Wraps `GET /v1/ownership/13d-13g`.
- `get_transactions` — `ticker` now documents that it accepts a comma-separated list of up to 25 symbols in one call (backend insiderapi #312), and gained `filed_from`/`filed_to` (inclusive date window on `Filing.FiledAt`, not `transactionDate`) so a caller can replay disclosures in the order the market actually saw them rather than the order the trades happened.
- `Transaction` and `Filing` response types gained `acceptedAt` (nullable) and `documentUrl` (backend insiderapi #312).

### Changed
- 14 list endpoints now accept `limit` as an alias for `per_page` on the backend (canonical `per_page` wins on conflict); the generated tool descriptions pick this up automatically from the spec. `order` is **not** an alias for `sort` — unchanged.
- `get_webhook_events`'s generated description now lists all five real webhook event types (`TransactionFiled`, `ClusterBuy`, `ClusterSell`, `CongressTradeFiled` (Starter+), `ConvergenceSignal` (Pro+)) with their exact `EventType`/`X-Event-Type` casing. The dotted-name forms (e.g. `signal.convergence`) that appeared in older backend descriptions were never real event names and are gone from the generated text. No hand-written tool in this repo hardcoded an event-type list — `create_webhook`/`delete_webhook` are deliberately excluded from the MCP tool surface (see `SKIP_OPERATIONS` in `codegen/generate.mjs`), so this was purely a generated-description fix.
- `GET /v1/transactions`, `POST /v1/webhooks`, and `DELETE /v1/webhooks/{id}` now declare 400/404 responses in the spec — no client-visible behavior change, error handling here was already generic.

## [1.14.1] — 2026-09-15

Tool count unchanged at 36. No tools added, renamed, or removed.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "form4api-mcp",
"version": "1.14.1",
"version": "1.15.0",
"mcpName": "io.github.theodor90/form4api-mcp",
"description": "MCP server for Form4API — SEC Form 4 insider trading, Form 144 intent-to-sell, institutional 13F-HR, and congressional STOCK Act trading data, including the insider/Congress convergence signal. Lets Claude / Cursor / Windsurf / Zed query real-time US insider and congressional trading data via natural language.",
"keywords": [
Expand Down
6 changes: 3 additions & 3 deletions server.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"name": "io.github.theodor90/form4api-mcp",
"title": "Form4API — SEC Insider Trading",
"description": "Real-time SEC Form 4 insider trading + Form 144, 13F-HR & congressional STOCK Act data. 36 tools + 6 research prompts.",
"version": "1.14.1",
"description": "Real-time SEC Form 4 insider trading + Form 144, 13F-HR & congressional STOCK Act data. 37 tools + 6 research prompts.",
"version": "1.15.0",
"websiteUrl": "https://www.form4api.com",
"repository": {
"url": "https://github.com/theodor90/form4api-mcp",
Expand All @@ -13,7 +13,7 @@
{
"registryType": "npm",
"identifier": "form4api-mcp",
"version": "1.14.1",
"version": "1.15.0",
"transport": { "type": "stdio" },
"environmentVariables": [
{
Expand Down
59 changes: 46 additions & 13 deletions src/tools/_generated.ts

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion src/tools/transactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { Form4ApiClient } from '../client.js'
import type { Transaction } from '../types.js'

export const getTransactionsSchema = z.object({
ticker: z.string().optional().describe('Stock ticker symbol, case-insensitive, e.g. AAPL or aapl.'),
ticker: z.string().optional().describe('Stock ticker symbol, case-insensitive, e.g. AAPL or aapl. Also accepts a comma-separated list of up to 25 symbols (e.g. "AAPL,MSFT") to match any of them in one call; tokens are trimmed and upper-cased, empty tokens are ignored, and more than 25 symbols returns 400.'),
cik: z.string().optional().describe('Company CIK number — SEC\'s numeric filer identifier, e.g. 0000320193. Leading zeros optional.'),
insider_cik: z.string().optional().describe('Insider CIK number — SEC\'s numeric filer identifier, e.g. 0001214128. Leading zeros optional.'),
code: z
Expand Down Expand Up @@ -98,6 +98,8 @@ export const getTransactionsSchema = z.object({
.describe('Filter by the trailing quarter-over-quarter trend in institutional (13F) ownership of the underlying company. No effect if institutional-ownership enrichment is disabled server-side; rows where the trend was suppressed for insufficient 13F coverage still match "stable".'),
from: z.string().optional().describe('Start date, inclusive, format YYYY-MM-DD (e.g. 2026-01-01). Filters on transactionDate.'),
to: z.string().optional().describe('End date, inclusive, format YYYY-MM-DD (e.g. 2026-12-31). Filters on transactionDate.'),
filed_from: z.string().optional().describe('Inclusive start of the filed-date window, format YYYY-MM-DD. Filters on Filing.FiledAt (when the filing became public), not transactionDate — the two can differ by days to weeks. Use this to replay disclosures in the order the market actually saw them.'),
filed_to: z.string().optional().describe('Inclusive end of the filed-date window, format YYYY-MM-DD. Filters on Filing.FiledAt.'),
page: z.number().int().min(1).optional().default(1).describe('1-based page number. Defaults to 1. Paging depth is plan-limited: Free reaches page 20, Starter page 100, Pro and above unlimited; beyond that the call returns 402 with the upgrade path. If you need the full history rather than a page of it, the REST endpoint GET /v1/transactions/export (Business plan) streams the entire filtered set as CSV in one request.'),
per_page: z.number().int().min(1).max(100).optional().default(20).describe('Results per page. Defaults to 20, maximum 100.'),
})
Expand Down Expand Up @@ -135,6 +137,8 @@ export async function getTransactions(client: Form4ApiClient, input: GetTransact
inst_ownership_trend: input.inst_ownership_trend,
from: input.from,
to: input.to,
filed_from: input.filed_from,
filed_to: input.filed_to,
page: input.page,
per_page: input.per_page,
})
Expand Down
4 changes: 4 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ export interface Transaction {
transactionId: number
accessionNumber: string
filedAt: string
acceptedAt: string | null
documentUrl: string | null
transactionDate: string
transactionCode: string
sharesAmount: number
Expand All @@ -23,6 +25,8 @@ export interface Transaction {
export interface Filing {
accessionNumber: string
filedAt: string
acceptedAt: string | null
documentUrl: string | null
periodOfReport: string
companyCik: string
companyName: string
Expand Down
9 changes: 6 additions & 3 deletions test/mcp-test.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* MCP protocol test — spawns the server and verifies:
* 1. initialize handshake
* 2. tools/list returns all 36 tools with correct names
* 2. tools/list returns all 37 tools with correct names
* 3. prompts/list returns all 6 recipe prompts
* 4. prompts/get returns rendered messages for a sample of prompts
* 5. (optional) live tool call if FORM4API_KEY is set
Expand Down Expand Up @@ -66,6 +66,9 @@ const EXPECTED_TOOLS = [
// #206, 2026-08-04). Missed by the 2026-08-10 codegen pass and caught by the
// 2026-08-12 coverage sweep — the endpoint existed for 8 days with no tool.
'list_filings',
// Auto-generated after the Schedule 13D/13G ownership-crossings endpoint
// shipped; picked up by the 2026-09-24 OpenAPI sync (v1.15.0).
'list_schedule13_dg',
]

// Recipe prompts (v1.9.0, 2026-07-11) — the MCP "prompts" capability.
Expand Down Expand Up @@ -150,11 +153,11 @@ async function runTest() {
server.stdin.write(JSON.stringify({ jsonrpc: '2.0', method: 'notifications/initialized', params: {} }) + '\n')

// ── Test 2: tools/list ────────────────────────────────────────────────
console.log('\n[2] tools/list — 36 tools registered')
console.log('\n[2] tools/list — 37 tools registered')
const listRes = await send('tools/list', {})
const toolNames = (listRes.result?.tools ?? []).map(t => t.name)
assert(!listRes.error, 'no error in tools/list response')
assert(toolNames.length === 36, `36 tools returned (got ${toolNames.length})`)
assert(toolNames.length === 37, `37 tools returned (got ${toolNames.length})`)
for (const name of EXPECTED_TOOLS) {
assert(toolNames.includes(name), `tool "${name}" present`)
}
Expand Down
Loading