filings-search

filings-search

Enables AI agents to search, retrieve, and analyze SEC 10-K filings with hybrid BM25+kNN retrieval, per-claim citations, and strict numeric grounding verification.

Category
Visit Server

README

filings-search

Hybrid (BM25 + kNN) agentic retrieval over SEC 10-K filings, on OpenSearch, with a Claude tool-use agent that plans its own searches and answers with per-claim citations and a hard numeric-grounding check. FastAPI surface + TypeScript MCP tool server. Built as the OpenSearch successor to my Qdrant pipeline; the cite-or-abstain grounding is ported from my 12-domain provenance wrapper.

EDGAR ──► connector ──► section splitter ──► chunker ──► embeddings ──► OpenSearch (BM25 + HNSW kNN)
(SEC API)  (ticker→CIK,   (Item 1/1A/1C/7/8…)  (450 tok,    (nomic-embed-text            │
            10-K list,     order-constrained,    60 overlap)  local via Ollama, or        │
            iXBRL strip)   x-ref filtered)                     OpenAI)                     ▼
                                                                          hybrid search (RRF) + filters
                                                                                       │
                                                       ┌───────────────────────────────┼─────────────────────┐
                                                       ▼                               ▼                     ▼
                                              FastAPI /search /ask           TS MCP server            eval harness
                                              /chunk /companies         (search_filings, ask_filings)  (P@k/MRR, LLM-judge)
                                                       ▲
                                              Claude agent (tool loop):
                                              resolve_company → search_filings (item/ticker/FY filters, hybrid|bm25|knn)
                                              → expand_chunk → answer with [c:chunk_id] cites → numeric grounding check

What's in it

Layer File Notes
Connector filings_search/edgar.py SEC ticker map + browse-edgar fallback (handles successor-shell CIKs), data.sec.gov submissions, primary-doc fetch, iXBRL/HTML strip, disk cache
Parsing filings_search/parse.py 10-K Item splitter: title-verified headings, cross-reference filter, canonical-order + longest-span selection (defeats ToC rows)
Chunking filings_search/chunk.py paragraph-respecting, token-bounded (450/60 overlap), deterministic chunk ids `sha1(accession
Embeddings filings_search/embed.py nomic-embed-text (768-d) on local Ollama by default; OpenAI text-embedding-3-small fallback
Index filings_search/index.py OpenSearch 2.19 mapping: english analyzer BM25 field + knn_vector (lucene HNSW, cosine) + keyword metadata (ticker, cik, item, fiscal_year, accession…)
Retrieval filings_search/search.py bm25, knn, hybrid (client-side reciprocal-rank fusion), metadata filters, neighbor expansion
Agent filings_search/agent.py Claude (claude-opus-5) tool loop; tools: list_indexed_companies, resolve_company, search_filings, expand_chunk; answer must cite [c:id]; every figure in the answer must appear in a cited chunk or the response is flagged grounded_numbers=false
API filings_search/api.py FastAPI: GET /search, POST /ask, GET /chunk/{id}?expand=, GET /companies, GET /health
MCP mcp/src/server.ts TypeScript stdio MCP server exposing list_companies, search_filings, get_chunk, ask_filings
Eval eval/ run_retrieval_eval.py (hit@1/hit@5/MRR@10 by mode, labeled queries), run_grounding_eval.py (numeric grounding + LLM-as-judge citation support)

Run it

docker compose up -d                       # OpenSearch 2.19 (knn + neural plugins), :9200
ollama pull nomic-embed-text               # local embeddings
python3.12 -m venv .venv && ./.venv/bin/pip install -r requirements.txt
./.venv/bin/python ingest.py --recreate AAPL MSFT NVDA JPM XOM WMT TSLA JNJ   # ~2 min, 8 filings, ~2.1k chunks
./run_api.sh                               # FastAPI on :8801
./.venv/bin/python -m filings_search.agent "What does NVIDIA disclose about export controls to China?"
./.venv/bin/python eval/run_retrieval_eval.py
./.venv/bin/python eval/run_grounding_eval.py

MCP (Claude Desktop / Claude Code / Cursor):

{"mcpServers": {"filings-search": {"command": "node", "args": ["/ABS/PATH/filings-search/mcp/dist/server.js"],
                                    "env": {"FILINGS_API_URL": "http://127.0.0.1:8801"}}}}

Config via env: FS_OPENSEARCH_URL, FS_INDEX, FS_EMBED_BACKEND=ollama|openai, FS_AGENT_MODEL, FS_JUDGE_MODEL, SEC_USER_AGENT. ANTHROPIC_API_KEY (or ~/.env) for the agent/judge.

Results (2026-08-18, 8 filings / 2,101 chunks, 28 labeled queries)

Retrieval — see eval/retrieval_results.json:

setting mode hit@1 hit@5 MRR@10 avg ms
unfiltered bm25 0.571 0.857 0.686 5.7
unfiltered knn 0.679 0.857 0.759 29.7
unfiltered hybrid 0.679 0.857 0.759 37.9
ticker_filtered bm25 0.679 0.929 0.772 3.8
ticker_filtered knn 0.679 0.893 0.779 26.6
ticker_filtered hybrid 0.714 0.929 0.812 34.9

Agent grounding — 10 analyst questions, claude-opus-5 agent + claude-opus-5 judge (see eval/grounding_results.json):

metric value
numeric-grounded rate (every figure appears in a cited chunk) 10/10 = 1.00
LLM-judge verdict: grounded / partially grounded / ungrounded 9 / 1 / 0
mean fraction of claims supported by a cited chunk 0.968
avg citations per answer 9.7
avg tool calls per answer (agent-chosen searches/expansions) 7.6
avg latency 41 s (13 s simple → 59 s multi-Item)
tokens for the 10-question run 563k in / 23k out

The one partially_grounded (Walmart tariffs, 0.87) was the agent summarizing a mitigation that the cited chunk states more narrowly — the judge caught it; that is what the judge is for.

Design notes / honest limitations

  • Agentic ≠ fixed RAG. The model chooses company, Item, mode, and how many rounds; a fixed top-k pipeline gets no second chance. The tool descriptions carry the "when to use" guidance (item map, bm25-for-figures, widen-if-empty).
  • Grounding is strict on purpose. A derived rounding ("$99,779M" → "~$99.8B") is flagged as ungrounded; analysts want the figure as filed. Loosen with a tolerance if you disagree.
  • 10-K structure quirks are real, not parser bugs: JPM and XOM are "wrapper" 10-Ks whose MD&A/financials sit in a back-of-book Financial Section (labeled under the last Item); NVIDIA files statements under Item 15. Eval labels for those are ticker-only. A follow-up is F-page detection (Consolidated Statements of … headings) to relabel as Item 8.
  • RRF is client-side — transparent and easy to reason about; OpenSearch's hybrid query
    • normalization pipeline is the in-cluster alternative. No cross-encoder re-ranker yet.
  • Single filing per company in this run; --filings N pulls prior years (fiscal_year filter already in the mapping and tools).
  • No auth, no rate limits, single-node OpenSearch — this is a working vertical, not a deployment.

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured