Skip to main content
Glama

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
NameRequiredDescriptionDefault
limitNo
note_typeNo
queryNo
tagsNo

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)
  • 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
Install Server

Other Tools

Related Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Liam-Deacon/zettelkasten-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server