zotero_search_items
Search your Zotero library by query, mode, and tags to find specific items, then retrieve metadata or full text for results.
Instructions
Search for items in your Zotero library, given a query string, query mode (titleCreatorYear or everything), and optional tag search (supports boolean searches). Returned results can be looked up with zotero_item_fulltext or zotero_item_metadata.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | ||
| qmode | No | titleCreatorYear | |
| tag | No | ||
| limit | No |
Implementation Reference
- src/zotero_mcp/__init__.py:188-338 (handler)The @mcp.tool decorator registers the tool and the search_items function implements the core logic: authenticates Zotero client, performs search with parameters, fetches items, and formats results with previews including titles, authors, dates, abstracts, and tags for easy reference.@mcp.tool( name="zotero_search_items", # More detail can be added if useful: https://www.zotero.org/support/dev/web_api/v3/basics#searching description="Search for items in your Zotero library, given a query string, query mode (titleCreatorYear or everything), and optional tag search (supports boolean searches). Returned results can be looked up with zotero_item_fulltext or zotero_item_metadata.", ) def search_items( query: str, qmode: Literal["titleCreatorYear", "everything"] | None = "titleCreatorYear", tag: str | None = None, limit: int | None = 10, ) -> str: """Search for items in your Zotero library""" zot = get_zotero_client() # Search using the q parameter params = {"q": query, "qmode": qmode, "limit": limit} if tag: params["tag"] = tag zot.add_parameters(**params) # n.b. types for this return do not work, it's a parsed JSON object results: Any = zot.items() if not results: return "No items found matching your query." # Header with search info header = [ f"# Search Results for: '{query}'", f"Found {len(results)} items." + (f" Using tag filter: {tag}" if tag else ""), "Use item keys with zotero_item_metadata or zotero_item_fulltext for more details.\n", ] # Format results formatted_results = [] for i, item in enumerate(results): data = item["data"] item_key = item.get("key", "") item_type = data.get("itemType", "unknown") # Special handling for notes if item_type == "note": # Get note content note_content = data.get("note", "") # Strip HTML tags for cleaner text (simple approach) note_content = ( note_content.replace("<p>", "") .replace("</p>", "\n") .replace("<br>", "\n") ) note_content = note_content.replace("<strong>", "**").replace( "</strong>", "**" ) note_content = note_content.replace("<em>", "*").replace("</em>", "*") # Extract a title from the first line if possible, otherwise use first few words title_preview = "" if note_content: lines = note_content.strip().split("\n") first_line = lines[0].strip() if first_line: # Use first line if it's reasonably short, otherwise use first few words if len(first_line) <= 50: title_preview = first_line else: words = first_line.split() title_preview = " ".join(words[:5]) + "..." # Create a good title for the note note_title = title_preview if title_preview else "Note" # Get a preview of the note content (truncated) preview = note_content.strip() if len(preview) > 150: preview = preview[:147] + "..." # Format the note entry entry = [ f"## {i + 1}. 📝 {note_title}", f"**Type**: Note | **Key**: `{item_key}`", f"\n{preview}", ] # Add parent item reference if available if parent_item := data.get("parentItem"): entry.insert(2, f"**Parent Item**: `{parent_item}`") # Add tags if present (limited to first 5) if tags := data.get("tags"): tag_list = [f"`{tag['tag']}`" for tag in tags[:5]] if len(tags) > 5: tag_list.append("...") entry.append(f"\n**Tags**: {' '.join(tag_list)}") formatted_results.append("\n".join(entry)) continue # Regular item processing (non-notes) title = data.get("title", "Untitled") date = data.get("date", "") # Format primary creators (limited to first 3) creators = [] for creator in data.get("creators", [])[:3]: if "firstName" in creator and "lastName" in creator: creators.append(f"{creator['lastName']}, {creator['firstName']}") elif "name" in creator: creators.append(creator["name"]) if len(data.get("creators", [])) > 3: creators.append("et al.") creator_str = "; ".join(creators) if creators else "No authors" # Get publication or source info source = "" if pub := data.get("publicationTitle"): source = pub elif book := data.get("bookTitle"): source = f"In: {book}" elif publisher := data.get("publisher"): source = f"{publisher}" # Get a brief abstract (truncated if too long) abstract = data.get("abstractNote", "") if len(abstract) > 150: abstract = abstract[:147] + "..." # Build formatted entry with markdown for better structure entry = [ f"## {i + 1}. {title}", f"**Type**: {item_type} | **Date**: {date} | **Key**: `{item_key}`", f"**Authors**: {creator_str}", ] if source: entry.append(f"**Source**: {source}") if abstract: entry.append(f"\n{abstract}") # Add tags if present (limited to first 5) if tags := data.get("tags"): tag_list = [f"`{tag['tag']}`" for tag in tags[:5]] if len(tags) > 5: tag_list.append("...") entry.append(f"\n**Tags**: {' '.join(tag_list)}") formatted_results.append("\n".join(entry)) return "\n\n".join(header + formatted_results)
- src/zotero_mcp/client.py:14-34 (helper)Helper function to initialize and return the authenticated Zotero client instance, used by zotero_search_items to perform API calls.def get_zotero_client() -> zotero.Zotero: """Get authenticated Zotero client using environment variables""" library_id = os.getenv("ZOTERO_LIBRARY_ID") library_type = os.getenv("ZOTERO_LIBRARY_TYPE", "user") api_key = os.getenv("ZOTERO_API_KEY") or None local = os.getenv("ZOTERO_LOCAL", "").lower() in ["true", "yes", "1"] if local: if not library_id: # Indicates "current user" for the local API library_id = "0" elif not all([library_id, api_key]): raise ValueError( "Missing required environment variables. Please set ZOTERO_LIBRARY_ID and ZOTERO_API_KEY" ) return zotero.Zotero( library_id=library_id, library_type=library_type, api_key=api_key, local=local, )
- src/zotero_mcp/__init__.py:188-192 (registration)The @mcp.tool decorator registers 'zotero_search_items' with the MCP server, including name and description.@mcp.tool( name="zotero_search_items", # More detail can be added if useful: https://www.zotero.org/support/dev/web_api/v3/basics#searching description="Search for items in your Zotero library, given a query string, query mode (titleCreatorYear or everything), and optional tag search (supports boolean searches). Returned results can be looked up with zotero_item_fulltext or zotero_item_metadata.", )
- src/zotero_mcp/__init__.py:193-198 (schema)Input schema defined by function type hints: query (str, required), qmode (Literal or None, default 'titleCreatorYear'), tag (str or None), limit (int or None, default 10), returns str.def search_items( query: str, qmode: Literal["titleCreatorYear", "everything"] | None = "titleCreatorYear", tag: str | None = None, limit: int | None = 10, ) -> str: