Skip to main content
Glama
johnoconnor0

Google Ads MCP Server

by johnoconnor0

google_ads_add_shared_negative_keywords

Add negative keywords to an account-level shared list to exclude unwanted search terms across multiple campaigns.

Instructions

Add negative keywords to a shared negative keyword list (account-level).

This adds keywords to an existing shared negative keyword list that can be applied across multiple campaigns for account-level negative keyword management.

Args: customer_id: Customer ID (without hyphens) shared_set_id: The ID of the shared negative keyword list keywords: List of keyword dicts with 'text' and 'match_type'

Returns: Success message with count of keywords added

Example: keywords = [ {"text": "cheap", "match_type": "BROAD"}, {"text": "free", "match_type": "BROAD"}, {"text": "jobs", "match_type": "BROAD"} ]

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
customer_idYes
shared_set_idYes
keywordsYes

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
resultYes

Implementation Reference

  • The MCP tool handler for 'google_ads_add_shared_negative_keywords'. This is the function decorated with @mcp.tool() that defines the tool, accepts customer_id, shared_set_id, and keywords params, validates input, calls the KeywordManager's add_keywords_to_shared_set method, logs the operation via audit_logger, invalidates cache, and returns a formatted success message.
    @mcp.tool()
    def google_ads_add_shared_negative_keywords(
        customer_id: str,
        shared_set_id: str,
        keywords: List[Dict[str, str]]
    ) -> str:
        """
        Add negative keywords to a shared negative keyword list (account-level).
    
        This adds keywords to an existing shared negative keyword list that can be
        applied across multiple campaigns for account-level negative keyword management.
    
        Args:
            customer_id: Customer ID (without hyphens)
            shared_set_id: The ID of the shared negative keyword list
            keywords: List of keyword dicts with 'text' and 'match_type'
    
        Returns:
            Success message with count of keywords added
    
        Example:
            keywords = [
                {"text": "cheap", "match_type": "BROAD"},
                {"text": "free", "match_type": "BROAD"},
                {"text": "jobs", "match_type": "BROAD"}
            ]
        """
        with performance_logger.track_operation('add_shared_negative_keywords', customer_id=customer_id):
            try:
                client = get_auth_manager().get_client()
                keyword_manager = KeywordManager(client)
    
                result = keyword_manager.add_keywords_to_shared_set(
                    customer_id,
                    shared_set_id,
                    keywords
                )
    
                # Audit log
                audit_logger.log_api_call(
                    customer_id=customer_id,
                    operation="add_shared_negative_keywords",
                    resource_type="shared_criterion",
                    action="create",
                    result="success",
                    details={
                        'shared_set_id': shared_set_id,
                        'keyword_count': len(keywords)
                    }
                )
    
                # Invalidate cache
                get_cache_manager().invalidate(customer_id, ResourceType.KEYWORD)
    
                output = f"✅ Keywords added to shared negative keyword list!\n\n"
                output += f"**Shared Set ID**: {shared_set_id}\n"
                output += f"**Keywords Added**: {result['keywords_added']}\n\n"
    
                output += "**Added Keywords**:\n"
                for kw in keywords[:20]:
                    output += f"- {kw['text']} ({kw['match_type']})\n"
    
                if len(keywords) > 20:
                    output += f"... and {len(keywords) - 20} more\n"
    
                output += "\nThese negative keywords will apply to all campaigns linked to this shared list."
    
                return output
    
            except Exception as e:
                error_msg = ErrorHandler.handle_error(e, context="add_shared_negative_keywords")
                return f"❌ Failed to add shared negative keywords: {error_msg}"
  • The 'add_keywords_to_shared_set' method in KeywordManager. This is the core business logic: it builds SharedCriterionOperations using the Google Ads API, setting the shared_set resource name and keyword text/match_type for each keyword, then calls shared_criterion_service.mutate_shared_criteria() to add negative keywords to the shared list.
    def add_keywords_to_shared_set(
        self,
        customer_id: str,
        shared_set_id: str,
        keywords: List[Dict[str, str]]
    ) -> Dict[str, Any]:
        """
        Add negative keywords to a shared negative keyword list.
    
        Args:
            customer_id: Customer ID
            shared_set_id: Shared set ID (the negative keyword list ID)
            keywords: List of dicts with 'text' and 'match_type'
    
        Returns:
            Operation result with count of keywords added
        """
        shared_criterion_service = self.client.get_service("SharedCriterionService")
    
        operations = []
    
        for kw in keywords:
            operation = self.client.get_type("SharedCriterionOperation")
            criterion = operation.create
    
            # Set the shared set resource name
            criterion.shared_set = f"customers/{customer_id}/sharedSets/{shared_set_id}"
    
            # Set keyword
            criterion.keyword.text = kw['text']
            criterion.keyword.match_type = self.client.enums.KeywordMatchTypeEnum[
                kw['match_type'].upper()
            ]
    
            operations.append(operation)
    
        # Add keywords to shared set
        response = shared_criterion_service.mutate_shared_criteria(
            customer_id=customer_id,
            operations=operations
        )
    
        added_count = len(response.results)
        logger.info(f"Added {added_count} keywords to shared set {shared_set_id}")
    
        return {
            "keywords_added": added_count,
            "shared_set_id": shared_set_id,
            "message": f"Successfully added {added_count} negative keywords to shared list"
        }
  • The 'register_keyword_tools' function that registers all keyword tools (including this one) with the MCP server via the @mcp.tool() decorator pattern.
    def register_keyword_tools(mcp: FastMCP):
        """Register keyword management tools with MCP server."""
    
        # ============================================================================
  • The '_register_all_modular_tools' function that dynamically imports and calls register_keyword_tools (among others) via the _TOOL_MODULES list, which includes the keywords module at line 483.
    def _register_all_modular_tools():
        """Import and register every modular tool module."""
        import importlib
    
        registered = 0
        for label, module_path, func_name in _TOOL_MODULES:
            try:
                mod = importlib.import_module(module_path)
                register_fn = getattr(mod, func_name)
                register_fn(mcp)
                logger.info(f"  ✓ {label}")
                registered += 1
            except Exception as exc:
                logger.error(f"  ✗ {label}: {exc}")
    
        logger.info(f"Registered {registered}/{len(_TOOL_MODULES)} modular tool modules")
  • Package init that exports register_keyword_tools from the keyword tools module, allowing the main server to discover and register this tool.
    """
    Google Ads MCP Tools - Keyword Management
    
    Tools for keyword research, management, and optimization.
    
    Includes tools for:
    - Adding and removing keywords
    - Managing negative keywords
    - Updating keyword bids and match types
    - Keyword quality score analysis
    - Search term mining and analysis
    - Keyword idea generation
    
    Total: 14 tools
    """
    
    from .mcp_tools_keywords import register_keyword_tools
    
    __all__ = ['register_keyword_tools']
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description is a mutation operation (adds keywords) with no annotations. It mentions the return format (success message with count) but lacks detail on error cases like non-existent shared list or duplicate keywords. The behavior is adequately described but not exhaustive.

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

Conciseness4/5

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

The description is well-structured with a summary, args, returns, and example. However, the first two sentences are slightly redundant ('Add negative keywords' and 'This adds keywords'). Still clear and front-loaded. Minor verbosity.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's simplicity and presence of an output schema, the description covers purpose, parameters, example, and return. It does not cover error conditions or prerequisites (e.g., shared list must exist), but overall it is sufficient for an agent to use correctly.

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

Parameters5/5

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

The schema coverage is 0% and the schema for 'keywords' is vague (array of objects with string additional properties). The description adds crucial meaning by specifying each dict must have 'text' and 'match_type' and provides an example. This fully compensates for the schema gaps.

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 'Add negative keywords to a shared negative keyword list (account-level)' and explains that the list can be applied across multiple campaigns. This distinguishes it from sibling tools like google_ads_add_negative_keywords which likely operate at campaign or ad group level.

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 implies use for account-level negative keyword management across campaigns. It does not explicitly state when not to use or name alternatives, but the phrase 'account-level shared' differentiates it from campaign-specific tools. Some guidance is provided.

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/johnoconnor0/google-ads-mcp'

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