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
| Name | Required | Description | Default |
|---|---|---|---|
| kind | No | what to list: resources | accounts | resources |
| filter | No | substring filter on resource name | |
| limit | No | max rows (1-1000) | |
| output_format | No | render format | table |
Implementation Reference
- src/server-tools.ts:647-715 (handler)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;
- src/schemas.ts:15-21 (schema)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;
- src/server-tools.ts:641-646 (registration)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) => {