Skip to main content
Glama

gmail_add_label_to_messages

Organize Gmail messages by adding labels to specific emails using message IDs or search queries. This tool helps categorize and manage your inbox efficiently.

Instructions

Add a label to one or more messages. Can specify messages by IDs or by search query.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
label_nameNoName of the label to add. Provide either label_name or label_id.
label_idNoID of the label to add. Provide either label_name or label_id.
message_idsNoComma-separated list of message IDs to add the label to.
queryNoGmail search query to find messages. Alternative to message_ids.
max_messagesNoMaximum messages to modify when using query. Default 100, max 500.
confirmYesMust be true to actually modify. Set false to preview.

Implementation Reference

  • MCP tool registration and input schema definition for 'gmail_add_label_to_messages'.
        name="gmail_add_label_to_messages",
        description="Add a label to one or more messages. Can specify messages by IDs or by search query.",
        inputSchema={
            "type": "object",
            "properties": {
                "label_name": {
                    "type": "string",
                    "description": "Name of the label to add. Provide either label_name or label_id."
                },
                "label_id": {
                    "type": "string",
                    "description": "ID of the label to add. Provide either label_name or label_id."
                },
                "message_ids": {
                    "type": "string",
                    "description": "Comma-separated list of message IDs to add the label to."
                },
                "query": {
                    "type": "string",
                    "description": "Gmail search query to find messages. Alternative to message_ids."
                },
                "max_messages": {
                    "type": "integer",
                    "description": "Maximum messages to modify when using query. Default 100, max 500."
                },
                "confirm": {
                    "type": "boolean",
                    "description": "Must be true to actually modify. Set false to preview."
                }
            },
            "required": ["confirm"]
        },
    ),
  • Primary handler function for the gmail_add_label_to_messages tool (handles args, preview/confirm, calls Gmail API). Dispatched from main handle_call_tool when name matches.
    async def _handle_label_modify(client: GmailClient, arguments: dict[str, Any], add: bool) -> list[TextContent]:
        """Handle add/remove label from messages.
        
        Args:
            client: The Gmail client instance.
            arguments: Tool arguments dictionary.
            add: True to add label, False to remove label.
            
        Returns:
            list[TextContent]: Response text.
        """
        label_id = arguments.get("label_id")
        label_name = arguments.get("label_name")
        message_ids_str = arguments.get("message_ids", "")
        query = arguments.get("query")
        max_messages = min(arguments.get("max_messages", 100), 500)
        confirm = arguments.get("confirm", False)
        
        action_verb = "add" if add else "remove"
        action_prep = "to" if add else "from"
        
        if not label_id and not label_name:
            return [TextContent(type="text", text="Error: Provide either label_id or label_name.")]
        if not message_ids_str and not query:
            return [TextContent(type="text", text="Error: Provide either message_ids or query.")]
        
        # Find label by name if ID not provided
        if not label_id:
            label = await client.find_label_by_name(label_name)
            if not label:
                return [TextContent(type="text", text=f"Error: Label not found: {label_name}")]
            label_id = label["id"]
            label_display = label["name"]
        else:
            label_display = label_name or label_id
        
        # Get message IDs from string or query
        if message_ids_str:
            message_ids = [mid.strip() for mid in message_ids_str.split(",") if mid.strip()]
        else:
            search_results = await client.search_emails(query, max_messages)
            if not search_results:
                return [TextContent(type="text", text=f"No emails found matching query: {query}")]
            message_ids = [email.id for email in search_results]
        
        if not confirm:
            return [TextContent(
                type="text",
                text=f"Preview: Would {action_verb} label '{label_display}' {action_prep} {len(message_ids)} message(s). Set confirm=true to proceed."
            )]
        
        if add:
            result = await client.modify_message_labels(message_ids, add_label_ids=[label_id])
        else:
            result = await client.modify_message_labels(message_ids, remove_label_ids=[label_id])
        
        if result["success"] > 0:
            return [TextContent(
                type="text",
                text=f"Success: {'Added' if add else 'Removed'} label '{label_display}' {action_prep} {result['success']} message(s)."
                + (f" Errors: {result['errors']}" if result['errors'] else "")
            )]
        else:
            return [TextContent(type="text", text=f"Error: Failed to modify labels. {result['errors']}")]
  • GmailClient helper method that performs the actual Gmail API batchModify call to add labels to messages.
    async def modify_message_labels(
        self,
        message_ids: list[str],
        add_label_ids: list[str] | None = None,
        remove_label_ids: list[str] | None = None
    ) -> dict:
        """Add or remove labels from messages.
        
        Args:
            message_ids: List of message IDs to modify
            add_label_ids: Label IDs to add to the messages
            remove_label_ids: Label IDs to remove from the messages
            
        Returns:
            Dict with success count and errors
        """
        if not message_ids:
            return {"success": 0, "errors": [], "message": "No message IDs provided"}
        
        if not add_label_ids and not remove_label_ids:
            return {"success": 0, "errors": [], "message": "No labels to add or remove"}
        
        results = {"success": 0, "errors": []}
        
        try:
            body = {"ids": message_ids}
            if add_label_ids:
                body["addLabelIds"] = add_label_ids
            if remove_label_ids:
                body["removeLabelIds"] = remove_label_ids
            
            # Use batchModify for efficiency
            if len(message_ids) <= 1000:
                self.service.users().messages().batchModify(
                    userId="me",
                    body=body
                ).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]
                    batch_body = body.copy()
                    batch_body["ids"] = batch
                    self.service.users().messages().batchModify(
                        userId="me",
                        body=batch_body
                    ).execute()
                    results["success"] += len(batch)
                    
        except HttpError as e:
            logger.error(f"Failed to modify message labels: {e}")
            results["errors"].append(str(e))
            
        return results
  • Helper method to resolve label name to label ID, used when only name is provided.
    async def find_label_by_name(self, name: str) -> dict | None:
        """Find a label by name (case-insensitive).
        
        Args:
            name: Label name to search for
            
        Returns:
            Label dict or None if not found
        """
        labels = await self.get_labels()
        name_lower = name.lower()
        for label in labels:
            if label.get("name", "").lower() == name_lower:
                return label
        return None
Behavior2/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 action ('Add a label') and selection methods, but lacks details on permissions needed, rate limits, error handling, or what happens if the label doesn't exist. For a mutation tool with zero annotation coverage, this is a significant gap.

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 a single, efficient sentence that front-loads the core purpose and key usage methods. Every word earns its place with no redundancy or unnecessary elaboration.

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?

Given the tool's moderate complexity (6 parameters, mutation operation) and lack of annotations or output schema, the description is minimally adequate. It covers the basic purpose and selection methods but misses behavioral details like side effects or return values, leaving gaps for an AI agent.

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 minimal value by mentioning 'IDs or by search query', which aligns with the schema's message_ids and query parameters. Baseline 3 is appropriate when the schema does the heavy lifting.

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

Purpose4/5

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

The description clearly states the action ('Add a label') and target ('to one or more messages'), distinguishing it from sibling tools like gmail_remove_label_from_messages. However, it doesn't explicitly differentiate from other labeling tools like gmail_create_label or gmail_rename_label, which keeps it from a perfect score.

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

Usage Guidelines3/5

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

The description implies usage by specifying two methods for selecting messages (IDs or search query), but it doesn't provide explicit guidance on when to use this tool versus alternatives like gmail_mark_as_read_by_ids or gmail_remove_label_from_messages. No exclusions or prerequisites are mentioned.

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