thought-search
MCP server for searching files on macOS by name or content using semantic and lexical search, providing file paths and line numbers for agents to read from.
README
thought-search
Find any file on your Mac by a vague memory of it. Spotlight for names, a
local semantic index for meaning, and results shaped for agents: a path:line
to start reading from, not a chunk dump. Nothing leaves the machine.
$ thought grep "the essay about calculating the punch force that could snap a jaw"
~/Documents/college/fermi-estimates-draft.pdf:118 (0.68) — assume the fist decelerates over ~2cm of jaw...
$ thought find "financial memo" --kind presentation
Found 2 in your work folders (newest first):
1. ~/Documents/Acme Financial - One Page Memo.pptx — 3d ago — 4.2MB
2. ~/Downloads/memo-draft-old.pptx — 2mo ago — 3.9MB
This is the file-search layer of Apprentice, a macOS AI apprentice, extracted as a standalone tool. It shipped to real users first; the code here is the shipped code, war-story comments included.
The problem
You know the file exists. You wrote it. You just can't produce the one thing every search interface demands: its name.
Two tools already live on your Mac, and each is broken alone:
- Spotlight is fast and literal. It finds
fermi-estimates-draft.pdfif you type "fermi". You didn't remember "fermi". You remembered punch force and jaw. - An agent with shell access can
ls,cat, andgrepits way around — powerful, but blind. Every wrong folder it opens burns seconds and context window, and an unscopedmdfindorfind /is a 30-60 second walk.
Semantic search should fix this, and in RAG form it half does: one query pulls content by meaning. But it hands back top-K chunks ripped out of their files — an answer fragment with no neighbors, no thread to pull, nowhere to stand.
The whole point is: you shouldn't have to pick.
The bet
Supermemory's SMFS work put numbers on the right design: let semantic search land on a path, then let the agent read, grep, and reason from that path with the tools it already has. Reach of semantic search, control of agentic search, one motion. (Their write-up is worth your time.)
SMFS mounts a cloud memory backend as a filesystem, so half their system is a sync engine: push queues, delta pulls, watermarks, FUSE/NFS mounts.
We took the other half of the idea and inverted the premise: your Mac is already the filesystem. The files are already local, already canonical, already yours. What's missing is not a mount — it's the semantic layer over what's there. So thought-search is the retrieval model without the cloud: no daemon, no sync, no account. An index in SQLite, a matrix in RAM, and two primitives.
Two primitives
find_file — the lexical leg. Name + kind + time + folder, straight off
the OS's own Spotlight index (mdfind, always scoped with -onlyin, never a
directory walk). Multi-word queries are tokenized so "financial memo" matches
Acme Financial - One Page Memo.pptx. Temporal queries use
kMDItemLastUsedDate — when you last opened the file, not when some
background process touched it — so "the deck I worked on yesterday" means what
you meant. On a miss it escalates: work folders → whole home directory →
external drives. It never dead-ends silently; it tells you where it looked.
semantic_grep — the semantic leg. File contents, chunked and embedded
locally (ONNX MiniLM, no PyTorch, ~15MB of runtime), searched by cosine over
an in-RAM matrix. A hit is path:line (score) — excerpt: a launch point. Open
the file, read around the line, follow the thread.
The router between them is not code — it's the model. find_file for
name/metadata references, semantic_grep for content references, and the
agent picks per query, exactly the way you reach for grep vs grep -r.
docs/AGENT_PROMPT.md is the battle-tested routing
doctrine to put in your agent's system prompt.
Install
pip install git+https://github.com/Reppin123/thought-search.git
thought index # crawl Downloads/Desktop/Documents, embed locally
thought grep "that doc about our pricing model"
thought find "contract" --since 'last week'
thought status
The embedding model (~90MB, all-MiniLM-L6-v2, Apache-2.0) downloads once on first use. After that the tool runs fully offline. macOS only — the lexical leg is Spotlight.
For agents (MCP)
pip install "thought-search[mcp]"
claude mcp add thought-search -- thought-mcp
Any MCP client works: {"mcpServers": {"thought-search": {"command": "thought-mcp"}}}.
Then wire docs/AGENT_PROMPT.md into your system
prompt — the tools are half the product; the routing doctrine is the other
half.
As a library
import asyncio
from thought_search import find_files, index_paths, search, start_background_index
index_paths(["~/Documents"]) # foreground crawl
start_background_index() # or: polite daemon-thread crawl
hits = search("unit economics assumptions") # [{path, score, snippet, start_line}]
res = asyncio.run(find_files("financial memo", kind="presentation"))
What makes it fast (and keeps it fast)
Everything below was a measured failure first. The numbers come from the machine this shipped on — a real corpus that grew to 7,207 files / 280k chunks / 1.2GB of index — not a synthetic benchmark.
Content-hash freshness. Every file's text is hashed with a settings fingerprint (model, dims, chunking params). Identical bytes never re-embed: unchanged files skip on an mtime+size gate without even being read, and a moved/renamed/copied file reuses its vectors for free. Steady-state re-crawls cost approximately nothing.
Progressive commits. The index commits every 20 embedded files. The first version committed at the end of the crawl — 26 minutes in, the WAL held 71MB and every search said "index empty." Partial results should be searchable during the first crawl, and a killed process should keep its progress.
A polite background crawl. The first cut pegged ~4 cores for 26 minutes.
Now: os.nice(10) plus a per-embed throttle. An index that makes the laptop
fans spin is an index that gets uninstalled.
One matrix in RAM. Brute-force cosine is a single matmul — if the
vectors are already in memory. Reloading 388MB from SQLite per query cost
3.5-5.8s; cached, a query is ~40ms at 252k chunks. The cache key is the chunk
count, not SQLite's data_version — that pragma is per-connection, and
keying on it silently rebuilt the cache on every single query.
Best-chunk-per-file, lazily. Results are files, not chunks: argsort once, walk until top-k distinct files, early-exit. The cache holds no Python strings — at 252k rows, six 252k-element Python lists were a 73s warm-up cost by themselves. Snippets are fetched from SQLite only for the winners.
No type filter on semantic search. An inferred file-type filter is a
silent excluder — a wrong kind guess once dropped the exact PDF the query
was about, and the agent thrashed for 8 turns on a file the index had.
semantic_grep returns every type and shows the extension; choosing is the
caller's job.
bf16 storage: evaluated, rejected. Halving vector RAM sounds free until you notice numpy has no BLAS f16 matmul — you pay a 2-3s per-query upcast or you upcast at load and the RAM comes back. Honest dead ends stay in the comments so nobody re-walks them.
What it deliberately doesn't do
- No cloud, no sync engine, no account. SMFS needs push queues and delta pulls because it mirrors to a backend. There is no backend here. Your files never leave your machine; neither do their embeddings.
- No daemon, no FUSE/NFS mount. It's a library, a CLI, and an MCP server. The OS filesystem is already mounted.
- No hardcoded query planner. The model decomposes "the pdf about X from last week" into parameters inline. Speed comes from the indexes; smarts stay in the model.
Status and honest limitations
- macOS only. The lexical leg is Spotlight. A Linux leg would need
locate/plocateor similar. - Index freshness is crawl-based (session start /
thought index), not a live file-watcher. Watcher is the natural next step. - Text, code, PDF, and docx are indexed today. Images/audio/video want
transcription siblings (
talk.mp3→talk.mp3.transcript.md) — designed, not built. - MiniLM is the floor. 384-dim, 256-token context; it lands on the right files but scores are modest. The embedder is a deliberate seam — swapping in a longer-context model is a two-constant change plus a re-index.
start_lineis chunk-start, not an exact span.- The eval is an A/B gate, not a benchmark.
evals/find_file_ab.pyscores hit@1 / found / latency against a bare-lscontrol on your files; there is no xAFS-style public corpus here yet. Numbers above are one real machine, disclosed as such.
Credits
The retrieval philosophy — semantic search as a navigation aid that lands on paths — is SMFS's (technical report), here reimplemented local-first. Embeddings by all-MiniLM-L6-v2 over ONNX Runtime. Extracted from Apprentice.
MIT.
Recommended Servers
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.
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.
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.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
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.
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.
Neon Database
MCP server for interacting with Neon Management API and databases
E2B
Using MCP to run code via e2b.
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.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.