Skip to main content
Glama
martechery

Google Ads MCP Server

by martechery

list_resources

List Google Ads API resources or accounts for querying and management. Filter by name, set limits, and export in table, JSON, or CSV format.

Instructions

List GAQL FROM-able resources via google_ads_field (category=RESOURCE, selectable=true) or list accounts. output_format=table|json|csv.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
kindNowhat to list: resources | accountsresources
filterNosubstring filter on resource name
limitNomax rows (1-1000)
output_formatNorender formattable

Implementation Reference

  • Core handler function executing the list_resources tool logic: supports listing accounts or GAQL resources (google_ads_field with category=RESOURCE, selectable=true), input parsing, session/rate limiting, error handling, output formatting (table/json/csv), and observability logging.
    const startTs = Date.now();
    let sessionKey: string | undefined;
    try {
      sessionKey = requireSessionKeyIfEnabled(input);
    } catch (e: any) {
      const msg = e?.message || String(e);
      logEvent('list_resources', startTs, { sessionKey, requestId: input?.request_id, error: { code: 'ERR_INPUT', message: String(msg) } });
      return { content: [{ type: 'text', text: `Error: ${msg}` }] };
    }
    if (sessionKey) {
      const rc = checkRateLimit(sessionKey);
      if (!rc.allowed) {
        logEvent('list_resources', startTs, { sessionKey, requestId: input?.request_id, error: { code: 'ERR_RATE_LIMITED', message: `Retry after ${rc.retryAfter}s` } });
        return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'ERR_RATE_LIMITED', message: `Rate limit exceeded. Retry after ${rc.retryAfter} seconds`, retry_after: rc.retryAfter } }) }] };
      }
    }
    const kind = String(input?.kind || 'resources').toLowerCase();
    if (kind === 'accounts') {
      const res = await listAccessibleCustomers(sessionKey);
      if (!res.ok) {
        const hint = mapAdsErrorMsg(res.status, res.errorText || '');
        const lines = [`Error listing accounts (status ${res.status}): ${res.errorText || ''}`];
        if (hint) lines.push(`Hint: ${hint}`);
        logEvent('list_resources', startTs, { sessionKey, requestId: input?.request_id, error: { code: `HTTP_${res.status}`, message: String(res.errorText || '') } });
        return { content: [{ type: 'text', text: lines.join('\n') }] };
      }
      const names = res.data?.resourceNames || [];
      const rows = names.map((rn: string) => ({ account_id: (rn.split('/').pop() || rn) }));
      const fields = ['account_id'];
      const fmt = (input.output_format || 'table').toLowerCase();
      if (fmt === 'json') return { content: [{ type: 'text', text: JSON.stringify(rows, null, 2) }] };
      if (fmt === 'csv') {
        const { toCsv } = await import('./utils/formatCsv.js');
        const csv = toCsv(rows, fields);
        return { content: [{ type: 'text', text: csv }] };
      }
      const table = tabulate(rows, fields);
      const out = { content: [{ type: 'text', text: `Accounts:\n${table}` }] };
      logEvent('list_resources', startTs, { sessionKey, requestId: input?.request_id });
      return out;
    }
    const limit = Math.max(1, Math.min(1000, Number(input?.limit ?? 500)));
    const filter = (input?.filter || '').trim();
    const where = ["category = 'RESOURCE'", 'selectable = true'];
    if (filter) where.push(`name LIKE '%${filter.replace(/'/g, "''")}%'`);
    // GoogleAdsFieldService search does NOT support FROM; use implicit FROM.
    const query = `SELECT name, category, selectable WHERE ${where.join(' AND ')} ORDER BY name LIMIT ${limit}`;
    const res = await searchGoogleAdsFields(query, sessionKey);
    if (!res.ok) {
      const hint = mapAdsErrorMsg(res.status, res.errorText || '');
      const lines = [`Error listing resources (status ${res.status}): ${res.errorText || ''}`];
      if (hint) lines.push(`Hint: ${hint}`);
      logEvent('list_resources', startTs, { sessionKey, requestId: input?.request_id, error: { code: `HTTP_${res.status}`, message: String(res.errorText || '') } });
      return { content: [{ type: 'text', text: lines.join('\n') }] };
    }
    const items = (res.data?.results || []).map((r: any) => ({ name: r.googleAdsField?.name, category: r.googleAdsField?.category, selectable: r.googleAdsField?.selectable }));
    if (!items.length) return { content: [{ type: 'text', text: 'No resources found.' }] };
    const fields = ['name', 'category', 'selectable'];
    const fmt = (input.output_format || 'table').toLowerCase();
    if (fmt === 'json') return { content: [{ type: 'text', text: JSON.stringify(items, null, 2) }] };
    if (fmt === 'csv') {
      const { toCsv } = await import('./utils/formatCsv.js');
      const csv = toCsv(items, fields);
      return { content: [{ type: 'text', text: csv }] };
    }
    const table = tabulate(items, fields);
    const out = { content: [{ type: 'text', text: `GAQL Resources:\n${table}` }] };
    logEvent('list_resources', startTs, { sessionKey, requestId: input?.request_id });
    return out;
  • Zod input schema (ListResourcesZ) with descriptions and derived JSON schema (ListResourcesSchema) for MCP tool validation.
    export const ListResourcesZ = z.object({
      kind: z.enum(['resources', 'accounts']).default('resources').describe('what to list: resources | accounts'),
      filter: z.string().optional().describe('substring filter on resource name'),
      limit: z.number().default(500).describe('max rows (1-1000)'),
      output_format: z.enum(['table', 'json', 'csv']).default('table').describe('render format'),
    });
    export const ListResourcesSchema: JsonSchema = zodToJsonSchema(ListResourcesZ, 'ListResources') as unknown as JsonSchema;
  • Tool registration via addTool(server, name, description, inputSchema, handlerFn) within registerTools function.
    addTool(
      server,
      "list_resources",
      "List GAQL FROM-able resources via google_ads_field (category=RESOURCE, selectable=true) or list accounts. output_format=table|json|csv.",
      ListResourcesZ,
      async (input: any) => {

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

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