Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 71 additions & 13 deletions src/basic_memory/mcp/tools/build_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,79 @@


@mcp.tool(
description="""Build context from a memory:// URI to continue conversations naturally.

Use this to follow up on previous discussions or explore related topics.
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.

```yaml
node:
topic: build_context - Graph Traversal
goal: Navigate knowledge graph via relations
insight: Memory URLs enable conversation continuity
context:
traversal: Breadth-first with depth control
patterns: ["memory://exact", "memory://folder/*", "memory://*"]
performance: O(n^depth) complexity
use_case: Continue discussions with full context
```

```baml
class BuildContextInput {
url string @pattern("memory://.*") @description("Memory URI pattern")
project string?
depth int @default(1) @range(1, 5) @description("Relation traversal depth")
timeframe string @default("7d") @description("Period like '2 days ago'")
types string[]? @description("Filter entity types")
page int @default(1)
page_size int @default(10)
max_related int @default(10)
}

class ContextResult {
primary_results Note[] @description("Direct matches")
related_results Note[] @description("Connected via relations")
metadata ContextMetadata
}

class ContextMetadata {
depth int
timeframe string
primary_count int
related_count int
generated_at datetime
}

function build_context(BuildContextInput) -> ContextResult {
@description("Traverse knowledge graph from memory:// starting points")
@complexity("O(n^depth)")
@async(true)
}
```

## Memory Patterns

- `memory://specs/search` - Exact note
- `memory://specs/*` - All in folder
- `memory://*` - Everything

## Traversal Examples
```python
# Continue discussion
context = build_context("memory://discussions/api-design")

# Deep exploration (2 hops)
context = build_context(
"memory://architecture/microservices",
depth=2,
timeframe="30d"
)

Memory URL Format:
- Use paths like "folder/note" or "memory://folder/note"
- Pattern matching: "folder/*" matches all notes in folder
- Valid characters: letters, numbers, hyphens, underscores, forward slashes
- Avoid: double slashes (//), angle brackets (<>), quotes, pipes (|)
- Examples: "specs/search", "projects/basic-memory", "notes/*"
# Filtered traversal
context = build_context(
"memory://specs/*",
types=["entity", "observation"]
)
```

Timeframes support natural language like:
- "2 days ago", "last week", "today", "3 months ago"
- Or standard formats like "7d", "24h"
""",
Performance: Depth 1: 100ms, Depth 2: 500ms, Depth 3+: May be slow""",
)
async def build_context(
url: MemoryUrl,
Expand Down
98 changes: 97 additions & 1 deletion src/basic_memory/mcp/tools/canvas.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,103 @@


@mcp.tool(
description="Create an Obsidian canvas file to visualize concepts and connections.",
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.

```yaml
node:
topic: canvas - Visual Knowledge Maps
goal: Generate Obsidian Canvas visualizations
insight: Spatial representation enhances understanding
context:
format: JSON Canvas 1.0 specification
nodes: [file, text, link, group]
layout: Manual positioning required
compatibility: Full Obsidian support
```

```baml
class CanvasNode {
id string @description("Unique identifier for edges")
type ("file" | "text" | "link" | "group")
x int @description("Horizontal position")
y int @description("Vertical position")
width int @min(100)
height int @min(50)
color string? @pattern("^(#[0-9a-f]{6}|[1-6])$")

// Type-specific fields
file string? @when(type="file") @description("Path to .md file")
text string? @when(type="text") @description("Markdown content")
url string? @when(type="link") @format("uri")
label string? @when(type="group")
}

class CanvasEdge {
id string
fromNode string @description("Source node id")
toNode string @description("Target node id")
fromSide ("left" | "right" | "top" | "bottom")?
toSide ("left" | "right" | "top" | "bottom")?
label string? @description("Edge annotation")
color string? @pattern("^(#[0-9a-f]{6}|[1-6])$")
}

class CanvasInput {
nodes CanvasNode[]
edges CanvasEdge[]
title string
folder string
project string?
}

class CanvasOutput {
status ("created" | "updated")
path string
stats {nodes: int, edges: int}
checksum string
}

function canvas(CanvasInput) -> CanvasOutput {
@description("Generate Obsidian Canvas for knowledge visualization")
@format("json_canvas_1.0")
@async(true)
}
```

## Node Creation Example
```python
canvas(
nodes=[
{
"id": "doc1",
"type": "file",
"file": "docs/architecture.md",
"x": 0, "y": 0,
"width": 400, "height": 300,
"color": "3"
},
{
"id": "note1",
"type": "text",
"text": "# Key Points\n- Scalability",
"x": 500, "y": 0,
"width": 300, "height": 200
}
],
edges=[
{
"id": "e1",
"fromNode": "doc1",
"toNode": "note1",
"label": "summarizes"
}
],
title="Architecture Overview",
folder="visualizations"
)
```

Colors: "1"-"6" or hex "#rrggbb" | Max recommended: 500 nodes""",
)
async def canvas(
nodes: List[Dict[str, Any]],
Expand Down
48 changes: 47 additions & 1 deletion src/basic_memory/mcp/tools/delete_note.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,53 @@ def _format_delete_error_response(project: str, error_message: str, identifier:
If the note should be deleted but the operation keeps failing, send a message to support@basicmemory.com."""


@mcp.tool(description="Delete a note by title or permalink")
@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.

```yaml
node:
topic: delete_note - Safe Removal
goal: Delete with relation impact analysis
insight: No recovery after deletion
context:
safety: Orphan detection before delete
confirmation: Required unless forced
impact: Reports all affected relations
```

```baml
class DeleteNoteInput {
identifier string @description("Note title or permalink")
project string?
force boolean @default(false) @description("Skip confirmation")
}

class DeleteNoteOutput {
success boolean
deleted_path string
orphaned_relations string[] @description("Notes with broken links")
affected_notes int
warning string? @when(orphaned_relations.length > 0)
}

function delete_note(DeleteNoteInput) -> DeleteNoteOutput {
@description("Remove note with safety checks")
@requires_confirmation(unless="force")
@irreversible(true)
}
```

## Safety Pattern
```python
# With safety check
result = delete_note("Deprecated API v1")
if result["orphaned_relations"]:
print(f"Warning: {len(result['orphaned_relations'])} broken links")

# Force delete (careful!)
result = delete_note("temp/scratch", force=True)
```

Impact Analysis: Scans all relations | No undo available""")
async def delete_note(
identifier: str, project: Optional[str] = None, context: Context | None = None
) -> bool | str:
Expand Down
63 changes: 62 additions & 1 deletion src/basic_memory/mcp/tools/edit_note.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,68 @@ def _format_error_response(


@mcp.tool(
description="Edit an existing markdown note using various operations like append, prepend, find_replace, or replace_section.",
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.

```yaml
node:
topic: edit_note - Surgical Edits
goal: Modify notes without full rewrite
insight: Preserves semantic elements during edits
context:
operations: [append, prepend, find_replace, replace_section]
preservation: Maintains observations and relations
atomicity: All edits in single transaction
```

```baml
class EditNoteInput {
identifier string @description("Note to edit")
operation ("append" | "prepend" | "find_replace" | "replace_section")
content string @description("New content")
find_text string? @when(operation="find_replace")
section string? @when(operation="replace_section") @description("Section heading")
expected_replacements int @default(1)
project string?
}

class EditNoteOutput {
success boolean
file_path string
operation string
changes_made int
warnings string[]?
}

function edit_note(EditNoteInput) -> EditNoteOutput {
@description("Precise note modifications preserving structure")
@atomic(true)
@async(true)
}
```

## Operation Patterns

**Append**: Add to end
```python
edit_note("Meeting Notes", "append", "\n## Action Items\n- [ ] Follow up")
```

**Find/Replace**: Exact substitution
```python
edit_note("API Spec", "find_replace",
"https://api.v2.example.com",
find_text="https://api.v1.example.com",
expected_replacements=3)
```

**Section Replace**: Full section update
```python
edit_note("README", "replace_section",
"## Installation\n\nUse pip: `pip install package`",
section="Installation")
```

Performance: Append 20ms, Find/Replace 100ms, Section 150ms""",
)
async def edit_note(
identifier: str,
Expand Down
57 changes: 56 additions & 1 deletion src/basic_memory/mcp/tools/list_directory.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,62 @@


@mcp.tool(
description="List directory contents with filtering and depth control.",
description="""Lists directory contents with filtering and recursive traversal. Detects semantic notes automatically and provides file metadata for comprehensive browsing.

```yaml
node:
topic: list_directory - Structured Browsing
goal: Navigate file system with semantic awareness
insight: Distinguishes notes from regular files
context:
features: [recursive, glob_filtering, metadata]
note_detection: Checks for semantic content
performance: Linear with depth and file count
```

```baml
class ListDirectoryInput {
dir_name string @default("/") @description("Directory path")
project string?
depth int @default(1) @range(1, 5)
file_name_glob string? @pattern("*.md") @description("Filter pattern")
}

class FileEntry {
name string
path string
type ("file" | "directory")
size int @when(type="file")
modified datetime
is_note boolean @description("Contains semantic elements")
}

class ListDirectoryOutput {
entries FileEntry[]
total_files int
total_dirs int
base_path string
}

function list_directory(ListDirectoryInput) -> ListDirectoryOutput {
@description("Browse with semantic note detection")
@async(true)
}
```

## Browse Patterns
```python
# List markdown only
files = list_directory("docs", file_name_glob="*.md")

# Deep scan with filter
files = list_directory("/", depth=3, file_name_glob="README*")

# Browse specific folder
files = list_directory("specs/v2")
```

Performance: Depth 1: 20ms, Depth 2: 150ms, Depth 3+: May be slower""",
)
async def list_directory(
dir_name: str = "/",
Expand Down
Loading
Loading