local_pack
Retrieve businesses from Google's local 3-pack for any keyword and city. Get names, ratings, reviews, phone numbers, websites, hours, and GPS coordinates.
Instructions
Get businesses in Google's local 3-pack for any keyword and city. Returns names, ratings, review counts, phone numbers, websites, hours, and GPS coordinates. Costs 1 credit.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| keyword | Yes | Search keyword (e.g. "plumber") | |
| location | Yes | City and state (e.g. "Orchard Park, NY") | |
| device | No | Device type. Default: desktop | |
| depth | No | Number of results. Default: 20, max: 60 |
Implementation Reference
- src/tools/serp.ts:11-30 (registration)The 'local_pack' tool is registered via server.tool() call inside registerSerpTools(). The registration defines the tool name 'local_pack', its description, input schema (keyword, location, device, depth), and the handler.
export function registerSerpTools(server: McpServer, getAuth: () => string) { server.tool( "local_pack", "Get businesses in Google's local 3-pack for any keyword and city. Returns names, ratings, review counts, phone numbers, websites, hours, and GPS coordinates. Costs 1 credit.", { keyword: z.string().describe('Search keyword (e.g. "plumber")'), location: z.string().describe('City and state (e.g. "Orchard Park, NY")'), device: z.enum(["desktop", "mobile"]).optional().describe("Device type. Default: desktop"), depth: z.number().int().min(1).max(60).optional().describe("Number of results. Default: 20, max: 60"), }, READ_ONLY, withErrorHandling(async ({ keyword, location, device, depth }) => { const result = await callApi( "/v1/serp/local-pack", { keyword, location, ...(device && { device }), ...(depth && { depth }) }, getAuth() ); return { content: [{ type: "text" as const, text: formatResult(result.data, result) }] }; }) ); - src/tools/serp.ts:22-29 (handler)The actual tool handler: an async function that calls the API endpoint '/v1/serp/local-pack' with keyword, location, optional device and depth parameters, then formats and returns the result.
withErrorHandling(async ({ keyword, location, device, depth }) => { const result = await callApi( "/v1/serp/local-pack", { keyword, location, ...(device && { device }), ...(depth && { depth }) }, getAuth() ); return { content: [{ type: "text" as const, text: formatResult(result.data, result) }] }; }) - src/tools/serp.ts:15-19 (schema)The Zod input schema for the local_pack tool: keyword (string), location (string), optional device (enum 'desktop'|'mobile'), optional depth (integer 1-60).
{ keyword: z.string().describe('Search keyword (e.g. "plumber")'), location: z.string().describe('City and state (e.g. "Orchard Park, NY")'), device: z.enum(["desktop", "mobile"]).optional().describe("Device type. Default: desktop"), depth: z.number().int().min(1).max(60).optional().describe("Number of results. Default: 20, max: 60"), - src/tools/serp.ts:5-30 (helper)The READ_ONLY configuration object passed to the tool, setting readOnlyHint=true, destructiveHint=false, openWorldHint=true.
const READ_ONLY = { readOnlyHint: true, destructiveHint: false, openWorldHint: true, } as const; export function registerSerpTools(server: McpServer, getAuth: () => string) { server.tool( "local_pack", "Get businesses in Google's local 3-pack for any keyword and city. Returns names, ratings, review counts, phone numbers, websites, hours, and GPS coordinates. Costs 1 credit.", { keyword: z.string().describe('Search keyword (e.g. "plumber")'), location: z.string().describe('City and state (e.g. "Orchard Park, NY")'), device: z.enum(["desktop", "mobile"]).optional().describe("Device type. Default: desktop"), depth: z.number().int().min(1).max(60).optional().describe("Number of results. Default: 20, max: 60"), }, READ_ONLY, withErrorHandling(async ({ keyword, location, device, depth }) => { const result = await callApi( "/v1/serp/local-pack", { keyword, location, ...(device && { device }), ...(depth && { depth }) }, getAuth() ); return { content: [{ type: "text" as const, text: formatResult(result.data, result) }] }; }) ); - src/api-client.ts:25-30 (helper)The callApi helper function calls the external LocalSEOData API. For local_pack it hits '/v1/serp/local-pack'. Also exports formatResult and withErrorHandling which format responses and wrap handlers with error handling.
export async function callApi( path: string, body: Record<string, unknown>, authHeader: string, timeoutMs = 60_000 ): Promise<{ data: unknown; credits_used: number; credits_remaining: number; cached: boolean }> {