search_by_property
Search for notes by frontmatter property values to quickly locate specific content based on metadata fields in your Obsidian vault.
Instructions
Search for notes by frontmatter property (metadata field)
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | ||
| property_name | Yes | ||
| property_value | No |
Implementation Reference
- src/obsidian_mcp/server.py:516-567 (handler)Primary MCP tool handler for 'search_by_property'. Validates inputs, invokes the VaultSearch method, formats results as markdown list.name="search_by_property", description="Search for notes by frontmatter property (metadata field)", ) async def search_by_property(property_name: str, property_value: str = "", limit: int = 50) -> str: """ Search for notes by frontmatter property. Args: property_name: Name of the frontmatter property to search property_value: Optional value to match (empty to find all notes with this property) limit: Maximum number of results (default: 50) Returns: Formatted list of notes matching the property """ if not property_name or not property_name.strip(): return "Error: Property name cannot be empty" if limit <= 0 or limit > 1000: return "Error: Limit must be between 1 and 1000" context = _get_context() try: # Convert empty string to None for "any value" search value = property_value if property_value else None results = await context.search.search_by_property(property_name, value, limit=limit) if not results: if value: return f"No notes found with property '{property_name}' = '{value}'" else: return f"No notes found with property '{property_name}'" # Format results if value: output = f"Found {len(results)} note(s) with '{property_name}' = '{value}':\n\n" else: output = f"Found {len(results)} note(s) with property '{property_name}':\n\n" for i, result in enumerate(results, 1): output += f"{i}. **{result.name}**\n" output += f" Path: `{result.path}`\n" if result.snippet: output += f" {result.snippet}\n" output += "\n" return output except Exception as e: logger.exception(f"Error searching by property: {property_name}") return f"Error searching by property: {e}"
- src/obsidian_mcp/search.py:199-264 (helper)Core implementation logic in VaultSearch class that scans notes, matches frontmatter properties, scores results, and returns SearchResult list.async def search_by_property( self, property_name: str, property_value: str | None = None, limit: int = 50 ) -> list[SearchResult]: """ Search for notes by frontmatter property. Args: property_name: Name of the frontmatter property property_value: Optional value to match (if None, matches any note with the property) limit: Maximum number of results Returns: List of search results """ results: list[SearchResult] = [] notes = self.vault.list_notes(limit=None) for note_meta in notes: if len(results) >= limit: break try: note = await self.vault.read_note(note_meta.path) if not note.frontmatter: continue # Check if property exists if property_name not in note.frontmatter: continue prop_val = note.frontmatter[property_name] # If no value specified, just match presence if property_value is None: score = 1.0 else: # Check if value matches if isinstance(prop_val, list): # Check if value is in list if property_value in prop_val or property_value in str(prop_val): score = 2.0 else: continue elif str(prop_val).lower() == property_value.lower(): score = 5.0 # Exact match elif property_value.lower() in str(prop_val).lower(): score = 2.0 # Partial match else: continue # Create snippet from frontmatter snippet = f"{property_name}: {prop_val}" results.append( SearchResult( path=note_meta.path, name=note_meta.name, score=score, snippet=snippet ) ) except (OSError, UnicodeDecodeError) as e: logger.debug(f"Failed to read note {note_meta.path}: {e}") continue results.sort(key=lambda r: r.score, reverse=True) return results[:limit]
- src/obsidian_mcp/search.py:14-23 (schema)Dataclass defining the structure of search results used by search_by_property.@dataclass(slots=True, frozen=True) class SearchResult: """A search result with context (immutable).""" path: str name: str score: float snippet: str | None = None matched_tags: list[str] | None = None
- src/obsidian_mcp/server.py:516-519 (registration)MCP tool registration decorator specifying the tool name and description.name="search_by_property", description="Search for notes by frontmatter property (metadata field)", ) async def search_by_property(property_name: str, property_value: str = "", limit: int = 50) -> str: