Skip to main content
Glama
georgeslane

hermes-digup

by georgeslane

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  └───────────────────────────┘ Tailscale

Contents

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:

  1. takes a consistent snapshot with SQLite's backup API, including changes still in DigUp's write-ahead log;

  2. runs an integrity check on the snapshot and turns it into one self-contained file;

  3. 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;

  4. 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-server

Setup

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.1

  • mac-mini: Mac no.2

  • raspberrypi: the Pi, with the user pi

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 --expose

The 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 --expose out if oMLX already listens on the network;

  • downloads google/embeddinggemma-2 with 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-digup for 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_KEY and DIGUP_EMBED_MODEL lines 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 with OMLX_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 omlx does 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-venv and rsync with apt if they're missing;

  • installs the server into its own virtualenv at ~/.local/share/hermes-digup/venv and 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 and hermes-digup doctor both read this file, so the agent and your shell always use the same settings;

  • installs the digup-search Hermes 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@raspberrypi

Then install the sync job:

deploy/mac-laptop/install.sh --pi pi@raspberrypi --key ~/.ssh/hermes_digup

The script:

  • copies digup-sync.sh to ~/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.log

Tailscale SSH (tailscale set --ssh on 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 serve

or 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: 30

Then 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 good

Then 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_id

Then 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

search_files

Hybrid search (meaning plus exact words). Takes optional kinds (screenshot, image, pdf, doc, audio, video), folder (~/Documents/Taxes means that folder on the Mac) and limit. Each result gives the path on the Mac, kind, score, where the match is (page, a timestamp for audio/video, chunk, lines), an excerpt with matched words marked «like this», the date modified, and a segment_id.

search_code

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.

read_passage

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.

read_file

All the text DigUp extracted from one file, in order, in pages (offset, next_offset). Lets the agent read or summarise a PDF or document even though the file itself is on the laptop.

browse_files

Lists indexed files, newest first, filtered by folder, kind or part of the name ("my latest screenshots").

index_status

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-search

It 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: off

This 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 DIGUP_STALE_HOURS (default 36), results include a note saying so. The laptop syncs when it wakes.

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 index_status shows newer_copy_refused with the reason. The sync also runs an integrity check before uploading anything.

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 "mode": "keywords only" with a note. Meaning search comes back on its own.

The embedding server returns the wrong kind of vector (wrong model, wrong size, token vectors)

A clear error saying what's wrong. doctor's comparison catches a model that's merely different.

DigUp turns code search off

The next sync removes code.sqlite from the Pi too.

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

DIGUP_INDEX_DIR

~/.local/share/hermes-digup/index

Folder holding index.sqlite, code.sqlite and manifest.json.

DIGUP_EMBED_URL

(none)

Embedding server base URL(s), comma-separated, tried in order. Without one, only keyword search works.

DIGUP_EMBED_API_KEY

(none)

Bearer token for the embedding server.

DIGUP_EMBED_API

openai

openai for oMLX (and llama-server, LM Studio, or Ollama's /v1); ollama for Ollama's /api/embed.

DIGUP_EMBED_MODEL

embeddinggemma-2

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.

DIGUP_EMBED_TIMEOUT

15

Seconds to wait for a vector once connected.

DIGUP_STALE_HOURS

36

Age after which results mention that the index is old.

DIGUP_FLOAT32

auto

1 keeps vectors as float32 (about 4× faster scoring, twice the memory), 0 keeps float16. Auto picks float32 when it fits in a quarter of free memory.

DIGUP_LOG_LEVEL

INFO

Logs go to stderr, which Hermes saves in ~/.hermes/logs/mcp-stderr.log.

Laptop: ~/.config/hermes-digup/sync.env

Setting

Default

Meaning

PI_HOST

(required)

user@host for SSH.

PI_INDEX_DIR

.local/share/hermes-digup/index

Where the copy goes on the Pi, relative to the Pi user's home. Must match DIGUP_INDEX_DIR.

SSH_KEY

(none)

Private key to use (a path without spaces).

DIGUP_INDEX_DIR

auto

Only if DigUp's index lives somewhere unusual.

SKIP_ON_BATTERY

0

1 skips syncing on battery.

SYNC_CODE_INDEX

1

Also copy code.sqlite when DigUp's code search is on.

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 run deploy/pi/install.sh again (it keeps your config). On the Macs, run their scripts again.

  • Pi: remove the digup entry from Hermes, then rm -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 --uninstall deletes 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/pytest

The 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 tools
browse_filesA
Read-onlyIdempotent

List the user's Mac files, newest first.

Filter by folder, kind or part of the name: 'my latest screenshots', 'PDFs in Downloads'.

NameRequiredDescriptionDefault
kindNoOnly this kind of file.
limitNo
folderNoOnly files under this folder on the Mac.
name_containsNoPart of the file or folder name.

TDQS

A3.5/5.0
Behavior3/5

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.

Conciseness5/5

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.

Completeness3/5

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.

Parameters3/5

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.

Purpose4/5

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.

Usage Guidelines3/5

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_statusA
Read-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.

NameRequiredDescriptionDefault

No parameters

TDQS

A3.8/5.0
Behavior4/5

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.

Conciseness4/5

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.

Completeness4/5

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.

Parameters4/5

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.

Purpose4/5

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.

Usage Guidelines3/5

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_fileA
Read-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.

NameRequiredDescriptionDefault
codeNoTrue for a file from search_code.
pathYesA path exactly as a search returned it.
offsetNoCharacter offset to continue from (next_offset).
max_charsNo

TDQS

A4/5.0
Behavior4/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters3/5

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.

Purpose4/5

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.

Usage Guidelines4/5

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_passageB
Read-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.

NameRequiredDescriptionDefault
codeNoTrue for a segment_id from search_code.
contextNoAlso return this many neighbouring pages or passages on each side.
segment_idYessegment_id from a search result.

TDQS

B3.1/5.0
Behavior3/5

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.

Conciseness4/5

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.

Completeness3/5

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.

Parameters3/5

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.

Purpose3/5

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.

Usage Guidelines3/5

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_codeA
Read-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).

NameRequiredDescriptionDefault
limitNo
queryYesWhat the code does ('retry a failed call with backoff') or a name that's in it.
folderNoOnly files under this folder on the Mac.

TDQS

A3.8/5.0
Behavior3/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters3/5

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.

Purpose4/5

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.

Usage Guidelines4/5

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_filesA
Read-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.

NameRequiredDescriptionDefault
kindsNoOnly these kinds of file.
limitNoHow many files to return.
queryYesWhat to find, described in plain words, or exact words/codes that are in the file.
folderNoOnly files under this folder on the Mac, e.g. '~/Documents/Taxes'.

TDQS

A4.5/5.0
Behavior3/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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.

  1. 6 tool updatesv0.1.0
    • First observedbrowse_files
    • First observedindex_status
    • First observedread_file
    • First observedread_passage
    • First observedsearch_code
    • First observedsearch_files

TDQS

A4/5.0

Scored across 6 tools

Disambiguation5/5

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.

Naming Consistency5/5

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.

Tool Count5/5

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.

Completeness5/5

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

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables 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 npm
    MIT
  • A
    license
    A
    quality
    C
    maintenance
    Enables 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.
    1
    MIT
  • A
    license
    A
    quality
    B
    maintenance
    Enables 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.
    2
    MIT
  • F
    license
    Not graded
    quality
    A
    maintenance
    Enables 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.
    -