request_cleaning
Schedule cleaning services in San Francisco by submitting customer details for automated booking requests to service partners.
Instructions
Request cleaning service in San Francisco
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| name | Yes | Customer name | |
| phone | Yes | Phone number (10-digit US format) | |
| address | Yes | Service address in San Francisco |
Implementation Reference
- index.js:135-270 (handler)Executes the 'request_cleaning' tool: extracts arguments, validates and sanitizes inputs (name, phone, address), checks if address is in San Francisco, sends email notification to partners using Resend, handles errors and timeouts.if (request.params.name === 'request_cleaning') { console.error('[INFO] Processing cleaning request'); try { const { name, phone, address } = request.params.arguments || {}; // Validate required fields if (!name || !phone || !address) { console.error('[WARN] Missing required fields'); return { content: [{ type: 'text', text: 'Error: All fields (name, phone, address) are required.' }] }; } // Sanitize inputs const cleanName = sanitizeString(name, 100); const cleanAddress = sanitizeString(address, 200); // Validate name if (cleanName.length < 2) { console.error('[WARN] Invalid name provided'); return { content: [{ type: 'text', text: 'Error: Please provide a valid name (at least 2 characters).' }] }; } // Validate phone const phoneValidation = validatePhone(phone); if (!phoneValidation.valid) { console.error('[WARN] Invalid phone number:', phone); return { content: [{ type: 'text', text: 'Error: Please provide a valid 10-digit US phone number.' }] }; } // Validate address if (cleanAddress.length < 10) { console.error('[WARN] Address too short'); return { content: [{ type: 'text', text: 'Error: Please provide a complete address.' }] }; } // Check if in SF if (!validateSFAddress(cleanAddress)) { console.error('[INFO] Non-SF address:', cleanAddress); return { content: [{ type: 'text', text: "Sorry, we only serve San Francisco currently. We're expanding - stay tuned!" }] }; } // Log the request console.error('[INFO] Sending email for:', { name: cleanName, phone: phoneValidation.cleaned, address: cleanAddress.substring(0, 50) + '...' }); // Send email with timeout const emailPromise = resend.emails.send({ from: process.env.FROM_EMAIL, to: process.env.PARTNER_EMAILS.split(',').map(e => e.trim()), subject: 'Cleaning Request - SF', text: `New cleaning service request: Customer Details: - Name: ${cleanName} - Phone: ${phoneValidation.cleaned} - Address: ${cleanAddress} Request Time: ${new Date().toLocaleString('en-US', { timeZone: 'America/Los_Angeles' })} Please contact the customer within 1 hour.`, html: ` <h2>New Cleaning Service Request</h2> <p><strong>Customer Details:</strong></p> <ul> <li><strong>Name:</strong> ${cleanName}</li> <li><strong>Phone:</strong> ${phoneValidation.cleaned}</li> <li><strong>Address:</strong> ${cleanAddress}</li> </ul> <p><strong>Request Time:</strong> ${new Date().toLocaleString('en-US', { timeZone: 'America/Los_Angeles' })}</p> <p><em>Please contact the customer within 1 hour.</em></p> ` }); // Add timeout to prevent hanging const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('Email timeout')), 10000) ); try { await Promise.race([emailPromise, timeoutPromise]); console.error('[SUCCESS] Email sent successfully'); return { content: [{ type: 'text', text: 'Successfully booked the maid. Will get confirmation shortly.' }] }; } catch (emailError) { console.error('[ERROR] Failed to send email:', emailError.message); return { content: [{ type: 'text', text: 'Booking received but email notification failed. The service will still process your request.' }] }; } } catch (error) { console.error('[CRITICAL] Unexpected error:', error); return { content: [{ type: 'text', text: 'Service temporarily unavailable. Please try again later.' }] }; } }
- index.js:104-129 (schema)Input schema definition for the 'request_cleaning' tool, specifying required fields and constraints.name: 'request_cleaning', description: 'Request cleaning service in San Francisco', inputSchema: { type: 'object', properties: { name: { type: 'string', description: 'Customer name', minLength: 2, maxLength: 100 }, phone: { type: 'string', description: 'Phone number (10-digit US format)', pattern: '^[0-9\\s\\-\\(\\)\\+]+$' }, address: { type: 'string', description: 'Service address in San Francisco', minLength: 10, maxLength: 200 } }, required: ['name', 'phone', 'address'] } }]
- index.js:100-131 (registration)Registers the 'request_cleaning' tool by handling ListToolsRequestSchema and returning the tool list with schema.server.setRequestHandler(ListToolsRequestSchema, async () => { console.error('[INFO] Listing tools'); return { tools: [{ name: 'request_cleaning', description: 'Request cleaning service in San Francisco', inputSchema: { type: 'object', properties: { name: { type: 'string', description: 'Customer name', minLength: 2, maxLength: 100 }, phone: { type: 'string', description: 'Phone number (10-digit US format)', pattern: '^[0-9\\s\\-\\(\\)\\+]+$' }, address: { type: 'string', description: 'Service address in San Francisco', minLength: 10, maxLength: 200 } }, required: ['name', 'phone', 'address'] } }] }; });
- index.js:134-280 (registration)Registers the tool call handler via CallToolRequestSchema, dispatching to 'request_cleaning' logic if matched.server.setRequestHandler(CallToolRequestSchema, async (request) => { if (request.params.name === 'request_cleaning') { console.error('[INFO] Processing cleaning request'); try { const { name, phone, address } = request.params.arguments || {}; // Validate required fields if (!name || !phone || !address) { console.error('[WARN] Missing required fields'); return { content: [{ type: 'text', text: 'Error: All fields (name, phone, address) are required.' }] }; } // Sanitize inputs const cleanName = sanitizeString(name, 100); const cleanAddress = sanitizeString(address, 200); // Validate name if (cleanName.length < 2) { console.error('[WARN] Invalid name provided'); return { content: [{ type: 'text', text: 'Error: Please provide a valid name (at least 2 characters).' }] }; } // Validate phone const phoneValidation = validatePhone(phone); if (!phoneValidation.valid) { console.error('[WARN] Invalid phone number:', phone); return { content: [{ type: 'text', text: 'Error: Please provide a valid 10-digit US phone number.' }] }; } // Validate address if (cleanAddress.length < 10) { console.error('[WARN] Address too short'); return { content: [{ type: 'text', text: 'Error: Please provide a complete address.' }] }; } // Check if in SF if (!validateSFAddress(cleanAddress)) { console.error('[INFO] Non-SF address:', cleanAddress); return { content: [{ type: 'text', text: "Sorry, we only serve San Francisco currently. We're expanding - stay tuned!" }] }; } // Log the request console.error('[INFO] Sending email for:', { name: cleanName, phone: phoneValidation.cleaned, address: cleanAddress.substring(0, 50) + '...' }); // Send email with timeout const emailPromise = resend.emails.send({ from: process.env.FROM_EMAIL, to: process.env.PARTNER_EMAILS.split(',').map(e => e.trim()), subject: 'Cleaning Request - SF', text: `New cleaning service request: Customer Details: - Name: ${cleanName} - Phone: ${phoneValidation.cleaned} - Address: ${cleanAddress} Request Time: ${new Date().toLocaleString('en-US', { timeZone: 'America/Los_Angeles' })} Please contact the customer within 1 hour.`, html: ` <h2>New Cleaning Service Request</h2> <p><strong>Customer Details:</strong></p> <ul> <li><strong>Name:</strong> ${cleanName}</li> <li><strong>Phone:</strong> ${phoneValidation.cleaned}</li> <li><strong>Address:</strong> ${cleanAddress}</li> </ul> <p><strong>Request Time:</strong> ${new Date().toLocaleString('en-US', { timeZone: 'America/Los_Angeles' })}</p> <p><em>Please contact the customer within 1 hour.</em></p> ` }); // Add timeout to prevent hanging const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('Email timeout')), 10000) ); try { await Promise.race([emailPromise, timeoutPromise]); console.error('[SUCCESS] Email sent successfully'); return { content: [{ type: 'text', text: 'Successfully booked the maid. Will get confirmation shortly.' }] }; } catch (emailError) { console.error('[ERROR] Failed to send email:', emailError.message); return { content: [{ type: 'text', text: 'Booking received but email notification failed. The service will still process your request.' }] }; } } catch (error) { console.error('[CRITICAL] Unexpected error:', error); return { content: [{ type: 'text', text: 'Service temporarily unavailable. Please try again later.' }] }; } } // Unknown tool console.error('[WARN] Unknown tool requested:', request.params.name); return { content: [{ type: 'text', text: 'Unknown tool requested.' }] }; });
- index.js:54-61 (helper)Helper function to sanitize string inputs by removing HTML tags, special characters, trimming, and limiting length.const sanitizeString = (str, maxLength = 200) => { if (typeof str !== 'string') return ''; return str .replace(/<[^>]*>/g, '') // Remove HTML tags .replace(/[<>\"']/g, '') // Remove special chars .trim() .slice(0, maxLength); };
- index.js:64-75 (helper)Helper to validate and format US phone numbers to 10 digits, returning cleaned formatted version.const validatePhone = (phone) => { if (typeof phone !== 'string') return { valid: false, cleaned: null }; const cleaned = phone.replace(/\D/g, ''); if (cleaned.length !== 10) { return { valid: false, cleaned: null }; } const formatted = `${cleaned.slice(0,3)}-${cleaned.slice(3,6)}-${cleaned.slice(6)}`; return { valid: true, cleaned: formatted }; };
- index.js:78-87 (helper)Helper to check if address is in San Francisco by keywords or zip code patterns.const validateSFAddress = (address) => { if (typeof address !== 'string') return false; const addr = address.toLowerCase(); const sfIndicators = ['sf', 'san francisco', 'sanfrancisco']; const hasSFText = sfIndicators.some(indicator => addr.includes(indicator)); const hasSFZip = /94[01]\d{2}/.test(addr); return hasSFText || hasSFZip; };