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 };
      }
    }
Behavior2/5

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

With no annotations provided, the description must disclose behavioral traits. It fails to mention pagination (despite 'after' parameter), rate limits, or read-only nature. The description is too brief to substitute for missing annotations.

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?

Two sentences with no fluff. It is front-loaded with the core purpose. Could be slightly more informative without sacrificing brevity, but it is efficient.

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 7 parameters and no output schema or annotations, the description is incomplete. It lacks information on return format, pagination behavior, or any constraints beyond what the schema provides. Leaves significant gaps for the 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 coverage is 100% so description adds limited value. It reinforces that search_terms, ad_reached_countries, and ad_type are key filters, but doesn't explain format or usage details beyond schema. 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 clearly states 'Search the Meta Ad Library for ads' with a specific verb and resource, and differentiates from siblings like search_targeting by targeting a different object (ads vs targeting options). Mention of searchable criteria further clarifies purpose.

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

Usage Guidelines2/5

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

No explicit guidance on when to use this tool versus alternatives like search_targeting or list_ads. The phrase 'Useful for competitive research and transparency' is vague and does not help an agent decide when to invoke.

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/mikusnuz/meta-ads-mcp'

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