Skip to main content

shelfmark

Give AI agents the right context, not your entire filesystem.

Shelfmark turns years of scattered documents into a governed context map for AI agents. An agent can discover what exists, understand what kind of material it is, and select the documents relevant to a task — before spending context opening files.

No document migration. No duplicated content store. No need for a perfect folder structure.

uv tool install shelfmark          # or: pipx install shelfmark

Local by design · metadata only · governed discovery · built for MCP


Your best knowledge is probably sitting in your folders

Reports, presentations, models, research, proposals and working documents accumulate over years. Some are carefully organised. Others sit inside crowded project folders, old archives, download directories, or collections that made sense only at the time.

The value is still there. The problem is that agents cannot use what they cannot discover — and giving an agent unrestricted filesystem access does not solve that. It transfers the work of finding, interpreting and filtering thousands of files into the context window.

Shelfmark gives the agent a map first.

Context is the scarce resource

An agent does not need every document. It needs to know what exists, what is likely to matter, where it came from, and whether it should be used at all.

Shelfmark is a discovery layer between the agent and your files. The agent searches the catalogue, narrows the field, and requests only the material the task actually needs — so an existing document estate becomes working context without turning the filesystem into one enormous prompt.

A catalogue of pointers, not another document store

Shelfmark builds a local SQLite catalogue of references and derived metadata: paths, filenames, formats, sizes, document types, authors, dates, classifications, selected Office properties, presentation titles, and optional content hashes.

It does not copy your documents into the catalogue. It does not index document body text. It does not create a second repository to govern, synchronise and maintain. Your files stay where they are; the catalogue points at them and describes what can be established from their metadata.

Finding a document does not expose its contents.

Turn messy folders into usable agent context

Shelfmark does not require a designed information architecture. Point it at accumulated project files, forgotten archives, or folders where documents have simply been dropped over the years.

It builds an inventory from signals that already exist — filenames, extensions, paths, authorship, dates, Office properties, and your own classification rules — creating a virtual structure across the material. Agents can then search by document type, client, project, purpose, author, year or location without anything being moved or renamed.

A messy folder can become navigable even when it never becomes tidy.

Shelfmark also stays honest about the limits of metadata. A file called final7.pdf with no useful properties cannot reveal its meaning without someone reading it. Shelfmark reports what is known rather than inventing certainty — the same reason it drops placeholder slide titles instead of listing twelve headings a deck does not have.

Built for selective context

  • Discover before opening. Search thousands of references before deciding which few files deserve attention.
  • Preserve the context window. Concise catalogue results instead of whole documents that may not be relevant.
  • Build on previous work. Reports, models, proposals, research and methods stay discoverable across new tasks and future engagements.
  • Keep provenance visible. Path, date, author, document type and surrounding context help an agent judge relevance.
  • Reduce blind exploration. Structured search instead of repeatedly walking directories and inspecting files one at a time.
  • Separate discovery from access. Shelfmark helps identify material; opening the original remains a separate, controllable decision.

Your roots are the trust boundary

Shelfmark walks only the roots you configure.

Symlinks are not followed. A link inside a root reads as an ordinary file and would otherwise walk straight out of the tree you configured — and hash opens files. Skipped links are reported, never silently dropped. To index another tree, add it as an extra root: the boundary widens by saying so in config, not by planting a link.

The catalogue is refused inside any root, because a database that indexes itself grows on every refresh. Both checks compare resolved paths, so .. and a symlinked root cannot slip past them.

Governance belongs in the retrieval layer

Not every useful document should be treated the same way. Shelfmark separates two questions that usually get confused:

  • Who owns or may reuse this?rights: OWN / REFERENCE / RESTRICTED
  • May this document leave its current context?confidential: 0 / 1

A method may belong to you while the client deliverable containing it stays confidential. Modelling the two separately lets agents discover reusable knowledge without treating everything discoverable as freely shareable.

Files matching your private/secret patterns become RESTRICTED: no tool returns their path, name, metadata or content, no argument overrides it, they are never opened for hashing, and the database is opened read-only. corpus_stats() reports a single corpus-wide count of sealed files and nothing else about them — not which root, not which folder. That count is the one thing disclosed, deliberately: silence about it would misrepresent the size of the corpus.

Governance is applied by the catalogue, not left to the wording of a prompt.

Know whether the map can be trusted

A search result is only useful if the agent knows the catalogue is current.

The MCP server keeps its own index current while it runs, so nothing has to be scheduled and no agent has to remember. When it cannot — never built, stale, a failed refresh, a clock it cannot reason from, or an index that no longer agrees with the filesystem — every tool says so above its answer, and corpus_stats() compares index against disk in full.

An old snapshot is never presented as complete knowledge.

Designed for knowledge-intensive work

Consultants and advisors Find previous analyses, proposals, frameworks and deliverables without exposing unrelated client material.
Researchers and analysts Navigate large collections of reports, datasets and source material through consistent metadata.
Product and strategy teams Reconnect decisions, research, roadmaps and previous thinking across projects and time.
Studios and independents Turn years of accumulated work into reusable context while keeping control over client files and IP.
Agent builders Give local agents a governed discovery layer over MCP.

How it works

  1. Point Shelfmark at your existing folders. One or more roots. Files stay where they are.
  2. Build the local catalogue. It walks the permitted roots, extracts available metadata, applies classification rules, and writes references into SQLite.
  3. Review ownership and confidentiality. shelfmark review asks a few questions about your own folders and writes the answers to config.
  4. Connect an MCP-compatible agent. It searches, browses and inspects catalogue records through structured tools.
  5. Retrieve only what matters. The agent identifies the relevant artefacts before any separate content access takes place.

Install

uv tool install shelfmark          # or: pipx install shelfmark
# from a checkout:
uv tool install /path/to/shelfmark

Python ≥ 3.11. macOS and Linux. Email ingestion is optional and pulls extra dependencies: uv tool install "shelfmark[email]".

Quickstart

shelfmark init                     # writes ~/.config/shelfmark/config.toml
$EDITOR ~/.config/shelfmark/config.toml   # set your [[roots]]
shelfmark refresh                  # first build (add --no-hash for a fast pass)
shelfmark review                   # answer a few questions -> rights get set
shelfmark stats                    # census of what it found

Register with Claude Code:

claude mcp add shelfmark -s user -- shelfmark-mcp

Then in a session: corpus_stats() to orient, browse_folder() to navigate, search_docs() / get_file() to find and inspect.

MCP tools

Tool What it answers
corpus_stats() What is here overall + an honest freshness line. Call first.
browse_folder(prefix) What is inside a folder: counts, sizes, facet mix.
search_docs(query, …) Metadata full-text search with facet filters.
get_file(path) Full record for one file: rights, authorship, slide titles, identical copies, on-disk status.
search_emails(query, …) Full-text over an ingested .pst/.msg email archive (optional).

Result lists always say when they are cut (showing 100 of 195 …), unknown filter values are reported as bad filters with suggestions (never as an empty corpus), and excerpts mark their truncation point.

Configuration

Everything corpus-specific lives in config.toml — the code ships with neutral defaults only. Resolution order: --config flag → $SHELFMARK_CONFIG~/.config/shelfmark/config.toml. See config.example.toml for the full annotated reference. Highlights:

Section What it controls
[[roots]] The trees to index. One unlabelled primary root; extra roots get a label prefix.
[index] Where the SQLite catalogue lives. Must be outside every indexed root and outside cloud-synced folders — it is a mutating binary DB, and this is enforced.
[privacy] Regexes for secrets and private subtrees → RESTRICTED. Built-ins already cover .env, key/cert files, id_rsa, backup codes, identity documents.
[authors] Regexes for your own name/company, for client authors, and for generator tools — drives OWN/REFERENCE classification from OOXML authorship.
[rights] Path-prefix rules for the two-axis model: rights (may I reuse it) × confidential (may it leave).
[facets] Which top-level folders count as work/personal; where client/project names sit in the path.
[doc_types] / [context_types] Extra filename/folder rules, checked before the built-in bilingual (EN/ES) defaults; built-ins can be disabled by name.

What shareable_only means

shareable_only=True means positively classified: confidential=0 AND rights IN (OWN, REFERENCE). Never-reviewed files are held back — unreviewed is not the same as cleared.

Getting rights set: shelfmark review

Most files carry no OOXML author — a corpus is mostly PDFs, markdown and images — so authorship classifies almost nothing and path rules are the only mechanism that can. They ship empty, which is why a fresh catalogue is mostly UNKNOWN and shareable_only comes back nearly empty.

That knowledge is yours, not the corpus's, so review asks for it — biggest win first, with what the catalogue knows on screen:

[1/8] Projects
      493 unclassified of 662 files · folder reads as: pitch
      is: note 202, document 89, code 88, pdf 51
      authored by: R. Okonjo (31), A. Lindqvist (12), openpyxl (9)
      e.g. programme-overview.pptx · phase-two-proposal.pptx
      answer [skip]:

Answer own, own-private, reference, client, personal, scratch or skip. On a ~1,900-file corpus, five answers settled 80% of the unclassified files and eight settled 90%.

shelfmark review                 # dry run — shows what it would write
shelfmark review --apply         # writes the prefixes, re-derives rights

It stops whenever you do, only ever asks about subtrees that are still unclassified (so re-running continues where you left off), and writes config, never rights values in the data — so any answer is undone by editing the config and re-running shelfmark rights. Where authorship is lopsided enough to be evidence it offers a default; where it is thin it stays quiet rather than guessing.

Editing classification rules

Two things people trip over, learned the hard way:

  1. Anchor short regex alternatives on both sides. An unanchored rfi matches inside "Dockerfile" and the Spanish word "perfil". Before adding or deleting an alternative, list the filenames it actually matches.

  2. A rule edit does not relabel existing files. The builder is incremental, so after any rule change run:

    shelfmark reclassify all          # dry run — shows what would change
    shelfmark reclassify all --apply
    

    Do not use build --rebuild for this: it re-walks everything (and on cloud-synced trees drags every evicted file back down) when the filenames are already in the DB.

Keeping it current

shelfmark refresh                # build + rights + prune + assertions
shelfmark refresh --if-needed    # only if a write landed or the index is old

refresh is incremental by (size, mtime, cloud-residency) — a couple of seconds over a ~30k-file tree — and it asserts its own correctness on every run: walk coverage (an OS-denied walk must not read as success), guarded pruning of deleted files (refused when it looks like a mass deletion or an unmounted root), and governance invariants (no RESTRICTED row in the search index, no unsealed secret, no unsearchable non-RESTRICTED row). Results land in REFRESH_STATUS.json next to the DB, and the MCP server reports them on every corpus_stats() call.

Any guard that declines to touch the index says so on stderr and exits non-zero — you never get a clean-looking summary hiding a refusal.

When a guard stops you

The two size guards cannot tell "the root was unreadable" from "those files really were deleted" — both look like a short walk. So they refuse, name both possibilities, and leave the index untouched:

FAILED — walk saw 63/123 catalogued files, below the 80% floor — either the
root was unreadable to this process or that many files really went away.
Index NOT updated; re-run with --force if the deletion was real.

Check which it was. If the files are genuinely gone:

shelfmark refresh --force        # accept the short walk, prune past the ceiling

--force backs the catalogue up to catalog.db.bak-preprune before deleting anything. If instead a root was merely unmounted or unreadable, fix that and refresh normally — the rows are still there.

You do not schedule this

The MCP server keeps its own index current. It is spawned by your client, lives for the whole session, and refreshes once on startup and then whenever refresh.max_age_seconds has passed. Nothing to install, no timer to configure, no command to remember.

That works because a resident process the client already starts is a better trigger than a scheduler: it inherits the same file access the client has, and it is running exactly when you are asking questions. If you maintain the catalogue some other way, shelfmark-mcp --no-auto-refresh leaves it alone.

Editor hooks are now an optimisation, not the mechanism — they cut the delay between an agent writing a file and that file being searchable, from one refresh interval to the end of the turn. With Claude Code, in ~/.claude/settings.json:

{
  "hooks": {
    "PostToolUse": [{"matcher": "Write|Edit|MultiEdit|NotebookEdit",
      "hooks": [{"type": "command", "command": "shelfmark mark-dirty"}]}],
    "Stop": [{"hooks": [{"type": "command",
      "command": "shelfmark refresh --if-needed >/dev/null 2>&1"}]}]
  }
}

mark-dirty drops a marker only when a write landed under an indexed root (near-free on every other write); refresh --if-needed picks it up at the end of the turn.

If you also want it current with no client running

Only worth it for non-MCP use, or an instant first query on a very large tree. On Linux, a systemd --user timer running shelfmark refresh --if-needed is clean.

On macOS, the obstacle is TCC, not launchd. A LaunchAgent is denied ~/Documents, ~/Desktop and ~/Downloads by default; os.walk swallows the error, so the build walks a handful of files and exits 0 — every layer reports success while the index never updates. Grant Full Disk Access to the interpreter that runs the job and a LaunchAgent is fine. The coverage assertion catches the ungranted case either way: the refresh fails loudly rather than quietly indexing nothing.

Content hashing and duplicates

The refresh never reads file contents (it must stay fast). To populate hashes for duplicate detection:

shelfmark hash                   # reads every unhashed, non-sensitive file
shelfmark hash --limit 2000      # chip away at it

Sensitive rows are never opened, and neither are symlinks. On cloud-synced trees, dataless placeholder files are skipped — reading one silently yields the hash of the empty string, which would make distinct files look identical. Re-run after large materialisations.

What makes Shelfmark different

  • Desktop search helps a person find text inside files.
  • Document management systems require documents to be imported and managed inside a new environment.
  • Retrieval systems parse, chunk and copy document content into search indexes or vector stores.
  • Filesystem tools give an agent direct access and leave every discovery decision to the agent.

Shelfmark sits at a different layer: a local, governed, metadata-based map of the documents you already have, built specifically for selective agent context.

What Shelfmark does not try to be

It is not a document management system. It does not replace your filesystem. It does not require embeddings. It does not reorganise your folders. It does not claim to understand content it has never read. And it does not assume every discoverable document is safe to share.

It gives agents a better starting point.

These are decisions, not a backlog:

  • No embeddings. Metadata FTS + facets first. Use it, note what you couldn't find, and let real misses decide. If misses cluster on "I know what it said, not what it was called", the fix is content extraction, not embeddings.
  • No content extraction. Body text stays out of the index by design.

FAQ

Does Shelfmark upload my documents? No. It runs locally and builds a local catalogue. Document contents are not copied into it.

What does the catalogue contain? File references and derived metadata: paths, filenames, types, sizes, dates, authors, classifications, selected Office properties, presentation titles, and optional content hashes.

Does it read document contents? The file catalogue does not index body text. Two deliberate exceptions: shelfmark hash opens files to compute content hashes for duplicate detection, and optional email ingestion can index message bodies so search_emails works. Both are opt-in commands, both skip symlinks, and both honour your privacy rules — a file or archive matching your secret/private patterns is never opened by either.

Do I need to reorganise my folders first? No. Better filenames and metadata improve classification, but no formal structure is required.

Can it understand every badly named file? No. A file with a meaningless name and no embedded metadata stays hard to identify without reading it. Shelfmark says what it knows rather than guessing.

Why not just give an agent filesystem access? Filesystem access lets an agent open files. Shelfmark helps it decide which files are worth opening — and which should stay out of its results entirely.

Why not a vector database? A vector database is for semantic retrieval from contents. Shelfmark solves the earlier problem: discovering and governing what exists, before deciding what content should be processed at all. They are compatible; this one comes first.

Which agents can use it? Any client that supports local MCP servers. Register with claude mcp add shelfmark -s user -- shelfmark-mcp, or point your client at the shelfmark-mcp command.

Development

uv run --group dev pytest        # the suite ships in the sdist, so this
                                 # runs from the release artifact too

The test corpus — including its OOXML files — is synthesised on every run; no fixture binaries are committed and no real document is ever read.

License

MIT — see LICENSE. Free to use, modify and redistribute.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

shelfmark-0.1.2.tar.gz (101.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

shelfmark-0.1.2-py3-none-any.whl (85.5 kB view details)

Uploaded Python 3

File details

Details for the file shelfmark-0.1.2.tar.gz.

File metadata

  • Download URL: shelfmark-0.1.2.tar.gz
  • Upload date:
  • Size: 101.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for shelfmark-0.1.2.tar.gz
Algorithm Hash digest
SHA256 cf1f8b7d110a8c253ec7ecd47b895b0551d1ccef013bec055e62f57fbe8610d6
MD5 383a99a67eb5ae33c1f830e798276260
BLAKE2b-256 4eca972456229eb46f119d295b71e83c18885652c8ae4b283d1ed9f0ddd9df7b

See more details on using hashes here.

Provenance

The following attestation bundles were made for shelfmark-0.1.2.tar.gz:

Publisher: release.yml on Dankaro-projects/shelfmark

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file shelfmark-0.1.2-py3-none-any.whl.

File metadata

  • Download URL: shelfmark-0.1.2-py3-none-any.whl
  • Upload date:
  • Size: 85.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for shelfmark-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 c2b1273dafd37cb1bbb3877f24982032331dd777407709ec6e6465fd85693050
MD5 bc4274a843be972984178763fa6de401
BLAKE2b-256 a0d735e0b87ea3d243578c2fbd0da2739c84317782c32fbd5b536ca6be6fd774

See more details on using hashes here.

Provenance

The following attestation bundles were made for shelfmark-0.1.2-py3-none-any.whl:

Publisher: release.yml on Dankaro-projects/shelfmark

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page