local_audit
Assess local pack position, organic rankings, profile completeness, review velocity, and competitors to receive actionable recommendations for improving local search visibility.
Instructions
Run a comprehensive local SEO audit. Checks local pack position, organic rankings, profile completeness, review velocity, and competitors. Returns actionable recommendations. Costs 50 credits. This runs as an async job and may take 15-45 seconds.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| business_name | Yes | Business name | |
| location | Yes | City and state |
Implementation Reference
- src/server.ts:38-38 (registration)registerAuditTools is called in createMcpServer, which registers all audit tools including 'local_audit'
registerAuditTools(server, getAuth); - src/tools/audit.ts:38-67 (handler)The 'local_audit' tool handler: submits an audit job POST /v1/audit/local, optionally polls for async completion, and returns formatted results
export function registerAuditTools(server: McpServer, getAuth: () => string) { server.tool( "local_audit", "Run a comprehensive local SEO audit. Checks local pack position, organic rankings, profile completeness, review velocity, and competitors. Returns actionable recommendations. Costs 50 credits. This runs as an async job and may take 15-45 seconds.", { business_name: z.string().describe("Business name"), location: z.string().describe("City and state"), }, READ_ONLY, withErrorHandling(async ({ business_name, location }) => { // Submit the job const submitResult = await callApi( "/v1/audit/local", { business_name, location }, getAuth(), 30_000 ); const submitData = submitResult.data as Record<string, unknown>; // If cached result returned directly (no job_id), return immediately if (!submitData.job_id) { return { content: [{ type: "text" as const, text: formatResult(submitResult.data, submitResult) }] }; } // Poll for completion const pollUrl = submitData.poll_url as string; const result = await pollAsyncJob(pollUrl, getAuth()); return { content: [{ type: "text" as const, text: formatResult(result.data, result) }] }; }) ); - src/tools/audit.ts:40-46 (schema)Schema definition for local_audit: requires business_name (string) and location (string), with read-only hints
"local_audit", "Run a comprehensive local SEO audit. Checks local pack position, organic rankings, profile completeness, review velocity, and competitors. Returns actionable recommendations. Costs 50 credits. This runs as an async job and may take 15-45 seconds.", { business_name: z.string().describe("Business name"), location: z.string().describe("City and state"), }, READ_ONLY, - src/tools/audit.ts:12-36 (helper)pollAsyncJob helper function used by the local_audit handler to poll an async job URL until completion or timeout (3 minutes)
async function pollAsyncJob( pollUrl: string, auth: string, maxWaitMs: number = 180_000 ): Promise<{ data: unknown; credits_used: number; credits_remaining: number; cached: boolean }> { const start = Date.now(); let delay = 2000; while (Date.now() - start < maxWaitMs) { await new Promise((r) => setTimeout(r, delay)); const result = await callApiGet(pollUrl, auth); const data = result.data as Record<string, unknown>; if (data.status === "complete") { return result; } if (data.status === "failed") { throw new Error((data.error as string) || "Audit job failed"); } // Still pending/running — increase delay up to 4s delay = Math.min(delay * 1.3, 4000); } throw new Error("Audit timed out after 3 minutes. The job may still be processing — try polling the status URL."); } - src/api-client.ts:143-158 (helper)withErrorHandling wrapper used in the local_audit handler to catch and surface errors as MCP error content
export function withErrorHandling<T>( fn: (args: T) => Promise<ToolResult> ): (args: T) => Promise<ToolResult> { return async (args) => { try { return await fn(args); } catch (err) { const message = err instanceof Error ? err.message : String(err); console.error(`[mcp] Tool error: ${message}`); return { content: [{ type: "text" as const, text: `Error: ${message}` }], isError: true, }; } }; }