Make `query` optional in `search_notes` so it becomes the single search tool. Remove `search_by_metadata` entirely — it was unreleased and redundant since `search_notes` already supports `metadata_filters`, `tags`, and `status`. - 🔧 `query` param is now `Optional[str] = None` - 🛡️ Added None guards for project detection and URL resolution - ✅ Added `no_criteria()` validation with helpful error message - 🗑️ Deleted `search_by_metadata` tool, imports, tests, and contract entry - 📝 Updated docs, README, and v0.19.0 release notes Closes #605 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: phernandez <paul@basicmachines.co>
7.1 KiB
Metadata Search Reference
Basic Memory automatically indexes custom frontmatter fields so you can query them with structured filters. Any YAML key in a note's frontmatter beyond the standard set (title, type, tags, permalink, schema) is stored as entity_metadata and becomes searchable.
Querying with search_notes
search_notes is the single search tool for all queries — text, metadata filters, or both. The query parameter is optional, so you can use metadata filters alone without passing an empty string.
Filter Syntax
Filters are a JSON dictionary where each key targets a frontmatter field and the value specifies the match condition. Multiple keys combine with AND logic — every filter must match.
Equality
Match a single value exactly.
{"status": "active"}
Finds notes whose frontmatter contains status: active.
Array Contains (all)
Pass a list to require all listed values to be present in the field.
{"tags": ["security", "oauth"]}
Finds notes tagged with both security and oauth.
$in (any of)
Match if the field equals any value in the list.
{"priority": {"$in": ["high", "critical"]}}
$gt, $gte, $lt, $lte
Numeric and text comparisons. Numeric values use numeric comparison; strings use lexicographic comparison.
{"confidence": {"$gt": 0.7}}
{"score": {"$lte": 100}}
$between
Range filter (inclusive). Takes a [min, max] pair.
{"score": {"$between": [0.3, 0.8]}}
Nested Access (dot notation)
Access nested frontmatter values using dots.
{"schema.version": "2"}
This queries the version key inside a schema object in frontmatter.
Summary Table
| Operator | Syntax | Example |
|---|---|---|
| Equality | {"field": "value"} |
{"status": "active"} |
| Array contains (all) | {"field": ["a", "b"]} |
{"tags": ["security", "oauth"]} |
$in (any of) |
{"field": {"$in": [...]}} |
{"priority": {"$in": ["high", "critical"]}} |
$gt / $gte |
{"field": {"$gt": N}} |
{"confidence": {"$gt": 0.7}} |
$lt / $lte |
{"field": {"$lt": N}} |
{"score": {"$lt": 0.5}} |
$between |
{"field": {"$between": [min, max]}} |
{"score": {"$between": [0.3, 0.8]}} |
| Nested access | {"a.b": "value"} |
{"schema.version": "2"} |
Key rules:
- Filter keys must match
[A-Za-z0-9_-]+(dots separate nesting levels). - Each operator dict must contain exactly one operator.
$inand array-contains require non-empty lists.$betweenrequires exactly two values[min, max].
MCP Tool — search_notes
search_notes is the single search tool for text queries, metadata filters, or both. The query parameter is optional.
Relevant parameters:
| Parameter | Type | Description |
|---|---|---|
query |
string (optional) | Text search query. Omit for filter-only searches. |
metadata_filters |
dict | Structured filter dict (see syntax above) |
tags |
list[str] | Convenience shorthand — merged into metadata_filters["tags"] |
status |
string | Convenience shorthand — merged into metadata_filters["status"] |
Merging rules: tags and status are convenience shortcuts. They are merged into metadata_filters using setdefault — if the same key already exists in metadata_filters, the explicit filter wins.
Examples:
# Text search filtered by metadata
await search_notes("authentication", metadata_filters={"status": "draft"})
# Filter-only search (no query needed)
await search_notes(metadata_filters={"type": "spec"})
# Combine text, tags shortcut, and metadata
await search_notes(
"oauth flow",
tags=["security"],
metadata_filters={"confidence": {"$gt": 0.7}},
)
# Convenience shortcuts
await search_notes("planning", status="active")
await search_notes(tags=["tier1", "alpha"])
Tag Search Shortcuts
The tag: prefix in a search query is a shorthand for tag-based metadata filtering. When search_notes receives a query starting with tag:, it converts the query into a tags filter and clears the text query.
# These are equivalent:
await search_notes("tag:tier1")
await search_notes("", tags=["tier1"])
# Multiple tags (comma or space separated) — all must be present:
await search_notes("tag:tier1,alpha")
await search_notes("tag:tier1 alpha")
CLI Access
The bm tool search-notes command exposes metadata filtering via --meta and --filter flags.
--meta — simple key=value filters
Repeatable flag for equality filters on frontmatter fields.
# Single filter
bm tool search-notes "my query" --meta status=draft
# Multiple filters (AND logic)
bm tool search-notes "" --meta status=active --meta priority=high
--filter — advanced JSON filters
Pass a full JSON filter dictionary for operator-based queries.
# Range filter
bm tool search-notes "" --filter '{"score": {"$between": [0.3, 0.8]}}'
# $in filter
bm tool search-notes "" --filter '{"priority": {"$in": ["high", "critical"]}}'
--tag and --status — convenience shortcuts
bm tool search-notes "query" --tag security --tag oauth
bm tool search-notes "" --status draft
Combined example
bm tool search-notes "authentication" --tag security --meta status=draft --type spec
Practical Examples
Example notes with custom frontmatter
specs/auth-design.md:
---
title: Auth Design
type: spec
tags: [security, oauth]
status: in-progress
priority: high
confidence: 0.85
---
# Auth Design
## Observations
- [decision] Use OAuth 2.1 with PKCE for all client types #security
- [requirement] Token refresh must be transparent to the user
## Relations
- implements [[Security Requirements]]
specs/search-redesign.md:
---
title: Search Redesign
type: spec
tags: [search, performance]
status: draft
priority: medium
confidence: 0.6
---
# Search Redesign
## Observations
- [goal] Sub-100ms search response times #performance
- [approach] Hybrid FTS + vector retrieval
## Relations
- depends_on [[Database Schema]]
Queries that find them
# Find all in-progress specs
await search_notes(metadata_filters={"status": "in-progress", "type": "spec"})
# → Auth Design
# Find high-confidence specs
await search_notes(metadata_filters={"confidence": {"$gt": 0.7}})
# → Auth Design (confidence: 0.85)
# Find specs with priority high or medium
await search_notes(metadata_filters={"priority": {"$in": ["high", "medium"]}})
# → Auth Design, Search Redesign
# Find specs in a confidence range
await search_notes(metadata_filters={"confidence": {"$between": [0.5, 0.9]}})
# → Auth Design (0.85), Search Redesign (0.6)
# Find notes tagged with security
await search_notes("tag:security")
# → Auth Design
# Combined: text search + metadata filter
await search_notes("OAuth", metadata_filters={"status": "in-progress"})
# → Auth Design
CLI equivalents
bm tool search-notes "" --meta status=in-progress --type spec
bm tool search-notes "" --filter '{"confidence": {"$gt": 0.7}}'
bm tool search-notes "OAuth" --meta status=in-progress
bm tool search-notes --tag security