escalate_to_vendor
Escalate maintenance tickets to specialized vendors by selecting appropriate trades, updating ticket status, and triggering automated notifications to vendors and tenants.
Instructions
Escalates a maintenance ticket to a specialist vendor. Selects the right vendor from the directory based on trade (plumbing, electrical, hvac, etc.), updates ticket status to 'escalated', triggers an n8n workflow that emails/WhatsApps the vendor, and sends the tenant an update message. Use overrideVendorPhone to assign a specific vendor instead of the default.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| ticketId | Yes | The ID of the ticket to escalate. | |
| trade | Yes | The trade/specialty required. Used to select the appropriate vendor from the directory. | |
| urgencyNote | Yes | A clear description of why this is being escalated and any access/safety considerations the vendor needs to know. This is sent directly to the vendor. | |
| preferredTime | No | Human-readable preferred scheduling window, e.g. 'Tuesday afternoon' or 'ASAP — emergency'. Passed to the vendor via the n8n workflow. | |
| overrideVendorPhone | No | WhatsApp-formatted phone number (e.g. 'whatsapp:+15125550001') to use instead of the default vendor for this trade. Useful for one-off vendor assignments. |
Implementation Reference
- Main handler function that executes the escalate_to_vendor tool logic. It validates the ticket, resolves the vendor based on trade, updates ticket status to 'escalated', triggers n8n webhook for vendor notification, and sends WhatsApp message to tenant.
export async function handleEscalateToVendor( input: EscalateToVendorInput ): Promise<EscalateToVendorOutput> { const ticket = ticketStore.getById(input.ticketId); if (!ticket) { throw new Error(`Ticket not found: ${input.ticketId}`); } if (ticket.status === "resolved" || ticket.status === "closed") { throw new Error( `Cannot escalate ticket ${input.ticketId} — it is already "${ticket.status}".` ); } const vendor = resolveVendor(input.trade as MaintenanceCategory, input.overrideVendorPhone); // 1. Update ticket status → escalated const now = new Date().toISOString(); const updated = ticketStore.updateStatus( input.ticketId, "escalated", `Escalated to ${vendor.name} (${input.trade}). ${input.urgencyNote}`, "Claude MCP Agent" ); // Attach vendor to ticket record const withVendor = { ...updated, vendorAssigned: vendor, updatedAt: now, }; ticketStore.upsert(withVendor); // 2. Trigger n8n escalation workflow (vendor notification, CRM logging) const n8nResult = await triggerEscalationWebhook({ ticketId: ticket.id, ticketTitle: ticket.title, unit: ticket.unit, propertyName: ticket.property.name, category: ticket.category, priority: ticket.priority, urgencyNote: input.urgencyNote, tenantName: ticket.tenant.name, vendorName: vendor.name, vendorPhone: vendor.phone, vendorEmail: vendor.email, preferredTime: input.preferredTime, }); if (n8nResult.executionId) { ticketStore.appendNote( input.ticketId, `n8n escalation workflow triggered (executionId=${n8nResult.executionId})` ); } // 3. Notify tenant that the issue has been escalated to a specialist const tenantMessage = buildTenantMessage( "update", ticket.tenant.name, ticket.title, `Hi ${ticket.tenant.name.split(" ")[0]}! We've escalated your maintenance request ` + `*"${ticket.title}"* to a specialist vendor (${vendor.name}). ` + `${input.preferredTime ? `We're targeting ${input.preferredTime} for the visit. ` : ""}` + `We'll confirm the exact appointment time shortly.` ); const whatsappResult = await sendWhatsApp(ticket.tenant.phone, tenantMessage); ticketStore.appendNote( input.ticketId, `[${now}] Tenant notified of escalation via ${whatsappResult.channel}. ` + `Vendor: ${vendor.name}` ); const finalTicket = ticketStore.getById(input.ticketId)!; return { success: true, vendor, n8nWorkflowTriggered: n8nResult.triggered, n8nExecutionId: n8nResult.executionId, whatsappNotification: whatsappResult, ticket: finalTicket, message: `Ticket ${input.ticketId} escalated to ${vendor.name}. ` + `n8n workflow ${n8nResult.triggered ? "triggered" : "skipped (not configured)"}. ` + `Tenant notified via ${whatsappResult.channel}.`, }; } - Zod schema definition for input validation. Defines required fields: ticketId, trade (enum of 7 maintenance categories), urgencyNote (min 10 chars), and optional fields: preferredTime, overrideVendorPhone.
export const EscalateToVendorSchema = z.object({ ticketId: z .string() .describe("The ID of the ticket to escalate."), trade: z .enum(["plumbing", "electrical", "hvac", "appliance", "structural", "pest", "general"]) .describe( "The trade/specialty required. Used to select the appropriate vendor from the directory." ), urgencyNote: z .string() .min(10) .describe( "A clear description of why this is being escalated and any access/safety considerations " + "the vendor needs to know. This is sent directly to the vendor." ), preferredTime: z .string() .optional() .describe( "Human-readable preferred scheduling window, e.g. 'Tuesday afternoon' or 'ASAP — emergency'. " + "Passed to the vendor via the n8n workflow." ), overrideVendorPhone: z .string() .optional() .describe( "WhatsApp-formatted phone number (e.g. 'whatsapp:+15125550001') to use instead of the " + "default vendor for this trade. Useful for one-off vendor assignments." ), }); - src/mcp-server/index.ts:120-140 (registration)Tool registration with MCP server. Registers 'escalate_to_vendor' tool with description, schema, and async handler that calls handleEscalateToVendor and returns JSON result.
server.tool( "escalate_to_vendor", "Escalates a maintenance ticket to a specialist vendor. " + "Selects the right vendor from the directory based on trade (plumbing, electrical, hvac, etc.), " + "updates ticket status to 'escalated', triggers an n8n workflow that emails/WhatsApps the vendor, " + "and sends the tenant an update message. " + "Use overrideVendorPhone to assign a specific vendor instead of the default.", EscalateToVendorSchema.shape, async (input) => { const result = await handleEscalateToVendor(input); return { content: [ { type: "text", text: JSON.stringify(result, null, 2), }, ], }; } ); - src/integrations/n8n.ts:64-94 (helper)Helper function that triggers n8n escalation workflow webhook. Sends vendor details, ticket info, and urgency note to n8n automation for vendor email/WhatsApp notification and CRM logging.
export async function triggerEscalationWebhook(payload: { ticketId: string; ticketTitle: string; unit: string; propertyName: string; category: string; priority: string; urgencyNote: string; tenantName: string; vendorName: string; vendorPhone: string; vendorEmail: string; preferredTime?: string; }): Promise<N8nTriggerResult> { if (!N8N_ESCALATE) { console.warn("[n8n] N8N_WEBHOOK_ESCALATE not set — skipping webhook"); return { triggered: false, error: "N8N_WEBHOOK_ESCALATE not configured" }; } try { const response = await http.post(N8N_ESCALATE, payload); const executionId: string | undefined = response.data?.executionId ?? response.data?.id; console.log(`[n8n] Escalation webhook triggered (executionId=${executionId ?? "unknown"})`); return { triggered: true, executionId }; } catch (err) { const msg = err instanceof Error ? err.message : String(err); console.error(`[n8n] Escalation webhook failed: ${msg}`); return { triggered: false, error: msg }; } } - src/integrations/whatsapp.ts:21-55 (helper)Helper function that builds tenant notification messages from templates. Used by escalate_to_vendor to construct WhatsApp messages for tenant status updates about escalation.
export function buildTenantMessage( messageType: string, tenantName: string, ticketTitle: string, customMessage?: string, scheduledFor?: string ): string { const firstName = tenantName.split(" ")[0]; if (customMessage) return customMessage; const formattedTime = scheduledFor ? new Date(scheduledFor).toLocaleString("en-US", { weekday: "long", month: "short", day: "numeric", hour: "numeric", minute: "2-digit", hour12: true, }) : "soon"; const templates: Record<string, string> = { acknowledgement: `Hi ${firstName}! 👋 We've received your maintenance request: *"${ticketTitle}"*. ` + `Our team is reviewing it now and will be in touch shortly with next steps. ` + `Reply STOP to unsubscribe from updates.`, scheduled: `Hi ${firstName}! Great news — your maintenance request *"${ticketTitle}"* has been scheduled. ` + `A technician will visit on *${formattedTime}*. ` + `Please ensure someone is home to provide access. Reply if you need to reschedule.`, update: `Hi ${firstName}! Quick update on your request *"${ticketTitle}"*: ` +