Skip to content

Commit 7341348

Browse files
phernandezclaude
andcommitted
feat: merge search_by_metadata into search_notes with optional query
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>
1 parent b09eca1 commit 7341348

9 files changed

Lines changed: 107 additions & 255 deletions

File tree

README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -438,8 +438,7 @@ list_directory(dir_name, depth) - Browse directory contents with filtering
438438
**Search & Discovery:**
439439
```
440440
search(query, page, page_size) - Search across your knowledge base
441-
search_notes(query, page, page_size, search_type, types, entity_types, after_date, metadata_filters, tags, status, project) - Search with filters
442-
search_by_metadata(filters, limit, offset, project) - Structured frontmatter search
441+
search_notes(query, page, page_size, search_type, types, entity_types, after_date, metadata_filters, tags, status, project) - Search with filters (query is optional for filter-only searches)
443442
```
444443

445444
**Project Management:**

docs/ai-assistant-guide-extended.md

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1060,9 +1060,9 @@ results = await search_notes(
10601060
project="main"
10611061
)
10621062

1063-
# Metadata-only search
1064-
results = await search_by_metadata(
1065-
filters={"type": "spec", "status": "in-progress"},
1063+
# Metadata-only search (no query needed)
1064+
results = await search_notes(
1065+
metadata_filters={"type": "spec", "status": "in-progress"},
10661066
project="main"
10671067
)
10681068
```
@@ -2915,18 +2915,11 @@ results = await search_notes(
29152915
)
29162916
```
29172917

2918-
**search_by_metadata(filters, limit, offset, project)**
2919-
- Metadata-only search using structured frontmatter
2920-
- Parameters:
2921-
- `filters` (required): Dict of field -> value (supports $in, $gt/$gte/$lt/$lte, $between)
2922-
- `limit` (optional): Max results (default: 20)
2923-
- `offset` (optional): Pagination offset (default: 0)
2924-
- `project` (required unless default_project_mode): Target project
2925-
- Returns: Matching entities
2926-
- Example:
2918+
**Metadata-only search (via search_notes)**
2919+
- Use `search_notes` with `metadata_filters` and no `query` for metadata-only searches:
29272920
```python
2928-
results = await search_by_metadata(
2929-
filters={"type": "spec", "status": "in-progress"},
2921+
results = await search_notes(
2922+
metadata_filters={"type": "spec", "status": "in-progress"},
29302923
project="main"
29312924
)
29322925
```

docs/metadata-search.md

Lines changed: 13 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,9 @@
22

33
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.
44

5-
## Two Ways to Query
5+
## Querying with `search_notes`
66

7-
| Tool | Use When |
8-
|------|----------|
9-
| `search_by_metadata` | You only need metadata filters (no text query) |
10-
| `search_notes` | You want to combine a text query with metadata filters |
11-
12-
Both tools accept the same filter syntax.
7+
`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.
138

149
## Filter Syntax
1510

@@ -88,45 +83,16 @@ This queries the `version` key inside a `schema` object in frontmatter.
8883
- `$in` and array-contains require non-empty lists.
8984
- `$between` requires exactly two values `[min, max]`.
9085

91-
## MCP Tools
92-
93-
### `search_by_metadata` — metadata-only search
94-
95-
Searches entities by structured frontmatter metadata without a text query. Results are scoped to entity-level items.
96-
97-
**Parameters:**
98-
99-
| Parameter | Type | Required | Description |
100-
|-----------|------|----------|-------------|
101-
| `filters` | dict | Yes | Metadata filter dictionary (see syntax above) |
102-
| `project` | string | No | Project to search in (uses default if omitted) |
103-
| `limit` | int | No | Max results (default 20) |
104-
| `offset` | int | No | Skip N results for pagination (default 0) |
105-
106-
**Example:**
107-
108-
```python
109-
# Find all notes with status "in-progress"
110-
await search_by_metadata({"status": "in-progress"})
111-
112-
# Find high-priority specs in the research project
113-
await search_by_metadata(
114-
{"type": "spec", "priority": {"$in": ["high", "critical"]}},
115-
project="research",
116-
limit=10,
117-
)
118-
```
119-
120-
### `search_notes` with metadata — combined text + metadata
86+
## MCP Tool — `search_notes`
12187

122-
The `search_notes` tool accepts `metadata_filters`, `tags`, and `status` parameters alongside the text `query`. This lets you combine full-text search with structured filtering.
88+
`search_notes` is the single search tool for text queries, metadata filters, or both. The `query` parameter is optional.
12389

12490
**Relevant parameters:**
12591

12692
| Parameter | Type | Description |
12793
|-----------|------|-------------|
128-
| `query` | string | Text search query (can be empty when using only filters) |
129-
| `metadata_filters` | dict | Structured filter dict (same syntax as `search_by_metadata`) |
94+
| `query` | string (optional) | Text search query. Omit for filter-only searches. |
95+
| `metadata_filters` | dict | Structured filter dict (see syntax above) |
13096
| `tags` | list[str] | Convenience shorthand — merged into `metadata_filters["tags"]` |
13197
| `status` | string | Convenience shorthand — merged into `metadata_filters["status"]` |
13298

@@ -138,8 +104,8 @@ The `search_notes` tool accepts `metadata_filters`, `tags`, and `status` paramet
138104
# Text search filtered by metadata
139105
await search_notes("authentication", metadata_filters={"status": "draft"})
140106

141-
# Filter-only search (empty query)
142-
await search_notes("", metadata_filters={"type": "spec"})
107+
# Filter-only search (no query needed)
108+
await search_notes(metadata_filters={"type": "spec"})
143109

144110
# Combine text, tags shortcut, and metadata
145111
await search_notes(
@@ -150,7 +116,7 @@ await search_notes(
150116

151117
# Convenience shortcuts
152118
await search_notes("planning", status="active")
153-
await search_notes("", tags=["tier1", "alpha"])
119+
await search_notes(tags=["tier1", "alpha"])
154120
```
155121

156122
## Tag Search Shortcuts
@@ -260,19 +226,19 @@ confidence: 0.6
260226

261227
```python
262228
# Find all in-progress specs
263-
await search_by_metadata({"status": "in-progress", "type": "spec"})
229+
await search_notes(metadata_filters={"status": "in-progress", "type": "spec"})
264230
# → Auth Design
265231

266232
# Find high-confidence specs
267-
await search_by_metadata({"confidence": {"$gt": 0.7}})
233+
await search_notes(metadata_filters={"confidence": {"$gt": 0.7}})
268234
# → Auth Design (confidence: 0.85)
269235

270236
# Find specs with priority high or medium
271-
await search_by_metadata({"priority": {"$in": ["high", "medium"]}})
237+
await search_notes(metadata_filters={"priority": {"$in": ["high", "medium"]}})
272238
# → Auth Design, Search Redesign
273239

274240
# Find specs in a confidence range
275-
await search_by_metadata({"confidence": {"$between": [0.5, 0.9]}})
241+
await search_notes(metadata_filters={"confidence": {"$between": [0.5, 0.9]}})
276242
# → Auth Design (0.85), Search Redesign (0.6)
277243

278244
# Find notes tagged with security

docs/releases/v0.19.0.md

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -94,13 +94,16 @@ All MCP tools now support `output_format="json"` for machine-readable responses.
9494

9595
### Structured Metadata Search
9696

97-
New `search_by_metadata` tool for searching by frontmatter fields.
97+
`search_notes` now supports filter-only searches — `query` is optional, so you can search
98+
purely by frontmatter metadata without passing an empty string.
9899

99100
```
100-
search_by_metadata({"status": "in-progress"})
101-
search_by_metadata({"tags": ["security", "oauth"]})
102-
search_by_metadata({"priority": {"$in": ["high", "critical"]}})
103-
search_by_metadata({"schema.confidence": {"$gt": 0.7}})
101+
search_notes(metadata_filters={"status": "in-progress"})
102+
search_notes(metadata_filters={"tags": ["security", "oauth"]})
103+
search_notes(metadata_filters={"priority": {"$in": ["high", "critical"]}})
104+
search_notes(metadata_filters={"schema.confidence": {"$gt": 0.7}})
105+
search_notes(tags=["security"]) # convenience shorthand
106+
search_notes(status="draft") # convenience shorthand
104107
```
105108

106109
### `tag:` Search Shorthand

src/basic_memory/cli/commands/tool.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -485,7 +485,7 @@ def search_notes(
485485
with force_routing(local=local, cloud=cloud):
486486
result = run_with_cleanup(
487487
mcp_search(
488-
query=query or "",
488+
query=query or None,
489489
project=project,
490490
workspace=workspace,
491491
search_type=search_type,

src/basic_memory/mcp/tools/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from basic_memory.mcp.tools.write_note import write_note
1919
from basic_memory.mcp.tools.cloud_info import cloud_info
2020
from basic_memory.mcp.tools.release_notes import release_notes
21-
from basic_memory.mcp.tools.search import search_notes, search_by_metadata
21+
from basic_memory.mcp.tools.search import search_notes
2222
from basic_memory.mcp.tools.canvas import canvas
2323
from basic_memory.mcp.tools.list_directory import list_directory
2424
from basic_memory.mcp.tools.edit_note import edit_note
@@ -58,7 +58,6 @@
5858
"schema_infer",
5959
"schema_validate",
6060
"search",
61-
"search_by_metadata",
6261
"search_notes",
6362
# "search_notes_ui",
6463
"view_note",

0 commit comments

Comments
 (0)