Export Leads
lead_exportExport leads to CRM, Google Sheets, or file formats. Filter by lead IDs or minimum score to target only qualified prospects.
Instructions
Push leads to an external destination. target must be one of "hubspot", "pipedrive", "google_sheets", "csv", or "json". For CRM targets (hubspot, pipedrive) the respective API key env var must be set (HUBSPOT_API_KEY, PIPEDRIVE_API_TOKEN) — if missing, the tool returns a dry-run payload instead of erroring. Filter the export via lead_ids (explicit list) or min_score (everything above threshold). Returns {target, count, summary, errors?}.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| lead_ids | No | Explicit list of lead UUIDs to export. If omitted, every lead matching min_score (or all leads, when min_score is also omitted) is exported. | |
| target | Yes | Where to send the leads. "hubspot" / "pipedrive" require HUBSPOT_API_KEY / PIPEDRIVE_API_TOKEN env vars — without them, the tool returns a dry-run payload instead of erroring. "google_sheets" requires GOOGLE_SHEETS_CREDENTIALS. "csv" / "json" produce inline output you can pipe to disk. | |
| min_score | No | Inclusive minimum score for inclusion. Use 60+ for "qualified-only" exports. Ignored when lead_ids is provided. |
Implementation Reference
- src/services/crm/exporter.ts:188-249 (handler)Main export handler: orchestrates fetching leads (by IDs or by min_score filter), routing to the correct target (hubspot, pipedrive, google_sheets, csv, json), and returning an ExportResult with summary, counts, and optional errors/data.
export async function exportLeads(input: LeadExportInput): Promise<ExportResult> { const leads = await getLeadsForExport(input); if (leads.length === 0) { return { target: input.target, exported_count: 0, failed_count: 0, summary: 'No leads matched the export criteria.', }; } switch (input.target) { case 'hubspot': return exportToHubSpot(leads); case 'pipedrive': return exportToPipedrive(leads); case 'google_sheets': return { target: 'google_sheets', exported_count: leads.length, failed_count: 0, summary: `Prepared ${leads.length} leads for Google Sheets. Set GOOGLE_SHEETS_CREDENTIALS to enable direct export.`, data: { rows: leads.map(formatForCRM) }, }; case 'csv': { const csv = leadsToCSV(leads); const ids = leads.map((l) => l.id); await markExported(ids); return { target: 'csv', exported_count: leads.length, failed_count: 0, summary: `Exported ${leads.length} leads as CSV.`, data: csv, }; } case 'json': { const ids = leads.map((l) => l.id); await markExported(ids); return { target: 'json', exported_count: leads.length, failed_count: 0, summary: `Exported ${leads.length} leads as JSON.`, data: leads, }; } default: return { target: input.target, exported_count: 0, failed_count: 0, summary: `Unknown export target: ${input.target}`, }; } } - src/index.ts:277-300 (registration)Tool registration for 'lead_export': registers with title 'Export Leads', description, LeadExportInputSchema, and a handler that checks pro license then calls exportLeads(input).
// ━━━ TOOL: lead_export ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ server.registerTool( 'lead_export', { title: 'Export Leads', description: 'Push leads to an external destination. target must be one of "hubspot", "pipedrive", "google_sheets", "csv", or "json". For CRM targets (hubspot, pipedrive) the respective API key env var must be set (HUBSPOT_API_KEY, PIPEDRIVE_API_TOKEN) — if missing, the tool returns a dry-run payload instead of erroring. Filter the export via lead_ids (explicit list) or min_score (everything above threshold). Returns {target, count, summary, errors?}.', inputSchema: LeadExportInputSchema, annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false }, }, async (input) => { try { const reject = await ensureProOrReject(LICENSE_CONFIG, 'lead_export'); if (reject) return reject; const result = await exportLeads(input); return { content: [{ type: 'text' as const, text: result.summary }], structuredContent: result as unknown as Record<string, unknown>, }; } catch (error) { return handleToolError(error); } } ); - src/models/lead.ts:184-191 (schema)LeadExportInputSchema and LeadExportInput type: defines input with optional lead_ids array, target enum (hubspot/pipedrive/google_sheets/csv/json), and optional min_score number.
/** Input schema for exporting leads to CRM or file. */ export const LeadExportInputSchema = z.object({ lead_ids: z.array(z.string()).optional().describe('Explicit list of lead UUIDs to export. If omitted, every lead matching min_score (or all leads, when min_score is also omitted) is exported.'), target: ExportTargetSchema.describe('Where to send the leads. "hubspot" / "pipedrive" require HUBSPOT_API_KEY / PIPEDRIVE_API_TOKEN env vars — without them, the tool returns a dry-run payload instead of erroring. "google_sheets" requires GOOGLE_SHEETS_CREDENTIALS. "csv" / "json" produce inline output you can pipe to disk.'), min_score: z.number().optional().describe('Inclusive minimum score for inclusion. Use 60+ for "qualified-only" exports. Ignored when lead_ids is provided.'), }); export type LeadExportInput = z.infer<typeof LeadExportInputSchema>; - src/services/crm/exporter.ts:21-38 (helper)getLeadsForExport: fetches leads by explicit IDs via storage.getLeadById, or filters all leads by min_score and qualified/scored status.
async function getLeadsForExport(input: LeadExportInput): Promise<Lead[]> { if (input.lead_ids && input.lead_ids.length > 0) { const leads: Lead[] = []; for (const id of input.lead_ids) { const lead = await storage.getLeadById(id); if (lead) leads.push(lead); } return leads; } const all = await storage.getAllLeads(); return all.filter((lead) => { if (input.min_score != null && (lead.score == null || lead.score < input.min_score)) { return false; } return lead.status === 'qualified' || lead.status === 'scored'; }); } - src/services/license.ts:133-145 (helper)ensureProOrReject: license gate that returns a rejection message if the user doesn't have a Pro license for 'lead_export'.
export async function ensureProOrReject( config: LicenseConfig, toolName: string, ): Promise<{ content: Array<{ type: 'text'; text: string }> } | null> { const status = await validateLicense(config); if (status.isPro) return null; return { content: [{ type: 'text', text: proGateMessage(toolName, status) }], }; }