UNPKG

@bdmarvin/mcp-server-gbp

Version:

MCP server for Google Business Profile Performance API.

201 lines (200 loc) 9.17 kB
import { z } from 'zod'; // Common Date schema (as defined from discovery doc) export const GbpDateSchema = z.object({ year: z.number().int().min(0).max(9999), month: z.number().int().min(0).max(12), day: z.number().int().min(0).max(31), }).refine(data => !(data.month === 0 && data.day !== 0), { message: "If month is 0, day must also be 0", path: ["day"], }); // Create a structurally identical but distinct schema for EndMonth (used in GetMonthlySearchKeywordsInputSchema) export const GbpEndMonthDateSchema = z.object({ year: z.number().int().min(0).max(9999), month: z.number().int().min(0).max(12), day: z.number().int().min(0).max(31), }).refine(data => !(data.month === 0 && data.day !== 0), { message: "If month is 0, day must also be 0", path: ["day"], }); // Create a structurally identical but distinct schema for EndDate (for GetDailyPerformanceMetricsInputSchema) export const GbpEndDateSchema = z.object({ year: z.number().int().min(0).max(9999), month: z.number().int().min(0).max(12), day: z.number().int().min(0).max(31), }).refine(data => !(data.month === 0 && data.day !== 0), { message: "If month is 0, day must also be 0", path: ["day"], }); // Enum for Daily Metrics (from discovery doc) export const DailyMetricEnum = z.enum([ "DAILY_METRIC_UNKNOWN", "BUSINESS_IMPRESSIONS_DESKTOP_MAPS", "BUSINESS_IMPRESSIONS_DESKTOP_SEARCH", "BUSINESS_IMPRESSIONS_MOBILE_MAPS", "BUSINESS_IMPRESSIONS_MOBILE_SEARCH", "BUSINESS_CONVERSATIONS", "BUSINESS_DIRECTION_REQUESTS", "CALL_CLICKS", "WEBSITE_CLICKS", "BUSINESS_BOOKINGS", "BUSINESS_FOOD_ORDERS", "BUSINESS_FOOD_MENU_CLICKS" ]); // Input schema for 'get_daily_performance_metrics' tool export const GetDailyPerformanceMetricsInputSchema = z.object({ locationName: z.string().regex(/^locations\/[^/]+$/, "locationName must be in the format locations/{location_id}"), dailyMetrics: z.array(DailyMetricEnum).min(1, "At least one metric must be requested in dailyMetrics"), startDate: GbpDateSchema, endDate: GbpEndDateSchema, // Use the new distinct schema for endDate }); // Input schema for 'get_monthly_search_keywords' tool export const GetMonthlySearchKeywordsInputSchema = z.object({ parentLocationName: z.string().regex(/^locations\/[^/]+$/, "parentLocationName must be in the format locations/{location_id}"), startMonth: GbpDateSchema.refine(d => d.year !== 0 && d.month !== 0, { message: "Year and month are required for startMonth", path: ["startMonth"] }), endMonth: GbpEndMonthDateSchema.refine(d => d.year !== 0 && d.month !== 0, { message: "Year and month are required for endMonth", path: ["endMonth"] }), pageSize: z.number().int().min(1).max(100).optional(), pageToken: z.string().optional(), }); // --- Schemas for Business Information API --- export const ListBusinessesInputSchema = z.object({ accountId: z.string().min(1, "accountId is required"), readMask: z.string().optional().describe("Field mask, e.g., name,title,storeCode,storefrontAddress.addressLines"), pageSize: z.number().int().min(1).max(100).optional(), pageToken: z.string().optional(), filter: z.string().optional().describe('Filter string, e.g., title = "My Store"'), orderBy: z.string().optional().describe("Order by string, e.g., title, store_code desc"), }); export const GetBusinessInformationInputSchema = z.object({ locationName: z.string().regex(/^locations\/[^/]+$/, "locationName must be in the format locations/{locationId}"), readMask: z.string().optional().describe("Field mask to specify returned fields"), }); export const GetMultipleBusinessInformationInputSchema = z.object({ locationNames: z.array(z.string().regex(/^locations\/[^/]+$/, "Each locationName must be in the format locations/{locationId}")).min(1, "At least one locationName is required"), readMask: z.string().optional().describe("Field mask to specify returned fields for each location"), }); export const UpdateBusinessInformationInputSchema = z.object({ locationName: z.string().regex(/^locations\/[^/]+$/, "locationName must be in the format locations/{locationId}"), updateMask: z.string().min(1, "updateMask is required to specify which fields to update."), data: z.any().describe("An object containing the fields to update, corresponding to the updateMask."), }); // --- Schemas for Business Services API --- const MoneySchema = z.object({ currencyCode: z.string().optional(), units: z.string().optional(), nanos: z.number().optional(), }); const ServiceItemSchema = z.object({ serviceTypeId: z.string(), displayName: z.string(), description: z.string().optional(), price: MoneySchema.optional(), }); export const ListServicesInputSchema = z.object({ locationName: z.string().regex(/^locations\/[^/]+$/, "locationName must be in the format locations/{locationId}"), }); export const UpdateServicesInputSchema = z.object({ locationName: z.string().regex(/^locations\/[^/]+$/, "locationName must be in the format locations/{locationId}"), services: z.array(ServiceItemSchema), }); // --- Schemas for v4 Local Posts API --- const V4TimeOfDaySchema = z.object({ hours: z.number().int().optional(), minutes: z.number().int().optional(), seconds: z.number().int().optional(), nanos: z.number().int().optional(), }); const V4DateSchema = z.object({ year: z.number().int(), month: z.number().int(), day: z.number().int(), }); const V4CallToActionSchema = z.object({ actionType: z.enum(["BOOK", "ORDER", "SHOP", "LEARN_MORE", "SIGN_UP", "CALL"]), url: z.string().url(), }); const V4MediaItemSchema = z.object({ mediaFormat: z.enum(["PHOTO", "VIDEO"]), sourceUrl: z.string().url(), }); const V4EventSchema = z.object({ title: z.string(), schedule: z.object({ startDate: V4DateSchema, startTime: V4TimeOfDaySchema.optional(), endDate: V4DateSchema, endTime: V4TimeOfDaySchema.optional(), }), }); const V4OfferSchema = z.object({ couponCode: z.string().optional(), redeemOnlineUrl: z.string().url().optional(), termsConditions: z.string().optional(), }); const V4LocalPostSchema = z.object({ languageCode: z.string().default('en-US'), summary: z.string(), topicType: z.enum(["STANDARD", "EVENT", "OFFER"]), callToAction: V4CallToActionSchema.optional(), media: z.array(V4MediaItemSchema).optional(), event: V4EventSchema.optional(), offer: V4OfferSchema.optional(), }); export const CreateLocalPostInputSchemaV4 = z.object({ accountId: z.string(), locationId: z.string(), post: V4LocalPostSchema, }); export const ListLocalPostsInputSchemaV4 = z.object({ accountId: z.string(), locationId: z.string(), pageSize: z.number().int().optional(), pageToken: z.string().optional(), }); export const GetLocalPostInputSchemaV4 = z.object({ accountId: z.string(), locationId: z.string(), localPostId: z.string(), }); export const UpdateLocalPostInputSchemaV4 = z.object({ accountId: z.string(), locationId: z.string(), localPostId: z.string(), updateMask: z.string(), post: V4LocalPostSchema.omit({ languageCode: true, topicType: true }), // These fields are immutable }); export const DeleteLocalPostInputSchemaV4 = z.object({ accountId: z.string(), locationId: z.string(), localPostId: z.string(), }); // --- New Schema for Account Management API --- export const AccountTypeEnum = z.enum([ "PERSONAL", "LOCATION_GROUP", "USER_GROUP", "ORGANIZATION" ]).describe("Filter by account type"); export const ListManagedAccountsInputSchema = z.object({ pageSize: z.number().int().min(1).max(20).optional().describe("How many accounts to fetch per page. Default and maximum is 20."), pageToken: z.string().optional().describe("Token for fetching the next page of accounts."), filterAccountType: AccountTypeEnum.optional().describe("Optional: Filter accounts by type (e.g., LOCATION_GROUP)"), }); // --- Schemas for Q&A API --- export const ListQuestionsInputSchema = z.object({ locationName: z.string().regex(/^locations\/[^/]+$/, "locationName must be in the format locations/{location_id}"), pageSize: z.number().int().min(1).max(100).optional(), pageToken: z.string().optional(), }); export const CreateQuestionInputSchema = z.object({ locationName: z.string().regex(/^locations\/[^/]+$/, "locationName must be in the format locations/{location_id}"), text: z.string().min(1, "Question text cannot be empty"), }); export const AnswerQuestionInputSchema = z.object({ questionName: z.string().regex(/^locations\/[^/]+\/questions\/[^/]+$/, "questionName must be in the format locations/{location_id}/questions/{question_id}"), text: z.string().min(1, "Answer text cannot be empty"), }); // New Schema for Get Question (if needed, but not in current tools) export const GetQuestionInputSchema = z.object({ questionName: z.string().regex(/^locations\/[^/]+\/questions\/[^/]+$/, "questionName must be in the format locations/{location_id}/questions/{question_id}"), });