Skip to main content
Glama

search_ad_library

Search ads by keywords and countries across the Meta Ad Library to support competitive research and ad transparency.

Instructions

Search the Meta Ad Library for ads. Allows searching by keywords, countries, and ad type. Useful for competitive research and transparency.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
search_termsYesKeywords to search for in ads
ad_reached_countriesYesComma-separated country codes where ads were shown (e.g. 'US,GB,KR')
ad_typeNoAd type filter: ALL, POLITICAL_AND_ISSUE_ADS, HOUSING_ADS, etc.
fieldsNoComma-separated fields to return
limitNoNumber of results (default 25)
afterNoPagination cursor for next page
order_byNoSort order for results

Implementation Reference

  • The async handler function that executes the search_ad_library tool logic. It builds query params from user input, calls client.get('/ads_archive', params), and returns the JSON response with rate limit info.
      async ({ search_terms, ad_reached_countries, ad_type, fields, limit, after, order_by }) => {
        try {
          const params: Record<string, unknown> = {
            search_terms,
            ad_reached_countries,
          };
          if (ad_type) params.ad_type = ad_type;
          if (fields) params.fields = fields;
          if (limit) params.limit = limit;
          if (after) params.after = after;
          if (order_by) params.order_by = order_by;
          const { data, rateLimit } = await client.get(`/ads_archive`, params);
          return { content: [{ type: "text" as const, text: JSON.stringify({ ...data as object, _rateLimit: rateLimit }, null, 2) }] };
        } catch (error) {
          return { content: [{ type: "text" as const, text: `Failed: ${error instanceof Error ? error.message : String(error)}` }], isError: true };
        }
      }
    );
  • Zod schema definitions for the search_ad_library tool's input parameters: search_terms, ad_reached_countries, ad_type, fields, limit, after, order_by.
    {
      search_terms: z.string().describe("Keywords to search for in ads"),
      ad_reached_countries: z.string().describe("Comma-separated country codes where ads were shown (e.g. 'US,GB,KR')"),
      ad_type: z.string().optional().describe("Ad type filter: ALL, POLITICAL_AND_ISSUE_ADS, HOUSING_ADS, etc."),
      fields: z.string().optional().describe("Comma-separated fields to return"),
      limit: z.number().optional().default(25).describe("Number of results (default 25)"),
      after: z.string().optional().describe("Pagination cursor for next page"),
      order_by: z.string().optional().describe("Sort order for results"),
    },
  • The registerAdLibraryTools function that registers 'search_ad_library' as an MCP tool on the server using server.tool().
    export function registerAdLibraryTools(server: McpServer, client: AdsClient): void {
      // ─── search_ad_library ────────────────────────────────────────
      server.tool(
        "search_ad_library",
        "Search the Meta Ad Library for ads. Allows searching by keywords, countries, and ad type. Useful for competitive research and transparency.",
        {
          search_terms: z.string().describe("Keywords to search for in ads"),
          ad_reached_countries: z.string().describe("Comma-separated country codes where ads were shown (e.g. 'US,GB,KR')"),
          ad_type: z.string().optional().describe("Ad type filter: ALL, POLITICAL_AND_ISSUE_ADS, HOUSING_ADS, etc."),
          fields: z.string().optional().describe("Comma-separated fields to return"),
          limit: z.number().optional().default(25).describe("Number of results (default 25)"),
          after: z.string().optional().describe("Pagination cursor for next page"),
          order_by: z.string().optional().describe("Sort order for results"),
        },
        async ({ search_terms, ad_reached_countries, ad_type, fields, limit, after, order_by }) => {
          try {
            const params: Record<string, unknown> = {
              search_terms,
              ad_reached_countries,
            };
            if (ad_type) params.ad_type = ad_type;
            if (fields) params.fields = fields;
            if (limit) params.limit = limit;
            if (after) params.after = after;
            if (order_by) params.order_by = order_by;
            const { data, rateLimit } = await client.get(`/ads_archive`, params);
            return { content: [{ type: "text" as const, text: JSON.stringify({ ...data as object, _rateLimit: rateLimit }, null, 2) }] };
          } catch (error) {
            return { content: [{ type: "text" as const, text: `Failed: ${error instanceof Error ? error.message : String(error)}` }], isError: true };
          }
        }
      );
    }
  • src/index.ts:96-96 (registration)
    Registration call in the main index.ts that wires up the tool registration function with the server and client.
    registerAdLibraryTools(server, client);
  • The AdsClient class providing the get() method (line 180-184) used by the handler to call Meta's Graph API (baseUrl https://graph.facebook.com/v25.0) with rate limit parsing and error formatting.
      private async request(
        method: string,
        path: string,
        params?: Record<string, unknown>
      ): Promise<ClientResponse> {
        if (!this.config.accessToken) {
          throw new Error(
            "META_ADS_ACCESS_TOKEN is not configured. Set it as an environment variable."
          );
        }
    
        let url = `${this.baseUrl}${path}`;
        const init: RequestInit = { method, signal: AbortSignal.timeout(30_000) };
    
        if (method === "GET" || method === "DELETE") {
          const qs = new URLSearchParams();
          qs.set("access_token", this.config.accessToken);
          if (params) {
            for (const [k, v] of Object.entries(params)) {
              if (v !== undefined && v !== null && v !== "") {
                qs.set(k, String(v));
              }
            }
          }
          url += `?${qs.toString()}`;
        } else {
          const body: Record<string, unknown> = {
            access_token: this.config.accessToken,
            ...params,
          };
          init.headers = { "Content-Type": "application/json" };
          init.body = JSON.stringify(body);
        }
    
        const res = await fetch(url, init);
    
        if (!res.ok) {
          const text = await res.text().catch(() => "");
          let errorMsg: string;
          try {
            const errorBody = JSON.parse(text);
            errorMsg = this.formatError(errorBody);
          } catch {
            errorMsg = `Meta Ads API ${method} ${path} (${res.status}): ${text}`;
          }
          throw new Error(errorMsg);
        }
    
        const { rateLimit, businessRateLimit } = this.parseRateLimit(res.headers);
        const contentType = res.headers.get("content-type") || "";
    
        if (contentType.includes("application/json")) {
          const data = await res.json();
          if (data.error) {
            throw new Error(this.formatError(data));
          }
          return { data, rateLimit, businessRateLimit };
        }
    
        const text = await res.text();
        return { data: text || { success: true }, rateLimit, businessRateLimit };
      }
    
      // --- Convenience methods ---
    
      async get(
        path: string,
        params?: Record<string, unknown>
      ): Promise<ClientResponse> {
        return this.request("GET", path, params);
      }
    
      async post(
        path: string,
        params?: Record<string, unknown>
      ): Promise<ClientResponse> {
        return this.request("POST", path, params);
      }
    
      async delete(
        path: string,
        params?: Record<string, unknown>
      ): Promise<ClientResponse> {
        return this.request("DELETE", path, params);
      }
    
      // --- Upload (URL-based) ---
    
      async upload(
        path: string,
        fileUrl: string,
        params?: Record<string, unknown>
      ): Promise<ClientResponse> {
        return this.post(path, { ...params, url: fileUrl });
      }
    
      // --- Account helpers ---
    
      get accountPath(): string {
        return `/act_${this.accountId}`;
      }
    
      get accountId(): string {
        if (!this.config.adAccountId) {
          throw new Error(
            "META_AD_ACCOUNT_ID is not configured. Set it as an environment variable."
          );
        }
        return this.config.adAccountId;
      }
    
      get pixelId(): string {
        if (!this.config.pixelId) {
          throw new Error(
            "META_PIXEL_ID is not configured. Set it as an environment variable."
          );
        }
        return this.config.pixelId;
      }
    
      get businessId(): string {
        if (!this.config.businessId) {
          throw new Error(
            "META_BUSINESS_ID is not configured. Set it as an environment variable."
          );
        }
        return this.config.businessId;
      }
    
      // --- Token management ---
    
      /** Exchange short-lived token for long-lived token */
      async exchangeToken(shortToken: string): Promise<ClientResponse> {
        if (!this.config.appId || !this.config.appSecret) {
          throw new Error(
            "META_APP_ID and META_APP_SECRET are required for token exchange."
          );
        }
        const qs = new URLSearchParams({
          grant_type: "fb_exchange_token",
          client_id: this.config.appId,
          client_secret: this.config.appSecret,
          fb_exchange_token: shortToken,
        });
        const url = `${this.baseUrl}/oauth/access_token?${qs.toString()}`;
        const res = await fetch(url, { signal: AbortSignal.timeout(30_000) });
    
        if (!res.ok) {
          const text = await res.text().catch(() => "");
          throw new Error(`Token exchange failed (${res.status}): ${text}`);
        }
    
        const data = await res.json();
        if (data.error) {
          throw new Error(this.formatError(data));
        }
        return { data };
      }
    
      /** Refresh a long-lived token */
      async refreshToken(longToken: string): Promise<ClientResponse> {
        const qs = new URLSearchParams({
          grant_type: "fb_exchange_token",
          access_token: longToken,
        });
        const url = `${this.baseUrl}/oauth/access_token?${qs.toString()}`;
        const res = await fetch(url, { signal: AbortSignal.timeout(30_000) });
    
        if (!res.ok) {
          const text = await res.text().catch(() => "");
          throw new Error(`Token refresh failed (${res.status}): ${text}`);
        }
    
        const data = await res.json();
        if (data.error) {
          throw new Error(this.formatError(data));
        }
        return { data };
      }
    
      /** Debug a token to inspect its properties */
      async debugToken(inputToken: string): Promise<ClientResponse> {
        if (!this.config.appId || !this.config.appSecret) {
          throw new Error(
            "META_APP_ID and META_APP_SECRET are required for token debug."
          );
        }
        const appToken = `${this.config.appId}|${this.config.appSecret}`;
        const qs = new URLSearchParams({
          input_token: inputToken,
          access_token: appToken,
        });
        const url = `${this.baseUrl}/debug_token?${qs.toString()}`;
        const res = await fetch(url, { signal: AbortSignal.timeout(30_000) });
    
        if (!res.ok) {
          const text = await res.text().catch(() => "");
          throw new Error(`Token debug failed (${res.status}): ${text}`);
        }
    
        const data = await res.json();
        if (data.error) {
          throw new Error(this.formatError(data));
        }
        return { data };
      }
    }

Schema Changelog

Changes observed during successful MCP inspections.

  1. First observedv1.2.0

TDQS

B3.4/5.0
Behavior2/5

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

With no annotations provided, the description must carry the full burden of behavioral disclosure. It only says 'Search' and 'Useful for competitive research and transparency,' which implies read-only, but it does not explicitly affirm read-only behavior, mention authentication/authorization requirements, rate limits, pagination, or what data is returned. This leaves significant behavioral ambiguity.

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?

Three short sentences, front-loaded with the core purpose, followed by supporting detail and use case. No redundancy or filler—every sentence earns its place.

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

Completeness2/5

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

Given no output schema, the description should explain what the response contains (e.g., ad fields, creative, impressions), but it is silent on return value structure. It also lacks mention of pagination behavior, result count defaults, or error scenarios. The tool has 7 parameters and no output schema, so this is a meaningful gap.

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?

The input schema has 100% description coverage, with all seven parameters individually described. The description adds no new semantics beyond restating that searching is possible by keywords, countries, and ad type, which maps to search_terms, ad_reached_countries, and ad_type but adds no syntax, format, or usage nuance. Baseline 3 is appropriate.

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 starts with a specific verb+resource: 'Search the Meta Ad Library for ads.' It clearly states the function and distinguishes it from sibling tools like list_ads or get_campaign_ads, which deal with the user's own ad objects. The added detail (keywords, countries, ad type) further clarifies scope.

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?

'Useful for competitive research and transparency' provides some context, implying a public-search use case, but it does not explicitly state when to prefer this tool over alternatives or mention exclusions (e.g., not for retrieving your own campaign ads). There is no direct comparison with sibling tools.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Deploy Server

Other Tools