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
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure. It mentions search functionality but doesn't describe the return format (e.g., list of note objects), pagination behavior (implied by 'limit'), error conditions, or performance characteristics. For a search tool with zero annotation coverage, this leaves significant gaps in understanding how the tool behaves.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is appropriately sized and front-loaded with the core purpose in the first sentence. The parameter explanations are clear and efficient, though the formatting with 'Args:' and indentation is slightly verbose. Every sentence adds value, with no redundant information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given 4 parameters with 0% schema coverage and no output schema, the description does a decent job explaining parameter semantics but falls short on behavioral aspects. It doesn't describe the return structure (critical for a search tool) or error handling. For a search operation with multiple siblings, more context about differences would be helpful.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The description adds substantial value beyond the input schema, which has 0% description coverage. It explains each parameter's purpose: 'query' searches titles and content, 'tags' is comma-separated, 'note_type' filters by type, and 'limit' sets maximum results. This compensates well for the schema's lack of descriptions, though it doesn't specify allowed note types or tag formats.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: 'Search for notes by text, tags, or type.' This specifies the verb ('search') and resource ('notes') with three search dimensions. It distinguishes from siblings like 'zk_list_notes_by_date' (date-based listing) and 'zk_find_similar_notes' (similarity-based), though not explicitly named.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No guidance is provided on when to use this tool versus alternatives. It doesn't mention when to prefer this over 'zk_find_similar_notes' (semantic similarity) or 'zk_list_notes_by_date' (date-based listing), nor does it specify prerequisites or exclusions. The description only states what it does, not when to use it.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

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