diff --git a/src/basic_memory/mcp/tools/build_context.py b/src/basic_memory/mcp/tools/build_context.py index a8c797186..a141aeff3 100644 --- a/src/basic_memory/mcp/tools/build_context.py +++ b/src/basic_memory/mcp/tools/build_context.py @@ -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, diff --git a/src/basic_memory/mcp/tools/canvas.py b/src/basic_memory/mcp/tools/canvas.py index 2bb320d6e..9e17aba00 100644 --- a/src/basic_memory/mcp/tools/canvas.py +++ b/src/basic_memory/mcp/tools/canvas.py @@ -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]], diff --git a/src/basic_memory/mcp/tools/delete_note.py b/src/basic_memory/mcp/tools/delete_note.py index 1bde5104e..9a4debae3 100644 --- a/src/basic_memory/mcp/tools/delete_note.py +++ b/src/basic_memory/mcp/tools/delete_note.py @@ -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: diff --git a/src/basic_memory/mcp/tools/edit_note.py b/src/basic_memory/mcp/tools/edit_note.py index 73566e1e3..54be8bb1d 100644 --- a/src/basic_memory/mcp/tools/edit_note.py +++ b/src/basic_memory/mcp/tools/edit_note.py @@ -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, diff --git a/src/basic_memory/mcp/tools/list_directory.py b/src/basic_memory/mcp/tools/list_directory.py index 4f36e7eae..0481e6c69 100644 --- a/src/basic_memory/mcp/tools/list_directory.py +++ b/src/basic_memory/mcp/tools/list_directory.py @@ -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 = "/", diff --git a/src/basic_memory/mcp/tools/move_note.py b/src/basic_memory/mcp/tools/move_note.py index 1d3606f30..5a872444e 100644 --- a/src/basic_memory/mcp/tools/move_note.py +++ b/src/basic_memory/mcp/tools/move_note.py @@ -338,7 +338,53 @@ def _format_move_error_response(error_message: str, identifier: str, destination @mcp.tool( - description="Move a note to a new location, updating database and maintaining links.", + description="""Relocates notes while maintaining knowledge graph integrity. Updates all incoming relations automatically and creates redirects from the old location. + +```yaml +node: + topic: move_note - Graph-Aware Relocation + goal: Move notes with automatic relation updates + insight: Maintains graph connectivity during moves + context: + features: [relation_updates, permalink_generation, redirects] + atomicity: All updates in single transaction + performance: Linear with relation count +``` + +```baml +class MoveNoteInput { + identifier string @description("Current location") + destination_path string @description("New location") + project string? + update_relations boolean @default(true) +} + +class MoveNoteOutput { + success boolean + old_path string + new_path string + new_permalink string + relations_updated int + redirects_created boolean +} + +function move_note(MoveNoteInput) -> MoveNoteOutput { + @description("Relocate with automatic relation updates") + @atomic(true) + @async(true) +} +``` + +## Move Examples +```python +# With automatic updates +move_note("drafts/api-design", "specs/v2/api-design") + +# Without relation updates (faster but breaks links) +move_note("temp/notes", "archive/old", update_relations=False) +``` + +Performance: Base 50ms + 10ms per relation | Atomic transaction""", ) async def move_note( identifier: str, diff --git a/src/basic_memory/mcp/tools/project_management.py b/src/basic_memory/mcp/tools/project_management.py index 969f493cf..4ce0bf5cd 100644 --- a/src/basic_memory/mcp/tools/project_management.py +++ b/src/basic_memory/mcp/tools/project_management.py @@ -18,28 +18,55 @@ from basic_memory.utils import generate_permalink -@mcp.tool("list_memory_projects") +@mcp.tool("list_memory_projects", + description="""Discovers all configured Basic Memory projects. Essential first step for multi-project environments to identify available knowledge bases. + +```yaml +node: + topic: list_memory_projects - Discovery + goal: Find all available projects + insight: Entry point for project selection + context: + caching: 60 second TTL + includes: [status, file_count, last_sync] +``` + +```baml +class Project { + name string + path string + is_default boolean + status ("active" | "inactive" | "error") + file_count int + last_sync datetime? +} + +class ListProjectsOutput { + projects Project[] + default_project string? + total int +} + +function list_memory_projects() -> ListProjectsOutput { + @description("Discover all configured projects") + @cache_ttl(60) + @async(true) +} +``` + +## Usage +```python +projects = list_memory_projects() +for p in projects["projects"]: + print(f"{p['name']}: {p['path']}") + if p["is_default"]: + print(" (default)") +``` + +Performance: 20-60ms | Cached for 60 seconds""" +) async def list_memory_projects(context: Context | None = None) -> str: - """List all available projects with their status. - - Shows all Basic Memory projects that are available for MCP operations. - Use this tool to discover projects when you need to know which project to use. - - Use this tool: - - At conversation start when project is unknown - - When user asks about available projects - - Before any operation requiring a project - - After calling: - - Ask user which project to use - - Remember their choice for the session - - Returns: - Formatted list of projects with session management guidance - - Example: - list_memory_projects() - """ + """List all available projects with their status.""" async with get_client() as client: if context: # pragma: no cover await context.info("Listing all available projects") @@ -71,27 +98,47 @@ async def list_memory_projects(context: Context | None = None) -> str: return result -@mcp.tool("create_memory_project") +@mcp.tool("create_memory_project", + description="""Initializes a new Basic Memory project at the specified location. Creates necessary structure and optionally sets as default project. + +```yaml +node: + topic: create_memory_project - Initialization + goal: Setup new knowledge base project + insight: One-time project bootstrap +``` + +```baml +class CreateProjectInput { + project_name string @pattern("^[a-z0-9-]+$") + project_path string @format("path") + set_default boolean @default(false) +} + +class CreateProjectOutput { + success boolean + project {name: string, path: string} + message string +} + +function create_memory_project(CreateProjectInput) -> CreateProjectOutput { + @description("Initialize new project") + @idempotent(true) +} +``` + +```python +create_memory_project( + "research-2024", + "/Users/me/research/2024", + set_default=True +) +```""" +) async def create_memory_project( project_name: str, project_path: str, set_default: bool = False, context: Context | None = None ) -> str: - """Create a new Basic Memory project. - - Creates a new project with the specified name and path. The project directory - will be created if it doesn't exist. Optionally sets the new project as default. - - Args: - project_name: Name for the new project (must be unique) - project_path: File system path where the project will be stored - set_default: Whether to set this project as the default (optional, defaults to False) - - Returns: - Confirmation message with project details - - Example: - create_memory_project("my-research", "~/Documents/research") - create_memory_project("work-notes", "/home/user/work", set_default=True) - """ + """Create a new Basic Memory project.""" async with get_client() as client: # Check if server is constrained to a specific project constrained_project = os.environ.get("BASIC_MEMORY_MCP_PROJECT") @@ -126,27 +173,47 @@ async def create_memory_project( return result -@mcp.tool() +@mcp.tool( + description="""Removes a project from Basic Memory configuration. Files remain on disk but project is no longer tracked. Requires re-addition to access content again. + +```yaml +node: + topic: delete_project - Project Removal + goal: Remove project from configuration + insight: Files preserved, only tracking removed + context: + safety: No file deletion + reversibility: Re-add project to restore access +``` + +```baml +class DeleteProjectInput { + project_name string @description("Project to remove") +} + +class DeleteProjectOutput { + success boolean + removed_project {name: string, path: string?} + message string + warning string @default("Files remain on disk") +} + +function delete_project(DeleteProjectInput) -> DeleteProjectOutput { + @description("Remove project tracking") + @safe(true) // No file deletion + @async(true) +} +``` + +```python +# Remove old project +delete_project("archived-research") +``` + +Performance: 30-100ms | No files deleted | Reversible""" +) async def delete_project(project_name: str, context: Context | None = None) -> str: - """Delete a Basic Memory project. - - Removes a project from the configuration and database. This does NOT delete - the actual files on disk - only removes the project from Basic Memory's - configuration and database records. - - Args: - project_name: Name of the project to delete - - Returns: - Confirmation message about project deletion - - Example: - delete_project("old-project") - - Warning: - This action cannot be undone. The project will need to be re-added - to access its content through Basic Memory again. - """ + """Delete a Basic Memory project.""" async with get_client() as client: # Check if server is constrained to a specific project constrained_project = os.environ.get("BASIC_MEMORY_MCP_PROJECT") diff --git a/src/basic_memory/mcp/tools/read_content.py b/src/basic_memory/mcp/tools/read_content.py index c15ca2826..d098673e5 100644 --- a/src/basic_memory/mcp/tools/read_content.py +++ b/src/basic_memory/mcp/tools/read_content.py @@ -148,7 +148,74 @@ def optimize_image(img, content_length, max_output_bytes=350000): return buf.getvalue() -@mcp.tool(description="Read a file's raw content by path or permalink") +@mcp.tool(description="""Provides raw file access for any content type including text, images, PDFs, and notebooks. Returns appropriate encoding based on file type with automatic optimization for images. + +```yaml +node: + topic: read_content - Universal File Reader + goal: Access any file type with appropriate handling + insight: Handles binary and text with smart encoding + context: + text_types: [md, txt, yaml, json, code] + binary_types: [images, pdfs, documents] + special: [notebooks with cell parsing] + size_limit: 10MB for binary files +``` + +```baml +class ReadContentInput { + path string @description("File path or memory:// URL") + project string? +} + +class TextContent { + type: "text" + text string + encoding string @default("utf-8") + content_type string +} + +class BinaryContent { + type: ("image" | "document") + source { + type: "base64" + media_type string + data string @encoding("base64") + } +} + +class ErrorContent { + type: "error" + error string +} + +type ReadContentOutput = TextContent | BinaryContent | ErrorContent + +function read_content(ReadContentInput) -> ReadContentOutput { + @description("Universal file reader with type detection") + @max_size("10MB") + @async(true) +} +``` + +## Type Handling +```python +# Text file +result = read_content("config.yaml") +if result["type"] == "text": + config = result["text"] + +# Image with base64 +result = read_content("logo.png") +if result["type"] == "image": + base64_data = result["source"]["data"] + # Use in HTML: + +# Memory URL +result = read_content("memory://assets/template") +``` + +Performance: Text 10-50ms, Images 50-200ms (with optimization)""") async def read_content( path: str, project: Optional[str] = None, context: Context | None = None ) -> dict: diff --git a/src/basic_memory/mcp/tools/read_note.py b/src/basic_memory/mcp/tools/read_note.py index 29dc29964..25b2383a3 100644 --- a/src/basic_memory/mcp/tools/read_note.py +++ b/src/basic_memory/mcp/tools/read_note.py @@ -16,7 +16,66 @@ @mcp.tool( - description="Read a markdown note by title or permalink.", + description="""Retrieves complete notes from your knowledge base using flexible lookup methods. Finds notes by title, permalink, or memory:// URL, with support for content search and pagination when multiple matches exist. + +```yaml +node: + topic: read_note - Knowledge Retrieval + goal: Access notes with full semantic context + insight: Multi-strategy lookup with graceful fallbacks + context: + lookup_order: [permalink, title, content_search] + performance: {direct: 10ms, search: 50ms, scan: 200ms} + returns: Full markdown with frontmatter and semantic elements +``` + +```baml +class ReadNoteInput { + identifier string @description("Title, permalink, or memory:// URL") + project string? @description("Target project") + page int @default(1) @description("Page number for results") + page_size int @default(10) @description("Items per page") +} + +class ReadNoteOutput { + content string @description("Full markdown with frontmatter") + title string + permalink string + type string + tags string[]? + found boolean + suggestions string[]? @description("Similar notes if not found") +} + +function read_note(ReadNoteInput) -> ReadNoteOutput { + @description("Retrieves notes with automatic fallback strategies") + @cache_ttl(300) + @async(true) +} +``` + +## Lookup Strategies + +1. **Direct permalink** → `specs/api-design` (fastest, most reliable) +2. **Title match** → `"API Design"` (human-friendly) +3. **Content search** → Partial text match (fallback) + +## Usage Examples +```python +# By title +note = read_note("Project Roadmap") + +# By permalink (stable across renames) +note = read_note("specs/2024/api-design") + +# Memory URL +note = read_note("memory://specs/api-v2") + +# With pagination +results = read_note("meeting", page=2, page_size=5) +``` + +Performance: Direct 10-50ms, Search 50-200ms | Cache: 5 minutes""", ) async def read_note( identifier: str, diff --git a/src/basic_memory/mcp/tools/recent_activity.py b/src/basic_memory/mcp/tools/recent_activity.py index 74c9a3fc0..f1d824145 100644 --- a/src/basic_memory/mcp/tools/recent_activity.py +++ b/src/basic_memory/mcp/tools/recent_activity.py @@ -20,16 +20,77 @@ @mcp.tool( - description="""Get recent activity for a project or across all projects. - - Timeframe supports natural language formats like: - - "2 days ago" - - "last week" - - "yesterday" - - "today" - - "3 weeks ago" - Or standard formats like "7d" - """, + description="""Tracks recent changes across your knowledge base with flexible timeframe specifications. Essential for staying updated on evolving knowledge and monitoring project activity. + +```yaml +node: + topic: recent_activity - Change Tracking + goal: Monitor knowledge base evolution + insight: Temporal index for efficient change detection + context: + formats: [duration, natural, relative] + index: Optimized temporal lookup + depth: Follow relations in changes + use_case: Daily reviews, project monitoring +``` + +```baml +class RecentActivityInput { + project string? + timeframe string @default("7d") @description("Period: '24h', 'last week', 'yesterday'") + types (string | string[])? @description("Filter by types") + depth int @default(1) @description("Follow relations") +} + +class ActivityItem { + id string + title string + type string + permalink string + action ("created" | "modified" | "deleted") + timestamp datetime + changes Change[]? +} + +class RecentActivityOutput { + results ActivityItem[] + timeframe string + project string + total_changes int +} + +function recent_activity(RecentActivityInput) -> RecentActivityOutput { + @description("Track changes with temporal filtering") + @index("temporal") + @async(true) +} +``` + +## Timeframe Formats + +**Duration**: `"7d"`, `"24h"`, `"30d"` +**Natural**: `"2 days ago"`, `"last week"` +**Relative**: `"today"`, `"yesterday"`, `"this month"` + +## Usage Patterns +```python +# Today's changes +activity = recent_activity(timeframe="today") + +# Filtered by type +activity = recent_activity( + timeframe="3 days", + types=["entity", "observation"] +) + +# Follow relations +activity = recent_activity( + timeframe="1 week", + depth=2 +) +``` + +Performance: 7d: 30-150ms, Month: 100-300ms | Index: Temporal optimization""", ) async def recent_activity( type: Union[str, List[str]] = "", diff --git a/src/basic_memory/mcp/tools/search.py b/src/basic_memory/mcp/tools/search.py index b1cbd3c89..b91e9dc3a 100644 --- a/src/basic_memory/mcp/tools/search.py +++ b/src/basic_memory/mcp/tools/search.py @@ -197,7 +197,80 @@ def _format_search_error_response( @mcp.tool( - description="Search across all content in the knowledge base with advanced syntax support.", + description="""Full-text search across your knowledge base with advanced filtering and Boolean operators. Supports content, title, and permalink searches with temporal and type filtering for precise knowledge discovery. + +```yaml +node: + topic: search_notes - Discovery Engine + goal: Find relevant knowledge with precision + insight: FTS5-powered search with semantic awareness + context: + engine: SQLite FTS5 + operators: [AND, OR, NOT, phrases, wildcards] + filters: [types, dates, categories, tags] + performance: Sub-100ms for most queries +``` + +```baml +class SearchNotesInput { + query string @description("Search query with operators") + project string? + search_type string @default("text") @enum(["text", "title", "permalink"]) + types string[]? @description("Filter by note types") + entity_types string[]? @description("Filter by entity types") + after_date string? @description("Temporal filter like '1 week'") + page int @default(1) + page_size int @default(10) +} + +class SearchResult { + id string + title string + type string + permalink string + score float @range(0, 1) + preview string @max_length(200) +} + +class SearchNotesOutput { + results SearchResult[] + total int + page int + has_more boolean +} + +function search_notes(SearchNotesInput) -> SearchNotesOutput { + @description("Advanced search with Boolean operators and filters") + @index("fts5") + @async(true) +} +``` + +## Search Syntax + +**Boolean**: `(bug OR issue) AND NOT resolved` +**Phrases**: `"exact phrase match"` +**Wildcards**: `project*` (permalink only) +**Special**: `tag:api`, `category:decision` + +## Example Queries +```python +# Complex Boolean +search_notes("(bug OR issue) AND NOT resolved") + +# Title search +search_notes("Architecture", search_type="title") + +# Filtered search +search_notes("performance", + types=["entity"], + after_date="1 week") + +# Permalink patterns +search_notes("docs/2024-*", search_type="permalink") +``` + +Performance: FTS5 <100ms | Index: Real-time updates""", ) async def search_notes( query: str, diff --git a/src/basic_memory/mcp/tools/sync_status.py b/src/basic_memory/mcp/tools/sync_status.py index c4162b61b..43cc19637 100644 --- a/src/basic_memory/mcp/tools/sync_status.py +++ b/src/basic_memory/mcp/tools/sync_status.py @@ -65,21 +65,57 @@ def _get_all_projects_status() -> list[str]: @mcp.tool( - description="""Check the status of file synchronization and background operations. - - Use this tool to: - - Check if file sync is in progress or completed - - Get detailed sync progress information - - Understand if your files are fully indexed - - Get specific error details if sync operations failed - - Monitor initial project setup and legacy migration - - This covers all sync operations including: - - Initial project setup and file indexing - - Legacy project migration to unified database - - Ongoing file monitoring and updates - - Background processing of knowledge graphs - """, + description="""Monitors file synchronization and background operations. Essential for understanding system state during indexing, migrations, and sync operations. + +```yaml +node: + topic: sync_status - Operation Monitor + goal: Track system synchronization state + insight: Real-time visibility into background ops + context: + states: [idle, indexing, migrating, syncing, error] + monitoring: Progress tracking with estimates + use_case: Pre-operation checks, debugging +``` + +```baml +class SyncStatusInput { + project string? + verbose boolean @default(false) +} + +class SyncStatusOutput { + state ("idle" | "indexing" | "migrating" | "syncing" | "error") + progress float? @range(0, 100) + message string + files_processed int? + files_total int? + errors string[]? + estimated_time string? @format("duration") +} + +function sync_status(SyncStatusInput) -> SyncStatusOutput { + @description("Monitor synchronization and background operations") + @real_time(true) + @async(true) +} +``` + +## Monitoring Patterns +```python +# Basic check +status = sync_status() +if status["state"] == "idle": + print("System ready") + +# Detailed monitoring +status = sync_status(verbose=True) +if status["state"] == "indexing": + print(f"Progress: {status['progress']}%") + print(f"Files: {status['files_processed']}/{status['files_total']}") +``` + +Performance: Check 10-30ms | Non-blocking | Real-time updates""", ) async def sync_status(project: Optional[str] = None, context: Context | None = None) -> str: """Get current sync status and system readiness information. diff --git a/src/basic_memory/mcp/tools/write_note.py b/src/basic_memory/mcp/tools/write_note.py index ef07e2023..c06703426 100644 --- a/src/basic_memory/mcp/tools/write_note.py +++ b/src/basic_memory/mcp/tools/write_note.py @@ -21,7 +21,88 @@ @mcp.tool( - description="Create or update a markdown note. Returns a markdown formatted summary of the semantic content.", + description="""Creates or updates markdown notes with automatic semantic enrichment, building your knowledge graph through observations (categorized facts) and relations (WikiLink connections). Supports idempotent operations with full project context awareness. + +```yaml +node: + topic: write_note - Knowledge Creation + goal: Create/update notes with semantic enrichment + insight: Primary knowledge ingestion point with graph building + context: + observations: [fact, idea, decision, question, task, problem, solution, tech, design, requirement] + relations: [relates_to, depends_on, implements, extends, part_of, follows, precedes, inspired_by, contradicts, example_of] + pattern: Markdown-based semantic extraction with WikiLink relations +``` + +```baml +class WriteNoteInput { + title string @description("Note title - becomes entity identity") + content string @description("Markdown with semantic observations and relations") + folder string @description("Folder path for organization") + project string? @description("Optional project name") + tags (string | string[])? @description("Categorization tags") + entity_type string @default("note") @description("Entity type: note, guide, report, etc.") +} + +class WriteNoteOutput { + action ("Created" | "Updated") + project string + file_path string + permalink string + checksum string + observations ObservationCount? + relations RelationCount? + tags string[]? +} + +class ObservationCount { + [category: string]: int @description("Count by observation category") +} + +class RelationCount { + resolved int + unresolved int +} + +function write_note(WriteNoteInput) -> WriteNoteOutput { + @description("Creates or updates a note with semantic knowledge graph integration") + @async(true) + @idempotent(true) + @retry_policy(max_attempts: 3, backoff: exponential) +} +``` + +## Semantic Patterns + +**Observations**: `- [category] Text #tag (context)` +```markdown +- [decision] Use REST over GraphQL #api (Better caching) +- [requirement] Response time < 100ms #performance +- [task] Implement error handling #todo +``` + +**Relations**: `- relation_type [[Target]] (reason)` +```markdown +- implements [[API Spec v2]] +- depends_on [[Auth Service]] (For token validation) +``` + +**Inline**: `Text with [[Entity]] reference` creates automatic `relates_to` + +## Example Usage +```python +write_note( + title="API Design", + folder="specs", + content='''# API Design +- [decision] REST architecture #api +- implements [[API Spec v2]] +See [[Guidelines]] for standards.''', + tags="api,rest" +) +``` + +Performance: 50-200ms | Errors: Project not found, Path traversal, Migration active""", ) async def write_note( title: str,