zk_search_notes
Search notes by text, tags, or type to filter and retrieve relevant information efficiently within a Zettelkasten knowledge management system.
Instructions
Search for notes by text, tags, or type. Args: query: Text to search for in titles and content tags: Comma-separated list of tags to filter by note_type: Type of note to filter by limit: Maximum number of results to return
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | ||
| note_type | No | ||
| query | No | ||
| tags | No |
Implementation Reference
- The handler function for zk_search_notes tool that validates inputs, performs the search using SearchService, limits results, formats output with previews, and handles errors.def zk_search_notes( query: str | None = None, tags: str | None = None, note_type: str | None = None, limit: int = 10, ) -> str: """Search for notes using text queries, tags, or note type filters. Args: query: Text to search for in note titles and content (optional) tags: Comma-separated list of tags to filter results (optional) note_type: Filter by note type - one of: fleeting, literature, permanent, structure, hub (optional) limit: Maximum number of results to return (default: 10) """ try: # Convert tags string to list if provided tag_list = None if tags: tag_list = [t.strip() for t in tags.split(",") if t.strip()] # Convert note_type string to enum if provided note_type_enum = None if note_type: try: note_type_enum = NoteType(note_type.lower()) except ValueError: return f"Invalid note type: {note_type}. Valid types are: {', '.join(t.value for t in NoteType)}" # Perform search results = self.search_service.search_combined( text=query, tags=tag_list, note_type=note_type_enum ) # Limit results results = results[:limit] if not results: return "No matching notes found." # Format results output = f"Found {len(results)} matching notes:\n\n" for i, result in enumerate(results, 1): note = result.note output += f"{i}. {note.title} (ID: {note.id})\n" if note.tags: output += ( f" Tags: {', '.join(tag.name for tag in note.tags)}\n" ) output += f" Created: {note.created_at.strftime('%Y-%m-%d')}\n" # Add a snippet of content (first 150 chars) content_preview = note.content[:150].replace("\n", " ") if len(note.content) > 150: content_preview += "..." output += f" Preview: {content_preview}\n\n" return output except Exception as e: return self.format_error_response(e)
- src/zettelkasten_mcp/server/mcp_server.py:379-387 (registration)The @mcp.tool decorator registering the zk_search_notes tool with its name, description, and operational hints.@self.mcp.tool( name="zk_search_notes", description="Search for notes using text queries, tags, or note type filters.", annotations={ "readOnlyHint": True, "destructiveHint": False, "idempotentHint": True, }, )
- Supporting search_combined method in SearchService that performs filtering by tags/note_type/date and keyword scoring in title/content, returning scored SearchResult objects.def search_combined( self, text: Optional[str] = None, tags: Optional[List[str]] = None, note_type: Optional[NoteType] = None, start_date: Optional[datetime] = None, end_date: Optional[datetime] = None, ) -> List[SearchResult]: """Perform a combined search with multiple criteria.""" # Start with all notes all_notes = self.zettel_service.get_all_notes() # Filter by criteria filtered_notes = [] for note in all_notes: # Check note type if note_type and note.note_type != note_type: continue # Check date range if start_date and note.created_at < start_date: continue if end_date and note.created_at > end_date: continue # Check tags if tags: note_tag_names = {tag.name for tag in note.tags} if not any(tag in note_tag_names for tag in tags): continue # Made it through all filters filtered_notes.append(note) # If we have a text query, score the notes results = [] if text: text = text.lower() query_terms = set(text.split()) for note in filtered_notes: score = 0.0 matched_terms: Set[str] = set() matched_context = "" # Check title title_lower = note.title.lower() if text in title_lower: score += 2.0 matched_context = f"Title: {note.title}" for term in query_terms: if term in title_lower: score += 0.5 matched_terms.add(term) # Check content content_lower = note.content.lower() if text in content_lower: score += 1.0 index = content_lower.find(text) start = max(0, index - 40) end = min(len(content_lower), index + len(text) + 40) snippet = note.content[start:end] matched_context = f"Content: ...{snippet}..." for term in query_terms: if term in content_lower: score += 0.2 matched_terms.add(term) # Add to results if score is positive if score > 0: results.append( SearchResult( note=note, score=score, matched_terms=matched_terms, matched_context=matched_context ) ) else: # If no text query, just add all filtered notes with a default score results = [ SearchResult(note=note, score=1.0, matched_terms=set(), matched_context="") for note in filtered_notes ] # Sort by score (descending) results.sort(key=lambda x: x.score, reverse=True) return results