get_booking_options
Retrieve UTM-tagged booking URL, accepted payment methods, and business hours for direct client booking without sharing customer data.
Instructions
Get UTM-tagged booking URL plus accepted methods and hours for a business. The user books directly with the SMB; we never see customer data.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | Business ID. | |
| agentName | No | MCP client identifier for UTM attribution. |
Implementation Reference
- src/tools/getBookingOptions.ts:21-50 (handler)The main handler function for get_booking_options. Looks up a business by ID, collects accepted booking methods (online, phone, email, walk_in), resolves fallback contact, and returns UTM-tagged booking URL, hours, timezone, and fallback contact info.
export async function getBookingOptions( input: GetBookingOptionsInput ): Promise<BookingOptions | { error: string }> { const b = await getBusinessById(input.id); if (!b) return { error: `No business found with id: ${input.id}` }; const acceptedMethods: BookingOptions["acceptedMethods"] = []; if (b.bookingUrl) acceptedMethods.push("online"); for (const c of b.contactChannels) { if (c.type === "phone") acceptedMethods.push("phone"); if (c.type === "email") acceptedMethods.push("email"); } // Always include walk-in by default for medical / realtor / insurance offices. acceptedMethods.push("walk_in"); const fallback: ContactChannel = b.contactChannels.find((c) => c.preferred) ?? b.contactChannels[0] ?? { type: "website", value: b.socials.website ?? "" }; return { businessId: b.id, bookingUrl: b.bookingUrl ? tagBookingUrl(b.bookingUrl, { businessId: b.id, agentName: input.agentName, campaign: "booking" }) : "", acceptedMethods: Array.from(new Set(acceptedMethods)), hours: b.hours, timezone: b.timezone, fallbackContact: fallback }; } - src/tools/getBookingOptions.ts:14-17 (schema)Zod schema defining the tool input: required business ID string and optional agentName string for UTM attribution.
export const getBookingOptionsSchema = z.object({ id: z.string().describe("Business ID."), agentName: z.string().optional().describe("MCP client identifier for UTM attribution.") }); - src/server.ts:45-53 (registration)Registration of the tool with the MCP server using server.tool(), connecting the schema and handler.
server.tool( "get_booking_options", "Get UTM-tagged booking URL plus accepted methods and hours for a business. The user books directly with the SMB; we never see customer data.", getBookingOptionsSchema.shape, async (args) => { const opts = await getBookingOptions(getBookingOptionsSchema.parse(args)); return { content: [{ type: "text", text: JSON.stringify(opts, null, 2) }] }; } ); - src/lib/db.ts:55-68 (helper)Helper function that looks up a business by ID from the data store (mock JSON fallback or Supabase).
export async function getBusinessById(id: string): Promise<BusinessProfile | null> { const all = await getAllBusinesses(); return all.find((b) => b.id === id) ?? null; } export async function getCategories(countryCode?: string): Promise<CategoryEntry[]> { if (DATA_SOURCE === "supabase") { throw new Error("Supabase data source not yet wired."); } const cats = loadMockCategories(); if (!countryCode) return cats; return cats.filter((c) => c.availableInCountries.includes(countryCode)); } - src/lib/utm.ts:17-34 (helper)Helper that appends UTM tracking parameters (source, medium, campaign, term, content) to the business's raw booking URL.
export function tagBookingUrl(rawUrl: string, ctx: UtmContext): string { if (!rawUrl) return rawUrl; let url: URL; try { url = new URL(rawUrl); } catch { // not a valid URL — return as-is rather than corrupting it return rawUrl; } url.searchParams.set("utm_source", SOURCE); url.searchParams.set("utm_medium", MEDIUM); if (ctx.campaign) url.searchParams.set("utm_campaign", ctx.campaign); if (ctx.agentName) url.searchParams.set("utm_term", ctx.agentName); url.searchParams.set("utm_content", ctx.businessId); return url.toString(); }