Skip to main content
Glama

gmail_mark_as_unread_by_query

Mark Gmail emails matching a search query as unread. Use Gmail query syntax to filter emails, set maximum count, and preview before confirming changes.

Instructions

Mark emails matching a search query as unread. Use Gmail query syntax. Requires confirmation.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
queryYesGmail search query to find emails to mark as unread. Examples: 'from:newsletter@example.com', 'subject:important'.
max_emailsNoMaximum number of emails to mark as unread. Default 100, max 500.
confirmYesMust be true to actually mark as unread. Set false to preview what would be marked.

Implementation Reference

  • MCP tool handler for gmail_mark_as_unread_by_query. Handles input arguments, confirmation preview (searches read emails with query and shows list), and executes GmailClient.mark_as_unread_by_query on confirm.
    elif name == "gmail_mark_as_unread_by_query":
        query = arguments.get("query", "")
        max_emails = min(arguments.get("max_emails", 100), 500)
        confirm = arguments.get("confirm", False)
        
        if not query:
            return [TextContent(type="text", text="Error: query is required.")]
        
        if not confirm:
            # Preview mode - show what would be marked
            search_results = await client.search_emails(f"{query} -is:unread", max_emails)
            if not search_results:
                return [TextContent(type="text", text=f"No read emails match the query: {query}")]
            
            lines = [f"Preview: {len(search_results)} email(s) would be marked as unread:\n"]
            for email in search_results[:20]:
                lines.append(f"- {email.subject}")
                lines.append(f"  From: {email.sender.email}")
                lines.append(f"  Date: {email.date.strftime('%Y-%m-%d %H:%M')}")
                lines.append("")
            
            if len(search_results) > 20:
                lines.append(f"... and {len(search_results) - 20} more\n")
            
            lines.append("Set confirm=true to proceed.")
            return [TextContent(type="text", text="\n".join(lines))]
        else:
            result = await client.mark_as_unread_by_query(query, max_emails)
            return [TextContent(
                type="text",
                text=f"Success: {result['message']}\nMatched: {result['matched']}, Marked as unread: {result['success']}"
                + (f", Errors: {result['errors']}" if result['errors'] else "")
            )]
  • JSON Schema definition for the tool input parameters: query (required), max_emails (optional), confirm (required). Included in GMAIL_TOOLS list.
    Tool(
        name="gmail_mark_as_unread_by_query",
        description="Mark emails matching a search query as unread. Use Gmail query syntax. Requires confirmation.",
        inputSchema={
            "type": "object",
            "properties": {
                "query": {
                    "type": "string",
                    "description": "Gmail search query to find emails to mark as unread. Examples: 'from:newsletter@example.com', 'subject:important'."
                },
                "max_emails": {
                    "type": "integer",
                    "description": "Maximum number of emails to mark as unread. Default 100, max 500."
                },
                "confirm": {
                    "type": "boolean",
                    "description": "Must be true to actually mark as unread. Set false to preview what would be marked."
                }
            },
            "required": ["query", "confirm"]
        },
    ),
  • GmailClient.mark_as_unread_by_query: Searches Gmail API for read emails (query -is:unread), gets message IDs, calls mark_as_unread to batch-add UNREAD label.
    async def mark_as_unread_by_query(self, query: str, max_emails: int = 100) -> dict:
        """Mark emails matching a query as unread.
        
        Args:
            query: Gmail search query (e.g., "from:newsletter@example.com", "older_than:7d")
            max_emails: Maximum number of emails to mark as unread (safety limit)
            
        Returns:
            Dict with success count, matched count, and any errors
        """
        try:
            # First, find matching emails (that are currently read)
            results = (
                self.service.users()
                .messages()
                .list(userId="me", q=f"{query} -is:unread", maxResults=max_emails)
                .execute()
            )
            
            messages = results.get("messages", [])
            if not messages:
                return {
                    "matched": 0,
                    "success": 0,
                    "errors": [],
                    "message": "No read emails matched the query"
                }
            
            message_ids = [msg["id"] for msg in messages]
            
            # Mark them as unread
            mark_result = await self.mark_as_unread(message_ids)
            mark_result["matched"] = len(messages)
            
            # Check if there might be more
            if len(messages) == max_emails:
                mark_result["message"] = f"Marked {mark_result['success']} emails as unread. There may be more matching emails (limit was {max_emails})."
            else:
                mark_result["message"] = f"Marked {mark_result['success']} emails as unread."
                
            return mark_result
            
        except HttpError as e:
            logger.error(f"Failed to search and mark emails as unread: {e}")
            return {"matched": 0, "success": 0, "errors": [str(e)], "message": f"Error: {e}"}
  • Low-level GmailClient.mark_as_unread: Uses Gmail API messages.batchModify to add 'UNREAD' label to given message IDs (batched for large lists).
    async def mark_as_unread(self, message_ids: list[str]) -> dict:
        """Mark one or more emails as unread by adding the UNREAD label.
        
        Args:
            message_ids: List of Gmail message IDs to mark as unread
            
        Returns:
            Dict with success count and any errors
        """
        if not message_ids:
            return {"success": 0, "errors": [], "message": "No message IDs provided"}
        
        results = {"success": 0, "errors": []}
        
        try:
            # Use batchModify for efficiency (up to 1000 at a time)
            if len(message_ids) <= 1000:
                self.service.users().messages().batchModify(
                    userId="me",
                    body={
                        "ids": message_ids,
                        "addLabelIds": ["UNREAD"]
                    }
                ).execute()
                results["success"] = len(message_ids)
            else:
                # Process in batches of 1000
                for i in range(0, len(message_ids), 1000):
                    batch = message_ids[i:i+1000]
                    self.service.users().messages().batchModify(
                        userId="me",
                        body={
                            "ids": batch,
                            "addLabelIds": ["UNREAD"]
                        }
                    ).execute()
                    results["success"] += len(batch)
                    
        except HttpError as e:
            logger.error(f"Failed to mark emails as unread: {e}")
            results["errors"].append(str(e))
            
        return results
  • MCP Server registration: Decorator registers list_tools handler that returns GMAIL_TOOLS containing the gmail_mark_as_unread_by_query tool schema.
    @server.list_tools()
    async def list_tools() -> list[Tool]:
        return GMAIL_TOOLS
Behavior3/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 the confirmation requirement, which is useful context, but lacks details on permissions, rate limits, or what happens to emails (e.g., if it affects all matching emails up to max_emails). It doesn't contradict annotations, but could be more informative.

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

Conciseness5/5

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

The description is two sentences, front-loaded with the core purpose and followed by essential usage guidance. Every word earns its place, with no redundancy or fluff, making it highly efficient and easy to parse.

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?

For a mutation tool with no annotations and no output schema, the description is adequate but has gaps. It covers the purpose and confirmation need, but lacks details on behavioral aspects like error handling or return values. Given the complexity and lack of structured data, it's minimally viable but not fully comprehensive.

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

Parameters3/5

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

Schema description coverage is 100%, so the schema already documents all parameters thoroughly. The description adds no additional parameter semantics beyond what's in the schema, such as explaining Gmail query syntax further or elaborating on the confirmation logic. Baseline 3 is appropriate given high schema coverage.

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

Purpose5/5

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

The description clearly states the specific action ('mark emails matching a search query as unread'), identifies the resource (emails), and distinguishes it from siblings like 'gmail_mark_as_unread_by_ids' by specifying the query-based approach. It's precise and avoids tautology.

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

Usage Guidelines4/5

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

The description provides clear context for when to use this tool (to mark emails as unread based on a search query) and mentions a prerequisite ('Requires confirmation'), but it doesn't explicitly state when not to use it or name alternatives like 'gmail_mark_as_unread_by_ids' for ID-based operations.

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

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/murphy360/mcp_gmail'

If you have feedback or need assistance with the MCP directory API, please join our Discord server