Skip to main content
Glama
Furkiozknn

local-notes-search-mcp

by Furkiozknn

πŸ”Ž local-notes-search-mcp

Semantic search over your own files β€” as an MCP server.

Ask questions in plain language instead of guessing the exact keyword you typed six months ago.

CI Tests License: MIT Python MCP

πŸ‡ΉπŸ‡· TΓΌrkΓ§e README β†’


Try it in a minute

git clone https://github.com/Furkiozknn/local-notes-search-mcp.git
cd local-notes-search-mcp
uv sync && uv run python examples/stdio_demo.py

That starts the server, talks to it over stdio the way an MCP client does, and searches the fixture notes in examples/notes/ β€” five short files written for this repository, not anyone's real notes β€” into a throwaway index in a temp directory. Nothing in ~/.local-notes-search is touched. Measured on the author's machine (Windows 11, Python 3.12, uv 0.12.5, 30 September 2026): uv sync with an empty uv cache 22 s, the one-time model download about 20 s, the demo itself about 19 s. The first run needs network for that download; see the model download if you want it as an explicit step.

The part of that output that matters (verbatim; the tools answer in Turkish, the author's working language, and distance is the sqlite-vec default L2 distance β€” lower is closer, and it is not a 0–1 score):

examples/notes indexlendi: 5 dosya (yeni/değişmiş), 0 değişmemiş dosya atlandı, 5 yeni chunk, 0 silinmiş dosya temizlendi.

search_notes("what did I decide about the auth redesign?", top_k=2)
2 sonuΓ§:

--- examples/notes/2026-08-decisions.md:1-10 (distance=4.5000) ---
# Decisions, August 2026

## Auth redesign
Decided: session cookies instead of JWT. The refresh-token rotation story was
getting worse than the problem it solved, and every client we ship is a browser.
[...]

--- examples/notes/meeting-2026-07-30.md:1-7 (distance=5.0842) ---
# Meeting, 30 July
[...]
- Agreed to revisit the login flow after the billing migration ships.

The query says "auth redesign"; the note says "session cookies instead of JWT". The second pasta query in the full record finds a Turkish note for an English question, which is what the multilingual model is for.

When to use it, and when not to

Use it when

Do not use it when

you remember what a note said but not the words you typed

you know the exact string: ripgrep is faster and exact

you want your MCP client (Claude Code, Claude Desktop, …) to search your own folders with file:line answers

you need a shared, multi-user or hosted index: this is one local SQLite file, single writer

the notes must not leave the machine and you have no API key to give

you need the ranking to be tuned per corpus: one fixed embedding model, no re-ranking

Turkish, English or mixed notes

your files are PDFs, Word or images: only text formats are read (list)

No API key Offline No server Optional LLM Storage Embeddings

Want a synthesized answer instead of a result list? πŸ’‘ ask_notes runs the exact same retrieval, then has an LLM answer grounded only in the retrieved chunks, with file:line sources attached. It's strictly opt-in β€” set GROQ_API_KEY or MISTRAL_API_KEY and it synthesizes; set neither and it quietly returns the raw matches instead of failing.

Indexing and search need no API key, no Docker container and no daemon. The one network call they can make is the one-time model download.


Related MCP server: code-rag

🧭 The 30-second pitch

grep / ripgrep

Cloud RAG SaaS

local-notes-search-mcp

Finds "the auth decision" when you wrote "session vs JWT"

❌

βœ…

βœ…

Works with no API key

βœ…

❌

βœ…

Your files never leave the machine

βœ…

❌

βœ…

No server / daemon / container to run

βœ…

❌

βœ…

Answers with exact file:line you can jump to

βœ…

⚠️

βœ…

Costs money per query

βœ… free

❌

βœ… free

Usable directly by Claude / any MCP client

❌

⚠️

βœ…

Optional grounded LLM answer with sources

❌

βœ…

βœ… opt-in


πŸ—οΈ How it works

flowchart LR
    subgraph INDEX["πŸ“₯ Index pipeline β€” runs when you ask it to"]
        direction LR
        A["πŸ“ Local folder"] --> B["🚢 Walk + filter<br/>skip hidden, node_modules,<br/>.venv, files &gt; 2MB"]
        B --> H{"πŸ” Content hash<br/>or embedder changed?"}
        H -- "no" --> SKIP["⏭️ Skip<br/>zero CPU"]
        H -- "yes" --> C["βœ‚οΈ Line-based chunker<br/>1500 chars + 200 overlap<br/>never splits a line"]
        C --> D["🧠 fastembed ONNX<br/>paraphrase-multilingual-MiniLM-L12-v2 · 384-d"]
    end

    D --> DB[("πŸ—„οΈ sqlite-vec<br/>vec0 virtual table<br/>~/.local-notes-search/index.db")]

    subgraph QUERY["πŸ” Query path β€” 100% offline"]
        direction LR
        Q["πŸ’¬ Natural-language<br/>question"] --> QE["🧠 Embed query<br/>same model"]
    end

    QE --> DB
    DB --> R["🎯 Top-k chunks<br/>file:line + snippet<br/>+ distance score"]

    R -. "opt-in: ask_notes<br/>needs an API key" .-> LLM["πŸ€– LLM synthesis<br/>Groq β†’ Mistral fallback<br/>grounded in retrieved chunks only"]
    LLM --> ANS["πŸ’‘ Answer + file:line sources"]

    style LLM fill:#1c1730,stroke:#a371f7,color:#ffffff
    style ANS fill:#1c1730,stroke:#a371f7,color:#ffffff
    style DB fill:#003b57,stroke:#00b4d8,color:#ffffff
    style R fill:#1a7f37,stroke:#3fb950,color:#ffffff
    style SKIP fill:#4d3800,stroke:#d4a72c,color:#ffffff

🧰 MCP tools

πŸ› οΈ Tool

What it does

πŸ—‚οΈ index_directory(path, extensions=None)

Recursively indexes a directory. Skips hidden files and directories (.git, .ssh, .config, .claude.json, …), node_modules / venv / __pycache__ / dist / build, binary files, anything that is not a regular file, and anything over 2 MB. Unchanged files are skipped via a cheap hash check β€” unless a different model or fastembed version embedded them, then they are re-embedded; deleted (or no-longer-readable) files are purged from the index.

πŸ” search_notes(query, top_k=5, path_prefix=None)

Natural-language semantic search. Returns file:line-range + snippet + distance score β€” not just a bag of filenames. path_prefix scopes the search to one subtree. top_k is 1–50.

πŸ’‘ ask_notes(question, top_k=5, path_prefix=None)

Optional. Same retrieval as search_notes, then an LLM (Groq β†’ Mistral fallback) answers using only those chunks, followed by a file:line source list. Needs GROQ_API_KEY or MISTRAL_API_KEY. With neither key set β€” or if the provider chain fails β€” it degrades to returning the raw matches with a note. It never hard-fails just because synthesis wasn't possible.

πŸ“‹ list_indexed_files(path_prefix=None)

What's in the index right now: path, chunk count, last-indexed timestamp (first 200 files, with a count of the rest), and a marker on files embedded by another model/fastembed version. Useful before searching, or to debug a stale result.

🧹 remove_directory(path)

Drops everything under path from the index. Does not delete your files β€” it only cleans the index.

πŸ”’ index_directory, search_notes, list_indexed_files and remove_directory require no API key and send nothing anywhere. The one network call they can make is the one-time embedding-model download described under Quickstart β€” set LOCAL_NOTES_SEARCH_OFFLINE=1 to rule even that out. ask_notes is the one tool that can talk to a remote provider, and only when you explicitly give it a key.


πŸš€ Quickstart

Needs uv and Python 3.10–3.13.

git clone https://github.com/Furkiozknn/local-notes-search-mcp.git
cd local-notes-search-mcp
uv sync

The demo above is the quickest check that everything works. If the model cannot be downloaded it prints an error naming the fix (network access, or --download-model + LOCAL_NOTES_SEARCH_OFFLINE); uv run local-notes-search-mcp --help lists the tools, the environment variables and the default paths.

Register local_notes_search.py as a stdio MCP server:

{
  "mcpServers": {
    "local-notes-search": {
      "command": "uv",
      "args": [
        "--directory", "/absolute/path/to/local-notes-search-mcp",
        "run", "local_notes_search.py"
      ]
    }
  }
}

Without a clone, straight from GitHub (measured: 29 s to the first tools/list with an empty uv cache, 11 s warm; it is not on PyPI):

{"mcpServers": {"local-notes-search": {"command": "uvx", "args": ["--from", "git+https://github.com/Furkiozknn/local-notes-search-mcp", "local-notes-search-mcp"]}}}

The model download, stated plainly. The embedding model is not bundled. Unless it is already cached, the first index_directory / search_notes call downloads it from Hugging Face (fastembed lists it at 0.22 GB) into ~/.local-notes-search/models, and every call after that is offline. To make that download an explicit step instead of a side effect:

uv run local-notes-search-mcp --download-model   # once, with network
export LOCAL_NOTES_SEARCH_OFFLINE=1              # from now on: never download

With LOCAL_NOTES_SEARCH_OFFLINE=1 and no cached model, a tool call fails with an error that says exactly this, instead of reaching for the network.

Env var

Default

What it does

LOCAL_NOTES_SEARCH_DB

~/.local-notes-search/index.db

Where the index lives. One single file for every indexed directory β€” so a single search_notes call can span all your project folders at once.

LOCAL_NOTES_SEARCH_MODEL

sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2

Embedding model (any fastembed-supported name). An index built with another model is refused, not silently compared.

LOCAL_NOTES_SEARCH_MODEL_DIR

~/.local-notes-search/models

Model cache. Falls back to FASTEMBED_CACHE_PATH if that is set. (fastembed's own default is the system temp directory, which is wiped on reboot β€” hence a download again later.)

LOCAL_NOTES_SEARCH_OFFLINE

unset

1 forbids any model download; the model must already be cached (--download-model).

LOCAL_NOTES_SEARCH_ALLOWED_ROOTS

unset

Optional allowlist. When set, index_directory refuses any path that does not resolve inside one of these directories. os.pathsep-separated (: on Linux/macOS, ; on Windows).

GROQ_API_KEY

unset

Optional. Enables ask_notes synthesis via Groq (first in the provider chain).

MISTRAL_API_KEY

unset

Optional. Fallback provider for ask_notes when Groq is unset or fails.

Keys are read from the environment only β€” never commit them, and never put them in the MCP client config file you check into git.

Default indexed extensions: .md .txt .py .js .ts .tsx .jsx .json .yaml .yml .rst .toml β€” override per call with extensions=[...].

πŸ”’ What can be indexed

index_directory reads whatever it is pointed at, and ask_notes sends the chunks it retrieves to a third-party LLM (Groq or Mistral) when a key is configured. So an indexed path is a path whose contents can leave the machine. These guards exist:

1. LOCAL_NOTES_SEARCH_ALLOWED_ROOTS (opt-in). Unset by default β€” that is the historical behaviour, any directory the running user can read is indexable, and this project does not pretend an empty default is a sandbox. Set it and index_directory refuses anything outside:

export LOCAL_NOTES_SEARCH_ALLOWED_ROOTS="$HOME/notes:$HOME/projects"

Paths are resolved (.. collapsed, symlinks followed) before the check, and a subdirectory of an allowed root is allowed. A configured entry that is not a directory is an error rather than being silently dropped β€” a typo must not quietly switch the allowlist off.

2. A credential-filename denylist (always on). These are never indexed, whatever the allowlist or the extensions=[...] argument says:

.env Β· .env.* Β· .netrc Β· _netrc Β· id_rsa Β· id_dsa Β· id_ecdsa Β· id_ed25519 Β· credentials.json Β· secrets.json Β· service-account*.json Β· .npmrc Β· .pypirc Β· .git-credentials Β· *.pem Β· *.key Β· *.p12 Β· *.pfx Β· *.ppk

Matching is case-insensitive. It is a name denylist, not a secret scanner: it stops the obvious cases (credentials.json would otherwise sail through the default .json extension filter), not a key pasted into a .md file.

3. Hidden files and directories are not walked (always on). .ssh, .aws, .gnupg, .docker (whose config.json holds registry auth), .config (where gh keeps an OAuth token in hosts.yml), ~/.claude.json (MCP server configs, API keys in env included) β€” pointing index_directory at a home directory must not sweep those in through the .json / .yml filters. A hidden directory you do want indexed can be passed as path itself. Only regular files are read: a FIFO or device named *.md is skipped (a FIFO used to hang the whole call), and a file is never read past 2 MB even if it grew after the walk.

4. Symlinks cannot leave the root (always on). A symlinked file is indexed only if it resolves to a regular file inside the directory being indexed, not inside a skipped directory, and not to a denylisted name β€” so notes/todo.md β†’ ~/.aws/credentials is skipped instead of being read under an innocent name, past both the denylist and the allowlist. Directory symlinks are never followed.

πŸ—„οΈ What the index stores

The index file holds, for every indexed chunk, its full text in plain text, its path and line range, and its vector. It is a copy of your notes, not just pointers to them. On Linux/macOS it is created owner-only (0600, inside a 0700 ~/.local-notes-search/; an index created by an older version is tightened the next time it is opened). Delete it β€” or use remove_directory β€” and the copy is gone; your original files are never modified.

Every indexed file also records which embedder produced its vectors: the model name and the fastembed version. That matters because fastembed 0.6.0 changed this model's pooling (CLS β†’ mean), so vectors from before and after are not comparable. After an upgrade, search_notes / ask_notes start with a warning that says how many files are stale, list_indexed_files marks them, and the next index_directory on those folders re-embeds them even though their content did not change. An index created before this was recorded is treated the same way.


🧠 Why this architecture

Decision

Why

πŸ—„οΈ sqlite-vec (Apache-2.0) for vector storage

A vec0 virtual table inside one ordinary .sqlite file β€” no daemon, no Docker, no hosted service. Qdrant and pgvector were evaluated and rejected specifically because both need a running server process. A personal notes index should not require ops.

⚑ fastembed (Apache-2.0), not sentence-transformers

Local embedding here is the only path β€” it runs on every index and every search. sentence-transformers drags in torch (~1 GB); that's an acceptable price for a rarely-hit fallback, but not for the hot path. fastembed's quantized ONNX models land around 100–150 MB with no torch at all. A deliberate divergence, documented in the module docstring.

🧬 paraphrase-multilingual-MiniLM-L12-v2, 384 dims

Small (0.22 GB), Apache-2.0, and β€” decisive for this tool β€” actually multilingual: the previous bge-small-en-v1.5 was an English-only model quietly embedding Turkish notes. Symmetric, so queries and passages embed identically. Override with LOCAL_NOTES_SEARCH_MODEL; a mismatched existing index is refused, never silently compared. No GPU required.

βœ‚οΈ Line-based chunking, no NLP/AST dependency

Chunks accumulate whole lines until a character budget is hit β€” a line is never split in half, so every file:line reference the tool returns is exact. An overlap window keeps context alive across boundaries. Deterministic, and fully unit-testable without loading the embedding model.

πŸ” Whole-file content-hash skip on re-index

index_directory is designed to be re-run constantly. Re-embedding unchanged files would burn CPU on every single call for zero benefit β€” one cheap hash comparison avoids it.


πŸ§ͺ Tests

uv run pytest -v

107 tests, on a deliberate two-tier strategy. Pure-logic tests (chunking, hashing, file walking, ask_notes' provider-chain and degradation paths) always run β€” no model, no network, no API key. Tests that need the real fastembed model or the sqlite-vec extension skip honestly when those can't be loaded β€” an offline runner, a blocked model download β€” rather than faking a green result.

What that means in practice, reported exactly as measured:

Environment

Result

βœ… CI (model cached, and required: a missing model fails the job instead of skipping; Python 3.10, 3.11, 3.12 and 3.13)

107 passed on each of the four β€” measured 30 September 2026 β€” including the real end-to-end flow β€” the fastembed model really loaded, the sqlite-vec extension really ran, and a "how do I cook pasta" query really retrieved the recipe note and not the car-maintenance one.

⚠️ Windows, model download blocked (LOCAL_NOTES_SEARCH_OFFLINE=1, empty cache)

84 passed, 23 skipped (15 model-backed + 8 POSIX-only) β€” measured 30 September 2026. Every model-free test green, and the model-backed ones skipped with an explicit reason instead of a false pass.

The second row is the honest cost of the first: this suite tells you when it couldn't verify something.


What this server can actually do

The expensive question about an MCP server is not what it promises but what it can do on your machine: which credentials it can touch, where it connects, what it runs. Answering that means reading the source, and most people will not.

On every push, mcp-vet from the same account audits this server from source and writes the whole report into the job summary, verdict included. The gate closes at HIGH and above β€” and it also closes if the tool itself could not run, because "I could not look" should not read as green.

Auditing our own server with our own tool had a side effect worth recording: adding this job surfaced a real false positive in mcp-vet, which was fixed. A tool nobody runs stays right by default.

⚠️ Known limitations

Written down on purpose, because a README that claims no weaknesses is a README you shouldn't trust.

  • No query-instruction prefix is needed anymore. The previous English-only bge-small-en-v1.5 recommended embedding queries with an instruction prefix, which this tool skipped as a v1 simplification. The current default, paraphrase-multilingual-MiniLM-L12-v2, is a symmetric model: queries and passages are meant to embed identically, so the simplification is now simply the correct usage. If you override LOCAL_NOTES_SEARCH_MODEL with an asymmetric model (BGE/E5 family), know that its prefix convention is still not applied.

  • path_prefix filters after the vector search, not inside it. The search over-fetches 4 Γ— top_k nearest chunks from the whole index and then keeps those under the prefix, so a narrow prefix in a large index can return fewer than top_k results even when more matching chunks exist.

  • Single-writer SQLite. Concurrent index_directory / search_notes calls from separate processes can collide on writes. The tool is designed around a single MCP client session.


πŸ““ Changelog

See CHANGELOG.md.

πŸ“œ License

MIT β€” and every runtime dependency was license-checked: sqlite-vec (Apache-2.0), fastembed (Apache-2.0), mcp (MIT), litellm (MIT). No non-commercial or field-restricted weights anywhere in the stack.

Built as part of an ecosystem of small, focused, self-hostable AI tools.


More from this ecosystem

All of them in one searchable page: furkiozknn.github.io β€” each card is generated from that repository's own project-meta.json.

Available Tools

5 tools
ask_notesA

Ask a question in natural language about your indexed files. Retrieves the most relevant chunks (same retrieval as search_notes) and asks an LLM (Groq, then Mistral fallback - needs GROQ_API_KEY or MISTRAL_API_KEY) to synthesize an answer grounded ONLY in those chunks, with file:line sources. Without either key configured, degrades to returning the raw retrieved chunks with a note that no LLM is available - never fails outright just because synthesis isn't possible.

ParametersJSON Schema
NameRequiredDescriptionDefault
top_kNo
questionYes
path_prefixNo

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.1/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries full behavioral burden. It discloses LLM provider fallback chain (Groq then Mistral), API key requirements, graceful degradation without keys, grounding in retrieved chunks with file:line sources, and guarantees no outright failure. This is exceptionally transparent.

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?

Though lengthy, the description is information-dense and front-loaded with the core purpose. The second sentence packs several critical behavioral details efficiently. Slightly more compact writing might be possible, but the content justifies the length.

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?

Given the complexity (LLM synthesis, API keys, fallback, grounding), the description covers the essential behavior well. An output schema exists, so not explaining return values is acceptable. Minor gaps like parameter details are addressed under parameter semantics, so overall completeness is high but not perfect.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must explain the parameters. It implicitly covers 'question' via the purpose, but says nothing about 'top_k' or 'path_prefix'. The retrieval similarity to search_notes does not substitute for explaining these parameters.

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?

The description states a specific verb ('Ask a question') and resource ('indexed files'), and explicitly contrasts with search_notes by adding LLM synthesis. It is clear what the tool does and how it differs from its closest sibling.

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?

It notes the same retrieval as search_notes and explains the synthesized vs. raw output difference, implying when to choose this over search_notes. However, it does not explicitly state 'use this when you need a synthesized answer' or provide exclusion criteria, so it stops short of a 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

index_directoryA

Index (or re-index) a local directory for semantic search. Walks recursively, skips .git/node_modules/.venv/etc and files >2MB, and skips any file whose content is unchanged since the last index (cheap: a whole-file hash check before touching the embedding model). Files that were indexed before but no longer exist under path are removed from the index.

Credential-shaped file names (.env, id_rsa, credentials.json, .netrc,
*.pem, ...) are never indexed. If LOCAL_NOTES_SEARCH_ALLOWED_ROOTS is
set, `path` must resolve inside one of its entries; unset (the default)
means any readable directory is indexable.
ParametersJSON Schema
NameRequiredDescriptionDefault
pathYes
extensionsNo

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.3/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations to lean on, the description fully covers side effects and behaviors: recursive walking, skipped directories and size limits, unchanged-file hashing, stale-entry removal, credential-shaped file exclusions, and the LOCAL_NOTES_SEARCH_ALLOWED_ROOTS constraint. This is exemplary disclosure for a tool with no annotations.

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 description is dense but every sentence earns its place. It is front-loaded with the core purpose, followed by skip rules, side effects, security exclusions, and configuration constraints. There is no fluff or redundant restating of the tool name.

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?

The description is unusually complete for a complex tool: it covers scope, side effects, security, and environment constraints, and an output schema exists to document return values. The only notable gap is the undocumented `extensions` parameter, which prevents a perfect score.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate. It adds useful meaning for `path` (must resolve inside allowed roots, must be readable), but it never mentions the `extensions` parameter at all, leaving its purpose and possible values entirely undocumented.

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?

The description opens with a specific verb and resource: 'Index (or re-index) a local directory for semantic search.' It then adds behavioral specifics that distinguish this tool from sibling tools like search_notes and list_indexed_files, making the tool's role unmistakable.

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?

The description makes the tool's use case clear: build or refresh a semantic-search index for a local directory. It does not explicitly name alternatives or say 'use search_notes instead for queries,' but the context is unambiguous and no exclusion conditions are omitted.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_indexed_filesA

Lists what's currently in the index - file path, chunk count, last indexed time. Useful to check what's covered before searching, or to debug a stale/missing result.

ParametersJSON Schema
NameRequiredDescriptionDefault
path_prefixNo

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.1/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations exist, so the description must carry the behavior. It conveys a read-only metadata snapshot ('currently'), states the returned fields, and frames staleness as an expected condition. It does not explicitly state that it never triggers indexing or how recently the index was refreshed, so it is not fully exhaustive.

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: the first front-loads the operation and result fields, the second adds pragmatic use cases. No filler or redundancy.

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 read-only listing with one optional parameter and an output schema, the description nearly suffices; the only real gap is the unmentioned path_prefix filtering, but the optional nature and param name mitigate the risk.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0%, and the description never mentions path_prefix or how filtering works. An agent must infer the parameter's meaning from its title alone, so the description fails to compensate for the missing schema documentation.

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?

Names a specific verb ('Lists') and resource ('what's currently in the index'), and enumerates the returned fields (file path, chunk count, last indexed time). This is enough to separate it from siblings like index_directory or search_notes.

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 concrete use cases: checking coverage before searching and debugging stale/missing results. It does not name sibling tools or explicitly list exclusions, but the guidance clearly implies reading index state rather than changing it.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

remove_directoryA

Removes every indexed file/chunk under path from the index. The index is persistent local state in ~/.local-notes-search/ (or LOCAL_NOTES_SEARCH_DB) - this is how you clean it up without deleting the whole database file.

ParametersJSON Schema
NameRequiredDescriptionDefault
pathYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden of behavioral disclosure. It explicitly describes the destructive scope ('every indexed file/chunk under path'), the persistent local state affected, and that the database file itself is not removed. It could mention irreversibility or permissions, but the core behavior is transparent.

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 compact sentences front-load the action and scope, then add the relevant storage and cleanup context. There is no filler, and every sentence contributes useful information.

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 one-parameter destructive index tool, the description covers what is removed, where the state lives, and why you would use it. The output schema handles return details, so the only mild gaps are explicit caveats about reversibility and path format, which are not critical.

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?

At 0% schema coverage, the description must compensate for the bare 'path' property; it does so by explaining that path defines the subtree whose indexed entries are removed. It does not specify whether path must be absolute or can point to a single file, but the parameter's role is clear.

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?

The description uses a specific verb ('Removes') and names the exact resource ('every indexed file/chunk under path from the index'). This clearly separates it from sibling tools like index_directory or search_notes. It also identifies the storage location, reinforcing that it is an index-cleanup operation.

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?

The description states this is how you clean up the persistent local index without deleting the whole database file, giving a clear when-to-use context. It does not explicitly name alternative sibling tools, but the cleanup intent is evident and not misleading.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

search_notesA

Semantic search across everything indexed so far. Returns the top matching chunks with file path, line range, and a relevance-ordered snippet - not just a bag of file names.

ParametersJSON Schema
NameRequiredDescriptionDefault
queryYes
top_kNo
path_prefixNo

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A3.8/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

There are no annotations to lean on, so the description carries the transparency burden. It discloses important behavior: results are relevance-ordered, returned as chunks with file path and line range, and not merely a list of file names. It does not mention side effects, but 'search' strongly implies read-only, and no contradiction exists.

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 description is two tight sentences, with the verb and scope front-loaded and the return behavior stated in a single clause. The extra 'not just a bag of file names' earns its place by highlighting the key differentiator.

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?

The core purpose and return shape are present, and an output schema exists for detailed return fields, so this is viable. However, there is no guidance on path_prefix semantics or when to prefer ask_notes, and with 0% schema descriptions the agent must infer too much for a tool with three parameters.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, and the description does not compensate by explaining query, top_k, or path_prefix. 'Top matching chunks' implies the role of top_k and query, but path_prefix is undocumented, and the phrase 'across everything indexed so far' obscures the fact that path_prefix can scope the search.

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 ('semantic search') over a defined corpus ('everything indexed so far') and goes on to describe the return granularity (matching chunks with file path, line range, and snippet). This distinguishes it clearly from the sibling tools index_directory, list_indexed_files, ask_notes, and remove_directory.

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 description implies use when the agent needs semantic retrieval of relevant chunks, rather than file listing or Q&A, but it never explicitly says when to choose this over ask_notes or how path_prefix narrows the search. Usage context is clear, but exclusions and alternative routing are left to inference.

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. 5 tool updatesv0.1.0
    • First observedask_notes
    • First observedindex_directory
    • First observedlist_indexed_files
    • First observedremove_directory
    • First observedsearch_notes

TDQS

A4.3/5.0

Scored across 5 tools

Disambiguation5/5

Each tool has a clearly distinct role: index_directory ingests data, search_notes and ask_notes are differentiated as raw retrieval vs. LLM-synthesized answers, list_indexed_files provides observability, and remove_directory cleans up. Even the two retrieval-based tools are easy to tell apart because their outputs and purposes are explicit.

Naming Consistency5/5

All tool names follow a consistent snake_case verb_noun pattern: index_directory, search_notes, list_indexed_files, ask_notes, remove_directory. There are no mixed conventions or vague generic verbs.

Tool Count5/5

Five tools is a well-scoped surface for a local notes search server: ingest, search, ask, list, and remove each earn their place. The count is neither bloated nor too thin.

Completeness5/5

The index lifecycle is fully covered: create/update via index_directory, read via search_notes and ask_notes, inspection via list_indexed_files, and deletion via remove_directory. Re-indexing handles changed and removed files, so there are no obvious dead ends or missing operations.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    Local MCP server that provides semantic search (RAG) over code repositories, enabling AI clients like Claude and Gemini to access project context without manual re-upload.
    -
  • F
    license
    Not graded
    quality
    C
    maintenance
    A semantic code search MCP server that enables natural language queries against your codebase, supporting features like related file discovery and context expansion, all running locally.
    2
    -
  • A
    license
    A
    quality
    A
    maintenance
    Local MCP server for semantic code search using Tree-sitter AST parsing, local embeddings, and hybrid search; enables indexing and querying codebases entirely offline.
    5
    MIT