thought-search

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.

Category
访问服务器

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.pdf if you type "fermi". You didn't remember "fermi". You remembered punch force and jaw.
  • An agent with shell access can ls, cat, and grep its way around — powerful, but blind. Every wrong folder it opens burns seconds and context window, and an unscoped mdfind or find / 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/plocate or 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.mp3talk.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_line is chunk-start, not an exact span.
  • The eval is an A/B gate, not a benchmark. evals/find_file_ab.py scores hit@1 / found / latency against a bare-ls control 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.

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选