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) => {
Behavior2/5

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

No annotations are provided, so the description carries the full burden of behavioral disclosure. It mentions the output format options (table, json, csv) and hints at filtering and limiting, but fails to describe key behavioral traits such as whether this is a read-only operation, potential rate limits, authentication requirements, or what happens if no resources match the filter. For a tool with no annotation coverage, this leaves significant gaps in understanding its behavior.

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 concise and front-loaded, stating the core purpose in a single sentence. It efficiently covers the main functionality and output format without unnecessary details. However, the sentence structure is slightly dense and could be clearer, but it avoids waste and is appropriately sized for the tool's complexity.

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 (4 parameters, no output schema, no annotations), the description is partially complete. It covers the basic purpose and output format but lacks details on behavioral aspects, usage context, and how results are structured. Without an output schema, the description should ideally explain return values or examples, which it doesn't. This makes it adequate but with clear gaps for effective agent use.

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%, meaning the input schema already documents all parameters thoroughly. The description adds minimal value beyond the schema: it mentions 'output_format' options but doesn't explain their semantics further, and it implies filtering on resource name without adding details. Since the schema does the heavy lifting, the baseline score of 3 is appropriate, as the description provides some context but no significant additional meaning.

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 tool's purpose: to list GAQL FROM-able resources via google_ads_field (category=RESOURCE, selectable=true) or list accounts. It specifies the verb ('List') and resources ('GAQL FROM-able resources' or 'accounts'), making the function evident. However, it doesn't explicitly distinguish this tool from sibling tools like 'execute_gaql_query' or 'gaql_help', which could provide similar or related functionality, leaving some ambiguity in differentiation.

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?

The description provides no guidance on when to use this tool versus alternatives. It mentions two possible outputs (resources or accounts) but doesn't explain the use cases for each or how this tool relates to siblings like 'execute_gaql_query' or 'gaql_help'. Without explicit when-to-use or when-not-to-use instructions, the agent lacks clear direction for selection.

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

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