search_bookmarks
Search your Pinboard bookmarks by query across titles, notes, and tags, focusing on recent entries first.
Instructions
Search bookmarks by query string across titles, notes, and tags (recent focus).
Args:
query: Search query to match against bookmark titles, notes, and tags
limit: Maximum number of results to return (1-100, default 20)
Note: Searches recent bookmarks first, expands automatically if needed.
For comprehensive historical search, use search_bookmarks_extended.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | ||
| limit | No |
Implementation Reference
- src/pinboard_mcp_server/main.py:19-40 (handler)MCP tool handler decorated with @mcp.tool. Validates limit, calls PinboardClient.search_bookmarks, formats response as dict with bookmarks list using model_dump().@mcp.tool async def search_bookmarks(query: str, limit: int = 20) -> dict[str, Any]: """Search bookmarks by query string across titles, notes, and tags (recent focus). Args: query: Search query to match against bookmark titles, notes, and tags limit: Maximum number of results to return (1-100, default 20) Note: Searches recent bookmarks first, expands automatically if needed. For comprehensive historical search, use search_bookmarks_extended. """ if not 1 <= limit <= 100: raise ValueError("Limit must be between 1 and 100") bookmarks = await client.search_bookmarks(query=query, limit=limit) return { "bookmarks": [bookmark.model_dump() for bookmark in bookmarks], "total": len(bookmarks), "query": query, }
- PinboardClient.search_bookmarks method: implements full-text search across title, notes, tags using cached bookmarks. Optimizes with exact tag match via direct API, expands cache if needed, caches results.async def search_bookmarks(self, query: str, limit: int = 20) -> list[Bookmark]: """Search bookmarks by query string.""" # Check query cache first cache_key = f"search:{query}:{limit}" if cache_key in self._query_cache: return self._query_cache[cache_key] # First try with recent bookmarks bookmarks = await self.get_all_bookmarks() query_lower = query.lower() # Search in title, notes, and tags matches = [] for bookmark in bookmarks: if ( query_lower in bookmark.title.lower() or query_lower in bookmark.notes.lower() or any(query_lower in tag.lower() for tag in bookmark.tags) ): matches.append(bookmark) if len(matches) >= limit: break # If no matches found, try optimized strategies before full expansion if not matches: # First, check if the query matches a tag exactly - use direct tag search tags = await self.get_all_tags() exact_tag_match = next( (tag.tag for tag in tags if tag.tag.lower() == query_lower), None ) if exact_tag_match: # Use efficient tag-based search try: await self._search_by_tag_direct( exact_tag_match, matches, None, None, limit ) except Exception: pass # Fall through to expanded search if tag search fails # If still no matches and we haven't expanded yet, try with more data # This allows comprehensive free-text search but limits scope for safety if not matches and not self._has_expanded_data: bookmarks = await self.get_all_bookmarks(expand_if_needed=True) for bookmark in bookmarks: if ( query_lower in bookmark.title.lower() or query_lower in bookmark.notes.lower() or any(query_lower in tag.lower() for tag in bookmark.tags) ): matches.append(bookmark) if len(matches) >= limit: break # Cache the result self._query_cache[cache_key] = matches return matches
- Pydantic BaseModel for Bookmark, defines structure of output bookmarks with fields id, url, title, tags, notes, saved_at. Includes from_pinboard factory.class Bookmark(BaseModel): """A Pinboard bookmark with mapped field names.""" id: str = Field( default_factory=lambda: str(uuid4()), description="Unique bookmark identifier" ) url: str = Field(description="The bookmark URL") title: str = Field( description="The bookmark title (mapped from Pinboard's 'description')" ) tags: list[str] = Field(default_factory=list, description="List of tags") notes: str = Field( default="", description="Free-form notes (mapped from Pinboard's 'extended')" ) saved_at: datetime = Field(description="When the bookmark was saved") @classmethod def from_pinboard(cls, pinboard_post: dict[str, Any]) -> "Bookmark": """Create a Bookmark from a Pinboard API post response.""" return cls( url=pinboard_post["href"], title=pinboard_post["description"], # Pinboard's description -> our title tags=pinboard_post["tags"].split() if pinboard_post["tags"] else [], notes=pinboard_post["extended"], # Pinboard's extended -> our notes saved_at=datetime.fromisoformat( pinboard_post["time"].replace("Z", "+00:00") ), )