zotero_search_items
Search your Zotero library using query strings, modes (titleCreatorYear or everything), and optional tags. Retrieve item results for further metadata or full-text lookup.
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 |
|---|---|---|---|
| limit | No | ||
| qmode | No | titleCreatorYear | |
| query | Yes | ||
| tag | No |
Implementation Reference
- src/zotero_mcp/__init__.py:188-338 (handler)Complete handler implementation for the 'zotero_search_items' tool, including decorator registration, input parameters defining the schema, search logic using Zotero API, and result formatting.@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)Utility function to create and configure the Zotero client instance, which is called within the tool handler to perform API operations.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, )