hermes-digup
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@hermes-digupfind that PDF about the Q3 budget I saved last month"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Hermes-indexing-mcp-server
An MCP server that lets your Hermes Agent search the files on your Mac by what's in them. It reuses the index the DigUp app already keeps, and gets query embeddings from EmbeddingGemma 2, served by oMLX on a second, always-on Mac.
Nothing is indexed twice. DigUp keeps indexing on your laptop the way it does now. This project copies its index to the Pi on a schedule, and searches it there with the same ranking DigUp uses.
Mac no.1 (laptop) Raspberry Pi Mac no.2 (always on)
┌──────────────────────┐ ┌───────────────────────────┐ ┌─────────────────────────┐
│ DigUp app │ rsync │ Hermes Agent (gateway) │ HTTP │ oMLX │
│ └ index.sqlite ─────┼──over ────▶│ └ hermes-digup (MCP) │──query──▶│ EmbeddingGemma 2 │
│ digup-sync (launchd) │ SSH, │ ├ index copy │◀─vector──│ (google's weights, │
│ │ every │ ├ meaning + keywords │ │ pinned in memory) │
│ Hermes desktop app ──┼── 15 min │ └ tools for the agent │ │ its own sub key │
│ (remote gateway) ───┼───────────▶│ │ └─────────────────────────┘
└──────────────────────┘ Tailscale └───────────────────────────┘ TailscaleContents
Related MCP server: Keepygaga RAG
How it works
The index. DigUp keeps one SQLite file, ~/Library/Application Support/DigUp/index.noindex/index.sqlite, plus
code.sqlite beside it if code search is on. The file holds every indexed file, a 768-dimension EmbeddingGemma 2
vector for every photo, PDF page, document passage, video frame and 30 seconds of audio, and a full-text keyword
index. This server reads that format directly (DigUp's schema 5) and never writes to it.
The sync. DigUp writes to its index while it runs, so copying the file as-is could catch it halfway through a
write. A launchd job on the laptop runs digup-sync.sh every 15 minutes. It:
takes a consistent snapshot with SQLite's backup API, including changes still in DigUp's write-ahead log;
runs an integrity check on the snapshot and turns it into one self-contained file;
sends it to the Pi with rsync over SSH. Only changed blocks cross the network, and rsync renames the finished file into place, so the Pi never sees half a file;
writes
manifest.json(when and from where), which lets the Pi tell how fresh its copy is.
If the laptop is asleep, nothing runs. launchd runs a missed sync when the laptop wakes. If the Pi can't be reached, the job logs it and tries again next time. If the files haven't changed, nothing is uploaded and only the "last confirmed" time is updated.
The search. On the Pi, hermes-digup loads the vectors into memory and watches the index file. When a new copy
lands, it loads and checks the new copy first, then switches to it. If the new copy won't open, the old one keeps
answering and index_status reports why the new one was refused. Each query goes to Mac no.2 to be embedded with
DigUp's prompt (task: search result | query: …). The ranking is DigUp's own, ported from its Swift code:
every vector is scored, with scores centred for each modality (text, image, audio);
a file has to stand out by meaning (z ≥ 2) or contain at least half of the query's words;
word matches add a bonus, which is why exact invoice numbers, names and error codes still work;
words that name a kind of file ("…in a video", "screenshot of…") give a small boost.
The embeddings. Query vectors only make sense if they come from the same model DigUp used for the index. DigUp
runs an 8-bit llama.cpp build of EmbeddingGemma 2, and it checked that build against mlx-vlm running Google's own
weights (google/embeddinggemma-2): cosine 0.999 or better on text. oMLX loads EmbeddingGemma 2 through that same
mlx-vlm, so the setup script on Mac no.2 serves Google's weights with your oMLX. oMLX adds no prompt of its own; the
Pi adds DigUp's. hermes-digup doctor proves the match: it re-embeds passages from your index and compares them with
the vectors DigUp stored. A cosine of about 1.0 means the two are identical.
What you need
Machine | Needs |
Mac no.1 (laptop) | DigUp installed, with its first indexing pass done. Stock macOS tools only: bash, sqlite3, rsync, ssh. |
Mac no.2 (server) | Apple Silicon, oMLX (the menu bar app or the Homebrew install) with a main API key set, about 1.5 GB free disk for the model and about 1 GB of memory to keep it loaded. |
Raspberry Pi | Hermes Agent, Raspberry Pi OS Bookworm or newer (Python 3.10+), rsync, and room for about two copies of the index (DigUp shows its size in Settings). A Pi 4 or 5 with 2 GB+ RAM. |
All three | Tailscale, on the same tailnet. |
Clone this repository on the Pi and on both Macs (or copy the deploy/ folder to the Macs):
git clone https://github.com/georgeslane/hermes-indexing-mcp-server.git
cd hermes-indexing-mcp-serverSetup
Do the steps in this order: the Pi needs oMLX's address, key and model name from step 2, and the laptop needs the Pi set up before it can sync.
1. Tailscale names
Find each machine's Tailscale name (MagicDNS) with tailscale status or in the Tailscale admin console. This guide
uses these example names, so swap in yours:
macbook: Mac no.1mac-mini: Mac no.2raspberrypi: the Pi, with the userpi
If MagicDNS is off, use the 100.x.y.z Tailscale IPs instead. Both work everywhere a name is asked for.
2. Mac no.2: the embedding server
oMLX needs a main API key before it can be reached from another machine. If you haven't set one, set it in the menu
bar app (Settings → Security) or in the web admin at http://127.0.0.1:8000/admin. Then, on the always-on Mac:
deploy/mac-server/setup-embedding-server.sh --exposeThe script only uses oMLX's admin API, so your other models and settings stay as they are. It:
finds your oMLX: its port and main key from
~/.omlx/settings.json(or the folder the app uses), and starts it if it isn't running;with
--expose, makes oMLX listen on the network (0.0.0.0) instead of only on this Mac, and restarts it. oMLX then refuses every request that has no key. Leave--exposeout if oMLX already listens on the network;downloads
google/embeddinggemma-2with oMLX's own downloader, unless oMLX already has it;pins the model, so oMLX keeps it loaded (text only, about 0.9 GB) and never unloads it for being idle. The first search after a quiet night is as quick as any other;
creates an oMLX sub key named
hermes-digupfor the Pi. A sub key can run models but can't change oMLX's settings, and you can revoke it on its own. It's saved in~/.config/hermes-digup/embedding-server.env, and a later run reuses it;embeds a test query the way the Pi will, and checks that the vector has 768 dimensions;
prints the
DIGUP_EMBED_URL,DIGUP_EMBED_API_KEYandDIGUP_EMBED_MODELlines for the Pi. Keep them for step 3.
Options:
--admin-key KEY: if the main key isn't in oMLX's settings file (for example, you set it withOMLX_API_KEY).--port 8000: if the script can't tell which port oMLX uses.--hf-token TOKEN: if Hugging Face asks you to accept the model's license first.--model-id NAME: to use an EmbeddingGemma 2 that oMLX already has under another name.--uninstall: deletes the Pi's sub key and unpins the model.
Don't quantize EmbeddingGemma 2 with oQ for this. A re-quantized copy can drift away from DigUp's vectors; doctor
would show it.
Keep this Mac available:
Never sleep: System Settings → Energy → turn on "Prevent automatic sleeping when the display is off". Or run
sudo pmset -a sleep 0.oMLX after a restart: the menu bar app starts its server at launch, so add oMLX to System Settings → General → Login Items, and turn on automatic login (Users & Groups) if the Mac runs without a screen. With Homebrew,
brew services start omlxdoes the same. Both restart oMLX if it crashes.Firewall: if the macOS firewall is on, allow oMLX the first time macOS asks.
From the Pi, check that you can reach it:
curl -s http://mac-mini:8000/health # {"status":"healthy",...}3. The Pi: install the MCP server
On the Pi, with the values step 2 printed:
deploy/pi/install.sh --embed-url http://mac-mini:8000,http://100.x.y.z:8000 \
--api-key <key from step 2> --model <model name from step 2>oMLX names a model after its folder, so the name depends on how the model was downloaded: for example
embeddinggemma-2, or embeddinggemma-2-bf16 for a community bf16 copy. If the name is wrong anyway, the Pi asks
oMLX for its model list and uses the EmbeddingGemma 2 it finds there (unquantized first). doctor then says which
name it used.
DIGUP_EMBED_URL takes several addresses, comma-separated, tried in order. Giving the MagicDNS name and the IP means
a DNS hiccup doesn't stop meaning search.
The script:
checks Python, and that this Python's SQLite can read DigUp's keyword index (FTS5 with DigUp's tokenizer);
installs
python3-venvandrsyncwith apt if they're missing;installs the server into its own virtualenv at
~/.local/share/hermes-digup/venvand links the command to~/.local/bin/hermes-digup;creates
~/.local/share/hermes-digup/index/, where the laptop will put the index;writes
~/.config/hermes-digup/config.env. The MCP server andhermes-digup doctorboth read this file, so the agent and your shell always use the same settings;installs the
digup-searchHermes skill into~/.hermes/skills/productivity/digup-search/(see Making Hermes search here first). If the desktop app uses a profile other than the default one, add--profile NAME.
Make sure SSH is on, so the laptop can log in to copy files: sudo raspi-config → Interface Options → SSH.
4. Mac no.1: sync DigUp's index to the Pi
On the laptop that runs DigUp, first give it a passwordless SSH key for the Pi. launchd can't type passwords:
ssh-keygen -t ed25519 -f ~/.ssh/hermes_digup -N ""
ssh-copy-id -i ~/.ssh/hermes_digup.pub pi@raspberrypiThen install the sync job:
deploy/mac-laptop/install.sh --pi pi@raspberrypi --key ~/.ssh/hermes_digupThe script:
copies
digup-sync.shto~/Library/Application Support/hermes-digup/, so the job keeps working even if you move or delete the repository;writes
~/.config/hermes-digup/sync.env;checks that SSH to the Pi works without a password and that the Pi has rsync;
installs the launchd job
com.hermes-digup.sync, which runs at login, then every 15 minutes, and after a wake when a run was missed. The first sync starts right away.
--interval 600 changes how often it syncs, in seconds. Ten to thirty minutes is a sensible range. DigUp makes a new
file searchable within seconds, so a shorter interval means fresher results on the Pi. Each run is cheap: an
unchanged index sends nothing, and a changed one sends only the changed blocks.
The sync finds the index on its own. It looks at DIGUP_INDEX_DIR in sync.env, then at DigUp's own "index folder"
setting, then at DigUp's default location.
To run a sync by hand and watch it:
~/Library/Application\ Support/hermes-digup/digup-sync.sh --force -v
tail -f ~/Library/Logs/hermes-digup-sync.logTailscale SSH (
tailscale set --sshon the Pi) also works with no keys at all. But the default tailnet policy asks you to sign in again in a browser every 12 hours ("check" mode), which breaks unattended syncs. Plain SSH keys are the reliable choice, unless your policy uses"action": "accept".
5. Add it to Hermes
Hermes runs on the Pi, so the MCP server runs there too, as a subprocess of Hermes. On the Pi, run:
hermes mcp add digup --command ~/.local/bin/hermes-digup --args serveor add this to ~/.hermes/config.yaml:
mcp_servers:
digup:
command: "/home/pi/.local/bin/hermes-digup" # the path install.sh printed
args: ["serve"]
timeout: 60
connect_timeout: 30Then run /reload-mcp in a Hermes chat. A running gateway notices the config change by itself within about a
minute. You can also add the server from the Hermes desktop app on the laptop: Settings → MCP servers, with your
Pi's gateway selected. MCP setup always applies to the gateway the app is connected to, which here is the Pi.
All settings come from ~/.config/hermes-digup/config.env, so this block needs no env:. Any setting can still be
overridden there; for example, env: {DIGUP_STALE_HOURS: "12"}.
Hermes registers the tools as mcp_digup_search_files, mcp_digup_read_passage and so on. Every tool is marked
read-only.
6. Check it
On the Pi:
hermes-digup doctor[ok ] index /home/pi/.local/share/hermes-digup/index/index.sqlite: 18342 files, 61877 vectors, schema 5
[ok ] last synced 0.2 hours ago
vectors made by: google/embeddinggemma-2@bfcd298 llama.cpp-vectors1 text-q8_0+mmproj-q8_0
[ok ] embedding server http://mac-mini:8000 answered in 38 ms
[ok ] query vectors vs DigUp's: mean cosine 0.9996 over 6 stored passages (match)
all goodThen search from the shell:
hermes-digup search "the clause about pets in the lease"
hermes-digup search "screenshot of the payment declined error" --kind screenshot
hermes-digup search "INV-2026-0042"
hermes-digup read 48213 # the text of a result's segment_idThen ask Hermes, from the desktop app on your laptop, something like "find the PDF where my landlord talks about pets", "what did that podcast say about sleep, and when?" or "summarise the latest bank statement in Downloads".
What Hermes can do with it
Tool | What it does |
| Hybrid search (meaning plus exact words). Takes optional |
| The same over DigUp's code index ("retry a failed call with backoff"), if code search is on in DigUp. Uses DigUp's code-retrieval prompt. |
| The full text of a match (a PDF page, a document passage, a screenshot's text, a stretch of code), with up to 3 neighbouring passages on each side. |
| All the text DigUp extracted from one file, in order, in pages ( |
| Lists indexed files, newest first, filtered by folder, kind or part of the name ("my latest screenshots"). |
| How fresh the copy is, what's in it, and whether the embedding server answers. |
Pictures, audio and video are found by meaning. DigUp keeps vectors for them, not words, so read_* has no text for
them, except for screenshots, whose text DigUp reads on a Mac. Paths are paths on the laptop, which you can open from
there.
Making Hermes search here first
Out of the box Hermes can take a while to reach for these tools. It tends to search the Pi's own disk first with its built-in file tools, which can never find your Mac's files. Two things cause this:
Tool Search. Hermes's Tool Search is on by default. It keeps every MCP tool out of the model's tool list, showing only a one-line preview of each, while its own file tools are always loaded.
What the model knows. Nothing tells the model that your files live on another machine.
These settings fix both. They're in order of effect; the first two together are usually enough.
1. Pin the digup-search skill (deploy/pi/install.sh installs it). Its full text then goes into the system
prompt of every new chat. It tells the model that your files are on your Mac, to use the digup tools first and not
search this machine's disk, and how to read a match before quoting it. In the Pi's ~/.hermes/config.yaml (or the
profile's), add:
skills:
auto_load:
- digup-searchIt costs about 1,000 tokens per chat. Without pinning, the skill still appears in Hermes's skill list and the model is told to load matching skills, so it helps either way; pinned, it's certain.
2. Tell Hermes to remember it. Memory is also part of every chat's system prompt. In any chat, say:
Remember: my files are on my Mac, not on this machine. To find, read or summarise any of my files, use the digup tools first, never the local filesystem or terminal.
3. Load the tools directly (optional). If digup is your only MCP server, or you have just a few, turn Tool Search off so their full descriptions are always in front of the model:
tools:
tool_search:
enabled: offThis loads every MCP server's tools into every chat, so skip it if you have large servers attached. Even with Tool Search on, each tool's preview starts by saying what it's for (for example, "Find the user's Mac files by content; use it first.").
Changes apply to new chats: start one in the desktop app after editing.
When something is down
Situation | What happens |
Laptop asleep, off, or away | The Pi keeps searching its last copy. When the copy is older than |
Pi unreachable during a sync | The sync logs it and exits. The next run sends everything that changed. |
Sync killed halfway (laptop sleeps, network drops) | rsync never put a partial file in place. The Pi still has the previous complete copy, and the next run finishes the job. A leftover lock is cleared automatically. |
A broken or unexpected file arrives | The new copy is checked before it's used. If it fails, the old one keeps answering and |
Mac no.2 asleep, rebooting, or unreachable | Each embedding address gets a 3-second connection check, then the next address is tried. If none answers, the search runs on exact words only, and the result says |
The embedding server returns the wrong kind of vector (wrong model, wrong size, token vectors) | A clear error saying what's wrong. |
DigUp turns code search off | The next sync removes |
DigUp is updated and its index schema changes | Known schemas load normally. A newer one is tried anyway, with a warning in results and status asking you to update hermes-digup. |
The same question twice | Its vector is cached (the last 512 queries), so Mac no.2 isn't asked again. |
Settings
Pi: ~/.config/hermes-digup/config.env. Environment variables override this file, and DIGUP_CONFIG points to
a different one.
Setting | Default | Meaning |
|
| Folder holding |
| (none) | Embedding server base URL(s), comma-separated, tried in order. Without one, only keyword search works. |
| (none) | Bearer token for the embedding server. |
|
|
|
|
| The model's name in oMLX, as the setup script printed it. If the server has no model by that name, its EmbeddingGemma 2 is used, whatever it's called. |
|
| Seconds to wait for a vector once connected. |
|
| Age after which results mention that the index is old. |
| auto |
|
|
| Logs go to stderr, which Hermes saves in |
Laptop: ~/.config/hermes-digup/sync.env
Setting | Default | Meaning |
| (required) |
|
|
| Where the copy goes on the Pi, relative to the Pi user's home. Must match |
| (none) | Private key to use (a path without spaces). |
| auto | Only if DigUp's index lives somewhere unusual. |
|
|
|
|
| Also copy |
To change the sync interval, run install.sh --interval <seconds> again.
Troubleshooting
hermes-digup doctor says there's no index. The laptop hasn't synced yet. Run
~/Library/Application\ Support/hermes-digup/digup-sync.sh --force -v on it and read the output. The usual causes:
SSH asks for a password: set up the key from step 4.
rsync is missing on the Pi:
sudo apt install rsync.DigUp hasn't finished its first pass yet.
"the Pi isn't reachable" in the sync log. Check that ssh pi@raspberrypi works from the laptop and that
Tailscale is connected on both machines.
The sync log says it can't open the index ("operation not permitted"). You moved DigUp's index into a folder
macOS protects, such as Documents or Desktop. Give /bin/bash Full Disk Access (System Settings → Privacy &
Security), or keep the index in its default place.
The comparison says "mismatch" (cosine well below 1). oMLX isn't making DigUp's vectors. Check that
DIGUP_EMBED_MODEL names Google's google/embeddinggemma-2 as oMLX downloaded it, not an oQ-quantized or
community-converted copy. Re-running setup-embedding-server.sh sets this up. A "close" result (0.9 to 0.98) still
searches, but ranks a little worse than DigUp.
Searches say "keywords only". Mac no.2 didn't answer. Check that oMLX is running there (its menu bar icon, or
brew services info omlx), and look at ~/.omlx/logs/server.log. From the Pi, run
curl http://mac-mini:8000/health. If it only answers on Mac no.2 itself, oMLX listens on 127.0.0.1: run the setup
script again with --expose.
HTTP 401. DIGUP_EMBED_API_KEY on the Pi doesn't match the sub key in Mac no.2's
~/.config/hermes-digup/embedding-server.env, or the sub key was deleted in oMLX. Re-running the setup script makes
a new one.
"no model 'embeddinggemma-2'" or "It has no EmbeddingGemma 2 model". When DIGUP_EMBED_MODEL isn't the model's
name in oMLX, the Pi uses the server's EmbeddingGemma 2 under its own name, and doctor shows a [warn] line with the
setting to put in config.env. If oMLX has no EmbeddingGemma 2 at all, the error lists the models it has: run the
setup script on Mac no.2 to download it.
Hermes doesn't show the tools. Run hermes mcp test digup, check ~/.hermes/logs/mcp-stderr.log, and make sure
the command: path exists (ls ~/.local/bin/hermes-digup). Run /reload-mcp after changes.
A result is missing. First check that DigUp itself finds it on the laptop. If it does, the Pi's copy is just
older: run index_status, or hermes-digup status, to see when it was last synced.
Other setups
Ollama, LM Studio or llama.cpp on Mac no.2 instead of oMLX. Set DIGUP_EMBED_URL to the server and
DIGUP_EMBED_MODEL to its name for EmbeddingGemma 2. With llama.cpp, the closest match is the file DigUp itself
runs: ggml-org/embeddinggemma-2-GGUF's embeddinggemma-2-Q8_0.gguf, served by
llama-server --embedding --pooling mean -c 2048 -ub 2048. For Ollama, either set DIGUP_EMBED_API=ollama (its native
/api/embed) or keep openai with the URL ending in /v1. Then run hermes-digup doctor and look at the
comparison line. Only a "match" means search will rank like DigUp.
Running the server on the laptop itself. Point DIGUP_INDEX_DIR at DigUp's own folder
(~/Library/Application Support/DigUp/index.noindex). The server sees DigUp's write-ahead log, opens the live index
read-only next to DigUp, and picks up DigUp's changes as they happen, with no sync needed. This is handy for trying
things out, or if Hermes ever runs on the Mac.
Hermes reaching the server over HTTP. Run hermes-digup serve --http --host 100.x.y.z --port 8765 (for example
under systemd) and use url: "http://100.x.y.z:8765/mcp" in Hermes's config instead of command:.
Updating and uninstalling
Update:
git pull, then on the Pi rundeploy/pi/install.shagain (it keeps your config). On the Macs, run their scripts again.Pi: remove the
digupentry from Hermes, thenrm -rf ~/.local/share/hermes-digup ~/.config/hermes-digup ~/.local/bin/hermes-digup.Laptop:
deploy/mac-laptop/install.sh --uninstall.Mac no.2:
deploy/mac-server/setup-embedding-server.sh --uninstalldeletes the Pi's sub key and unpins the model. To free its disk space too, delete EmbeddingGemma 2 on oMLX's Models page.
Development
python3 -m venv .venv && .venv/bin/pip install -e '.[test]'
.venv/bin/pytestThe tests build indexes with DigUp's exact schema and run the real embedding client against a fake embedding server. They cover:
meaning, keyword and lookup ranking;
filters;
reading pages and documents (chunk overlaps merged);
hot reload, and refusing a broken copy;
a live WAL-mode index;
failover, timeouts and the keyword fallback;
Ollama and API keys;
the vector comparison;
the sync script, against a local stand-in for the Pi;
the oMLX setup script, against a fake oMLX (its JavaScript JSON helper runs under Node instead of
osascript);the Hermes skill: Hermes's authoring rules, that it names exactly the server's tools, and that every tool's preview fits Tool Search's 60 characters.
Layout:
src/hermes_digup/
index.py reads DigUp's SQLite index, validates and hot-reloads snapshots
search.py DigUp's hybrid ranking (Searcher.swift), ported
textnorm.py DigUp's query normalisation, stopwords and kind words (SearchText.swift)
embed.py EmbeddingGemma 2 client: DigUp's prompts, failover, cache
service.py the tools' logic; server.py exposes it over MCP; cli.py the shell commands
hermes/skills/
digup-search/ the Hermes skill that makes the agent search the Mac's files here first
deploy/
mac-laptop/ digup-sync.sh + launchd installer (Mac no.1)
mac-server/ oMLX setup for EmbeddingGemma 2, through its admin API (Mac no.2)
pi/ virtualenv installer and Hermes config (Raspberry Pi)DigUp's index format isn't a public API, so a DigUp update could change it. The reader checks the schema version and tells you when it sees one it doesn't know.
License
MIT. DigUp is MIT licensed, EmbeddingGemma 2 is Apache 2.0, and oMLX is Apache 2.0. This project isn't affiliated with DigUp, oMLX, Google or Nous Research.
Available Tools
6 toolsbrowse_filesARead-onlyIdempotent
List the user's Mac files, newest first.
Filter by folder, kind or part of the name: 'my latest screenshots', 'PDFs in Downloads'.
| Name | Required | Description | Default |
|---|---|---|---|
| kind | No | Only this kind of file. | |
| limit | No | ||
| folder | No | Only files under this folder on the Mac. | |
| name_contains | No | Part of the file or folder name. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly, idempotent, non-destructive and closed-world, so the safety profile is covered. The description adds the non-obvious behavior of result ordering ('newest first') and the scope (the user's Mac), but says nothing about result caps, pagination, or what happens when no filter matches.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two short sentences, ordering stated first where it matters, then the filter capability demonstrated with concrete query phrasings. Nothing is padded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
There is no output schema, so the description should hint at what a result record contains and how the default limit affects results; it does neither. It is adequate for selecting the tool but leaves gaps an agent must discover by calling it.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 75%: kind, folder and name_contains are documented in the schema, while limit is undocumented in both places. The description's examples helpfully map natural user phrasing onto the filter parameters, but it never mentions the limit parameter or its default of 20.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('List the user's Mac files') plus an ordering guarantee ('newest first'), which an agent cannot infer from the schema. It does not distinguish itself from the sibling search_files tool, so an agent cannot tell from the description alone which of the two to pick when the request is name-based.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The inline examples ('my latest screenshots', 'PDFs in Downloads') implicitly show the intended use case of browsing with a filter, but there is no explicit statement of when to use this versus search_files or read_file. Usage is inferred rather than declared.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
index_statusARead-onlyIdempotent
Check how fresh the user's Mac file index is.
When it was last synced, what's in it, and whether the embedding server answers.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare read-only, idempotent, non-destructive behavior, so safety is covered. The description adds real value by disclosing the three things reported back: last sync time, index contents, and embedding-server reachability — the last being a useful dependency health signal an agent would not otherwise know.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three short lines, front-loaded with the core question and then the specific sub-checks. Minor ambiguity in 'what's in it', but no wasted text.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a parameterless diagnostic with no output schema, the description adequately signals what comes back. It could add what an agent should do when the embedding server is unreachable, but that is a small gap.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Zero parameters, so the baseline is 4; there is nothing for the description to clarify beyond confirming this is a no-argument check.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb ('Check') and resource ('the user's Mac file index') with clear scope. It is distinguishable from all siblings, which are search/read tools, though the description never explicitly names them.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is only implied: an agent infers this is a pre-flight/diagnostic check before searching or reading, but there is no statement of when to call it, when not to, or which sibling to prefer when the index looks stale.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
read_fileARead-onlyIdempotent
Read all the text of one of the user's Mac files.
Everything DigUp extracted from the file (PDF pages, document text, slide and sheet text, screenshot text), in order, in pages of max_chars. Use it for paths from a digup search, which don't exist on this machine. Pictures, audio and video have no text.
| Name | Required | Description | Default |
|---|---|---|---|
| code | No | True for a file from search_code. | |
| path | Yes | A path exactly as a search returned it. | |
| offset | No | Character offset to continue from (next_offset). | |
| max_chars | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly, idempotent, non-destructive and closed-world, so the safety profile is covered. The description adds genuinely new behavior: results come back in document order as paginated character windows, and images/audio/video yield no text at all. It doesn't mention any size or rate limits beyond the schema's max_chars cap.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three short sentences, front-loaded with the core action, then scope, then the important caveats. No sentence is filler; even the closing line about pictures and audio prevents a wasted call.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description carries the burden of explaining returns, and it does: ordered text in max_chars pages. A note on what happens when the offset exceeds the content, or confirmation that next_offset is returned, would close the last gap.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 75% and the schema already documents path, code and offset well. The description reinforces the pagination model ('in pages of max_chars') and the search-derived origin of paths, but adds nothing about how offset/next_offset chains together or how max_chars should be tuned.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Read all the text of one of the user's Mac files') and scopes it to DigUp-extracted text from PDFs, documents, slides and sheets. It does not explicitly distinguish itself from the sibling read_passage, which is the main remaining ambiguity for an agent choosing between the two.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
'Use it for paths from a digup search, which don't exist on this machine' gives a clear positive condition and heads off a likely mistake (treating the path as a local filesystem path). It offers no explicit exclusion or alternative for when read_passage would be the better choice.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
read_passageBRead-onlyIdempotent
Read the text of a digup search match (segment_id).
The full text of the matched page, passage or code stretch, as DigUp read it.
| Name | Required | Description | Default |
|---|---|---|---|
| code | No | True for a segment_id from search_code. | |
| context | No | Also return this many neighbouring pages or passages on each side. | |
| segment_id | Yes | segment_id from a search result. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint, destructiveHint=false and openWorldHint=false, so the safety profile is covered. The description adds useful behavioral context about what is returned ('the full text ... as DigUp read it'), but says nothing about failure modes for a stale or invalid segment_id, nor how the context parameter expands results.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two short sentences with the resource and key argument front-loaded. The second sentence is close to redundant with the first but does clarify the granularity of the returned text, so it earns partial credit.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description does discharge its duty to say what comes back (full page/passage/code text). Gaps remain: nothing about invalid or expired segment_ids, nothing about how context>0 changes the shape of the response, and no routing versus the other read tools.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so all three parameters are already documented in the schema, including the code and context semantics. The description only restates that segment_id identifies the search match, adding no syntax or format detail beyond the structured fields; a baseline 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description names a specific verb and resource ('Read the text of a digup search match') and pins it to a segment_id, which is more than a tautology. However, it never distinguishes this tool from the sibling read_file, so an agent cannot tell from the description alone which one retrieves passage text versus file text.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
'Read the text of a digup search match (segment_id)' implies a search must happen first and that this is a follow-up fetch, but the dependency is only implied, not stated. There is no explicit when-to-use/when-not guidance and no mention of read_file as the alternative for non-search content.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_codeARead-onlyIdempotent
Find code in the user's Mac projects by what it does.
Searches DigUp's code index (if code search is turned on in DigUp). Returns files with line ranges; read them with read_passage(code=true) or read_file(code=true).
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | ||
| query | Yes | What the code does ('retry a failed call with backoff') or a name that's in it. | |
| folder | No | Only files under this folder on the Mac. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint, destructiveHint=false and openWorldHint=false, so the safety profile is covered. The description usefully adds that results are references only (files with line ranges, not content) and that the index may be disabled — genuinely beyond the annotations — but says nothing about ranking, partial results, or empty-result behavior.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three short sentences, purpose first, then the availability caveat, then the next-step call. Nothing repeats the name or title and nothing is filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description correctly describes the return shape (files with line ranges) and the follow-up tools needed to consume results, plus the index-enabled prerequisite. Minor gaps remain around result count limits and what happens when nothing matches, but the essentials for a correct call are present.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 67% and the query parameter already carries a strong example ('retry a failed call with backoff'), so the schema does the heavy lifting. The description adds no meaning for limit or folder beyond their schema text; baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource with scope: 'Find code in the user's Mac projects by what it does,' which signals semantic code search rather than name matching. It doesn't explicitly name the sibling search_files to draw the contrast, so differentiation is inferable but not spelled out.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives a real precondition ('if code search is turned on in DigUp') and routes the agent forward to read_passage(code=true) or read_file(code=true) for the returned ranges. It doesn't say when to prefer this over search_files, so the alternative-routing half of the guidance is missing.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_filesARead-onlyIdempotent
Find the user's Mac files by content; use it first.
Searches DigUp's index of the user's Mac, by meaning and exact words: documents, PDFs page by page, photos and screenshots by what they show, audio and video by what's said, with timestamps. Use it first for any question about the user's files; they aren't on this machine, so the terminal and the local file tools can't find them. Returns the best matching files with where the match is (page, timestamp, chunk), an excerpt, and a segment_id for read_passage. Results marked weaker_match trail far behind the top ones.
| Name | Required | Description | Default |
|---|---|---|---|
| kinds | No | Only these kinds of file. | |
| limit | No | How many files to return. | |
| query | Yes | What to find, described in plain words, or exact words/codes that are in the file. | |
| folder | No | Only files under this folder on the Mac, e.g. '~/Documents/Taxes'. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint, and non-destructive, so safety is covered. The description adds useful behavioral context beyond that: content coverage (PDFs page-by-page, photos, audio/video transcripts with timestamps), the return shape (match location, excerpt, segment_id), and the meaning of the 'weaker_match' flag. It does not discuss ranking limits beyond the 'weaker_match' hint or pagination behavior.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The routing instruction ('use it first'/'they aren't on this machine') is front-loaded, followed by scope, then return format. No sentence is filler – each adds a distinct piece of information the agent can act on.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
There is no output schema, so the description's prose about return contents (match location, excerpt, segment_id, weaker_match) carries the burden and does so adequately. Combined with explicit usage guidance, nothing essential is missing for correct invocation.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so all four parameters already have definitions, establishing a baseline of 3. The description adds a little interpretive value by clarifying that the query works both semantically ('by meaning') and as exact words/codes, which helps frame how to phrase a query.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Find the user's Mac files by content') and immediately scopes it as index-based semantic + keyword search. It explicitly distinguishes itself from the terminal/local file tools and from browse_file-style siblings by naming its role as the entry point.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives an explicit rule: 'use it first for any question about the user's files' and explains why the alternatives fail ('they aren't on this machine'). It also names read_passage as the follow-up consumer of the returned segment_id, routing the agent across siblings without ambiguity.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
6 tool updates
v0.1.0- First observed
browse_files - First observed
index_status - First observed
read_file - First observed
read_passage - First observed
search_code - First observed
search_files
TDQS
Scored across 6 tools
Each tool targets a distinct action/resource: index health, semantic file search, semantic code search, passage reading, whole-file reading, and file listing. The overlap between read_passage and read_file is clearly resolved by segment_id vs whole-file path semantics.
All names use consistent snake_case with predictable verb_noun or noun_status patterns. The verbs search_, read_, browse_, and index_ are used coherently and reflect their operations.
Six tools is well-scoped for a file-index search/read server. Each tool has a clear role, and there are no redundant or filler tools.
The surface covers the lifecycle needed for this domain: checking index status, discovering files via search or browsing, searching code separately, and reading either matched passages or complete files. No obvious gaps for the stated purpose.
Maintenance
Related MCP Connectors
Securely search and manage workspace context files for AI agents and teams.
Agentic search over your Dewey document collections from any MCP-compatible client.
Search your knowledge bases from any AI assistant using hybrid RAG.
Read and search folders on your own computer with explicit consent. Files are never uploaded.
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceEnables AI agents to search a Mac's documents by content using a local index, returning ranked paths and matching snippets. It supports formats like PDF, DOCX, HWP, and OCR'd scans, with Korean-aware search, while keeping everything offline and without requiring filesystem access.246 npmMIT
- AlicenseAqualityCmaintenanceEnables local-first hybrid knowledge retrieval from authorized Markdown and plain-text files, combining full-text and vector search with reranking and traceable source references via a single search tool.1MIT
- AlicenseAqualityBmaintenanceEnables natural-language search over locally indexed files such as markdown, text, images, videos, and PDFs, and retrieves indexed text or media metadata by path. It lets Cursor query a local embedding index built with Gemini and SQLite.2MIT
- FlicenseNot gradedqualityAmaintenanceEnables AI agents and MCP hosts to search a locally indexed second brain built from scattered notes, docs, PDFs, and chat exports, returning hybrid vector-plus-keyword results with section-level citations rather than whole files.-