Local-first RAG pipeline for Bear notes with ONNX embeddings and MCP server
Bear-notes demonstrates solid tool design with clear naming (verb_noun pattern), comprehensive descriptions (100-300 chars), and well-structured schemas. All 6 tools have input schemas with typed parameters and descriptions. Error handling via _handle_errors decorator provides generic, safe responses. However, output schemas are not explicitly documented in code, parameter constraints lack enums/ranges, and some descriptions could be more concise. No tool annotations (readOnlyHint/destructiveHint) despite clear risk profiles. Composition is strong: tools chain logically (search→read, list→search). Naming is consistent and action-oriented.
Browse and filter Bear notes by metadata. Returns note metadata (title, tags, date) without full text. Use read_note to get the full content of specific notes. All parameters are optional. With no filters, returns the most recent notes. Date parameters use ISO format (e.g. "2026-01-01"). Trashed and archived notes are excluded, mirroring Bear's own UI (D17).
List all Bear note tags with note counts. Use this to discover what topics and categories exist in the notes before drilling down with search_notes or list_notes. Counts exclude trashed and archived notes, mirroring Bear's own UI (D17).
Get the full text of a specific Bear note by title (case-insensitive). Use this after search_notes when you need the complete note content, not just a chunk excerpt. Unlike search_notes/list_notes/list_tags, this is a deliberate direct fetch that can still return an *archived* note; the returned ``is_archived`` flag lets the caller mirror Bear's UX (D17).
Semantic search over Bear notes. Finds content by meaning. Use this to discover notes relevant to a topic. Returns ranked chunks (excerpts) with metadata. For full note text, follow up with read_note. When *tags* are given, results are restricted to notes carrying any of them (OR semantics), excluding trashed and archived notes. Snapshot semantics: tag membership is resolved from *live* Bear SQL while chunk content comes from the indexed snapshot. A tag edited in Bear since the last sync can therefore filter against membership the index hasn't caught up to yet; the gap only affects tag edits since the last sync and self-heals on the next sync (see ADR-0004). For the same reason, each result's ``tags`` field is read from the indexed snapshot: a note may be included by the live filter on a freshly-added tag that does not yet appear in its returned ``tags`` list until the next sync.
Output schemas not documented in code. LLMs cannot plan downstream calls or extract fields without knowing response structure (e.g., search_notes returns 'chunks' with what fields?). Baseline: 100% of A+ tools document return types.
No tool annotations (readOnlyHint/destructiveHint/idempotentHint). sync_notes is destructive (WRITE risk) and should carry destructiveHint=true; read-only tools should carry readOnlyHint=true. Agents need this metadata to reason about safety and retry logic.
Parameter constraints lack enums and ranges. 'limit' has no min/max (baseline: 1 - 100 for pagination). 'tags' array has no length limit. 'modified_since'/'modified_before' accept ISO strings but no format validation hint. Enums prevent hallucinated values.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | C | 63 | 2026-07-28+ | v2 |
Show the current state of the search index. Returns the number of indexed chunks, unique notes, and the last sync timestamp. Use this to check whether the index is populated and fresh.
Sync recent Bear note changes into the search index. Run this if notes were recently edited in Bear and you want the latest content available for search.
Error handling returns generic dicts without actionable recovery guidance. 'Invalid input' does not tell the LLM what to fix or retry. Baseline: errors should state the constraint violated and suggest next steps.
'status' tool description is vague (65 chars). Does not explain when to call it or what 'fresh' means. Baseline: 10 - 1024 chars, with WHEN/WHY context. Should state: 'Call before search to verify index is populated; returns chunk count, note count, and last sync timestamp.'