Skip to content

Commit 9ecedd7

Browse files
committed
feat: improve basic_memory tools description
1 parent 994c8b8 commit 9ecedd7

13 files changed

Lines changed: 912 additions & 106 deletions

src/basic_memory/mcp/tools/build_context.py

Lines changed: 71 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,79 @@
2020

2121

2222
@mcp.tool(
23-
description="""Build context from a memory:// URI to continue conversations naturally.
24-
25-
Use this to follow up on previous discussions or explore related topics.
23+
description="""Navigates your knowledge graph by following relations from a starting point. Builds comprehensive context by traversing semantic connections, perfect for continuing conversations or exploring related concepts.
24+
25+
```yaml
26+
node:
27+
topic: build_context - Graph Traversal
28+
goal: Navigate knowledge graph via relations
29+
insight: Memory URLs enable conversation continuity
30+
context:
31+
traversal: Breadth-first with depth control
32+
patterns: ["memory://exact", "memory://folder/*", "memory://*"]
33+
performance: O(n^depth) complexity
34+
use_case: Continue discussions with full context
35+
```
36+
37+
```baml
38+
class BuildContextInput {
39+
url string @pattern("memory://.*") @description("Memory URI pattern")
40+
project string?
41+
depth int @default(1) @range(1, 5) @description("Relation traversal depth")
42+
timeframe string @default("7d") @description("Period like '2 days ago'")
43+
types string[]? @description("Filter entity types")
44+
page int @default(1)
45+
page_size int @default(10)
46+
max_related int @default(10)
47+
}
48+
49+
class ContextResult {
50+
primary_results Note[] @description("Direct matches")
51+
related_results Note[] @description("Connected via relations")
52+
metadata ContextMetadata
53+
}
54+
55+
class ContextMetadata {
56+
depth int
57+
timeframe string
58+
primary_count int
59+
related_count int
60+
generated_at datetime
61+
}
62+
63+
function build_context(BuildContextInput) -> ContextResult {
64+
@description("Traverse knowledge graph from memory:// starting points")
65+
@complexity("O(n^depth)")
66+
@async(true)
67+
}
68+
```
69+
70+
## Memory Patterns
71+
72+
- `memory://specs/search` - Exact note
73+
- `memory://specs/*` - All in folder
74+
- `memory://*` - Everything
75+
76+
## Traversal Examples
77+
```python
78+
# Continue discussion
79+
context = build_context("memory://discussions/api-design")
80+
81+
# Deep exploration (2 hops)
82+
context = build_context(
83+
"memory://architecture/microservices",
84+
depth=2,
85+
timeframe="30d"
86+
)
2687
27-
Memory URL Format:
28-
- Use paths like "folder/note" or "memory://folder/note"
29-
- Pattern matching: "folder/*" matches all notes in folder
30-
- Valid characters: letters, numbers, hyphens, underscores, forward slashes
31-
- Avoid: double slashes (//), angle brackets (<>), quotes, pipes (|)
32-
- Examples: "specs/search", "projects/basic-memory", "notes/*"
88+
# Filtered traversal
89+
context = build_context(
90+
"memory://specs/*",
91+
types=["entity", "observation"]
92+
)
93+
```
3394
34-
Timeframes support natural language like:
35-
- "2 days ago", "last week", "today", "3 months ago"
36-
- Or standard formats like "7d", "24h"
37-
""",
95+
Performance: Depth 1: 100ms, Depth 2: 500ms, Depth 3+: May be slow""",
3896
)
3997
async def build_context(
4098
url: MemoryUrl,

src/basic_memory/mcp/tools/canvas.py

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,103 @@
1616

1717

1818
@mcp.tool(
19-
description="Create an Obsidian canvas file to visualize concepts and connections.",
19+
description="""Creates interactive Obsidian Canvas files to visualize your knowledge graph. Transforms structured node and edge data into spatial maps, enabling visual exploration of relationships and concepts.
20+
21+
```yaml
22+
node:
23+
topic: canvas - Visual Knowledge Maps
24+
goal: Generate Obsidian Canvas visualizations
25+
insight: Spatial representation enhances understanding
26+
context:
27+
format: JSON Canvas 1.0 specification
28+
nodes: [file, text, link, group]
29+
layout: Manual positioning required
30+
compatibility: Full Obsidian support
31+
```
32+
33+
```baml
34+
class CanvasNode {
35+
id string @description("Unique identifier for edges")
36+
type ("file" | "text" | "link" | "group")
37+
x int @description("Horizontal position")
38+
y int @description("Vertical position")
39+
width int @min(100)
40+
height int @min(50)
41+
color string? @pattern("^(#[0-9a-f]{6}|[1-6])$")
42+
43+
// Type-specific fields
44+
file string? @when(type="file") @description("Path to .md file")
45+
text string? @when(type="text") @description("Markdown content")
46+
url string? @when(type="link") @format("uri")
47+
label string? @when(type="group")
48+
}
49+
50+
class CanvasEdge {
51+
id string
52+
fromNode string @description("Source node id")
53+
toNode string @description("Target node id")
54+
fromSide ("left" | "right" | "top" | "bottom")?
55+
toSide ("left" | "right" | "top" | "bottom")?
56+
label string? @description("Edge annotation")
57+
color string? @pattern("^(#[0-9a-f]{6}|[1-6])$")
58+
}
59+
60+
class CanvasInput {
61+
nodes CanvasNode[]
62+
edges CanvasEdge[]
63+
title string
64+
folder string
65+
project string?
66+
}
67+
68+
class CanvasOutput {
69+
status ("created" | "updated")
70+
path string
71+
stats {nodes: int, edges: int}
72+
checksum string
73+
}
74+
75+
function canvas(CanvasInput) -> CanvasOutput {
76+
@description("Generate Obsidian Canvas for knowledge visualization")
77+
@format("json_canvas_1.0")
78+
@async(true)
79+
}
80+
```
81+
82+
## Node Creation Example
83+
```python
84+
canvas(
85+
nodes=[
86+
{
87+
"id": "doc1",
88+
"type": "file",
89+
"file": "docs/architecture.md",
90+
"x": 0, "y": 0,
91+
"width": 400, "height": 300,
92+
"color": "3"
93+
},
94+
{
95+
"id": "note1",
96+
"type": "text",
97+
"text": "# Key Points\n- Scalability",
98+
"x": 500, "y": 0,
99+
"width": 300, "height": 200
100+
}
101+
],
102+
edges=[
103+
{
104+
"id": "e1",
105+
"fromNode": "doc1",
106+
"toNode": "note1",
107+
"label": "summarizes"
108+
}
109+
],
110+
title="Architecture Overview",
111+
folder="visualizations"
112+
)
113+
```
114+
115+
Colors: "1"-"6" or hex "#rrggbb" | Max recommended: 500 nodes""",
20116
)
21117
async def canvas(
22118
nodes: List[Dict[str, Any]],

src/basic_memory/mcp/tools/delete_note.py

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,53 @@ def _format_delete_error_response(project: str, error_message: str, identifier:
148148
If the note should be deleted but the operation keeps failing, send a message to support@basicmemory.com."""
149149

150150

151-
@mcp.tool(description="Delete a note by title or permalink")
151+
@mcp.tool(description="""Safely removes notes from your knowledge base with impact analysis. Identifies orphaned relations and affected notes before deletion, requiring confirmation by default.
152+
153+
```yaml
154+
node:
155+
topic: delete_note - Safe Removal
156+
goal: Delete with relation impact analysis
157+
insight: No recovery after deletion
158+
context:
159+
safety: Orphan detection before delete
160+
confirmation: Required unless forced
161+
impact: Reports all affected relations
162+
```
163+
164+
```baml
165+
class DeleteNoteInput {
166+
identifier string @description("Note title or permalink")
167+
project string?
168+
force boolean @default(false) @description("Skip confirmation")
169+
}
170+
171+
class DeleteNoteOutput {
172+
success boolean
173+
deleted_path string
174+
orphaned_relations string[] @description("Notes with broken links")
175+
affected_notes int
176+
warning string? @when(orphaned_relations.length > 0)
177+
}
178+
179+
function delete_note(DeleteNoteInput) -> DeleteNoteOutput {
180+
@description("Remove note with safety checks")
181+
@requires_confirmation(unless="force")
182+
@irreversible(true)
183+
}
184+
```
185+
186+
## Safety Pattern
187+
```python
188+
# With safety check
189+
result = delete_note("Deprecated API v1")
190+
if result["orphaned_relations"]:
191+
print(f"Warning: {len(result['orphaned_relations'])} broken links")
192+
193+
# Force delete (careful!)
194+
result = delete_note("temp/scratch", force=True)
195+
```
196+
197+
Impact Analysis: Scans all relations | No undo available""")
152198
async def delete_note(
153199
identifier: str, project: Optional[str] = None, context: Context | None = None
154200
) -> bool | str:

src/basic_memory/mcp/tools/edit_note.py

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,68 @@ def _format_error_response(
127127

128128

129129
@mcp.tool(
130-
description="Edit an existing markdown note using various operations like append, prepend, find_replace, or replace_section.",
130+
description="""Performs precise edits to existing notes without full rewrites. Supports append, prepend, find/replace, and section replacement operations while preserving semantic elements and formatting.
131+
132+
```yaml
133+
node:
134+
topic: edit_note - Surgical Edits
135+
goal: Modify notes without full rewrite
136+
insight: Preserves semantic elements during edits
137+
context:
138+
operations: [append, prepend, find_replace, replace_section]
139+
preservation: Maintains observations and relations
140+
atomicity: All edits in single transaction
141+
```
142+
143+
```baml
144+
class EditNoteInput {
145+
identifier string @description("Note to edit")
146+
operation ("append" | "prepend" | "find_replace" | "replace_section")
147+
content string @description("New content")
148+
find_text string? @when(operation="find_replace")
149+
section string? @when(operation="replace_section") @description("Section heading")
150+
expected_replacements int @default(1)
151+
project string?
152+
}
153+
154+
class EditNoteOutput {
155+
success boolean
156+
file_path string
157+
operation string
158+
changes_made int
159+
warnings string[]?
160+
}
161+
162+
function edit_note(EditNoteInput) -> EditNoteOutput {
163+
@description("Precise note modifications preserving structure")
164+
@atomic(true)
165+
@async(true)
166+
}
167+
```
168+
169+
## Operation Patterns
170+
171+
**Append**: Add to end
172+
```python
173+
edit_note("Meeting Notes", "append", "\n## Action Items\n- [ ] Follow up")
174+
```
175+
176+
**Find/Replace**: Exact substitution
177+
```python
178+
edit_note("API Spec", "find_replace",
179+
"https://api.v2.example.com",
180+
find_text="https://api.v1.example.com",
181+
expected_replacements=3)
182+
```
183+
184+
**Section Replace**: Full section update
185+
```python
186+
edit_note("README", "replace_section",
187+
"## Installation\n\nUse pip: `pip install package`",
188+
section="Installation")
189+
```
190+
191+
Performance: Append 20ms, Find/Replace 100ms, Section 150ms""",
131192
)
132193
async def edit_note(
133194
identifier: str,

src/basic_memory/mcp/tools/list_directory.py

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,62 @@
1212

1313

1414
@mcp.tool(
15-
description="List directory contents with filtering and depth control.",
15+
description="""Lists directory contents with filtering and recursive traversal. Detects semantic notes automatically and provides file metadata for comprehensive browsing.
16+
17+
```yaml
18+
node:
19+
topic: list_directory - Structured Browsing
20+
goal: Navigate file system with semantic awareness
21+
insight: Distinguishes notes from regular files
22+
context:
23+
features: [recursive, glob_filtering, metadata]
24+
note_detection: Checks for semantic content
25+
performance: Linear with depth and file count
26+
```
27+
28+
```baml
29+
class ListDirectoryInput {
30+
dir_name string @default("/") @description("Directory path")
31+
project string?
32+
depth int @default(1) @range(1, 5)
33+
file_name_glob string? @pattern("*.md") @description("Filter pattern")
34+
}
35+
36+
class FileEntry {
37+
name string
38+
path string
39+
type ("file" | "directory")
40+
size int @when(type="file")
41+
modified datetime
42+
is_note boolean @description("Contains semantic elements")
43+
}
44+
45+
class ListDirectoryOutput {
46+
entries FileEntry[]
47+
total_files int
48+
total_dirs int
49+
base_path string
50+
}
51+
52+
function list_directory(ListDirectoryInput) -> ListDirectoryOutput {
53+
@description("Browse with semantic note detection")
54+
@async(true)
55+
}
56+
```
57+
58+
## Browse Patterns
59+
```python
60+
# List markdown only
61+
files = list_directory("docs", file_name_glob="*.md")
62+
63+
# Deep scan with filter
64+
files = list_directory("/", depth=3, file_name_glob="README*")
65+
66+
# Browse specific folder
67+
files = list_directory("specs/v2")
68+
```
69+
70+
Performance: Depth 1: 20ms, Depth 2: 150ms, Depth 3+: May be slower""",
1671
)
1772
async def list_directory(
1873
dir_name: str = "/",

0 commit comments

Comments
 (0)