Seed Demo Leads
lead_demo_seedSeed a realistic demo dataset with 14 leads across 5 archetypes to test LeadPipe tools without external API keys. Returns counts and sample lead IDs for further processing.
Instructions
Populate the pipeline with a realistic demo dataset: 14 leads across 5 archetypes (hot decision-makers, warm mid-level, cold junior/small-co, raw unenriched, and disqualified). Each lead has appropriate enrichment state, scoring breakdown, and status, so every downstream tool — lead_list, lead_search, lead_score, crm_export, and the pipeline-overview resource — returns meaningful output immediately. Use this to evaluate LeadPipe via MCP Inspector without Hunter, HubSpot, or Pipedrive API keys. Safe to call multiple times; each call appends a fresh batch with new UUIDs. Returns counts by status plus sample_lead_ids you can feed into lead_enrich, lead_score, or crm_export.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
No arguments | |||
Implementation Reference
- src/index.ts:40-71 (registration)Registration of the 'lead_demo_seed' tool on the McpServer. Includes its schema (empty object input) and the handler that invokes seedDemoLeads().
// ━━━ TOOL: lead_demo_seed ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ server.registerTool( 'lead_demo_seed', { title: 'Seed Demo Leads', description: 'Populate the pipeline with a realistic demo dataset: 14 leads across 5 archetypes (hot decision-makers, warm mid-level, cold junior/small-co, raw unenriched, and disqualified). Each lead has appropriate enrichment state, scoring breakdown, and status, so every downstream tool — lead_list, lead_search, lead_score, crm_export, and the pipeline-overview resource — returns meaningful output immediately. Use this to evaluate LeadPipe via MCP Inspector without Hunter, HubSpot, or Pipedrive API keys. Safe to call multiple times; each call appends a fresh batch with new UUIDs. Returns counts by status plus sample_lead_ids you can feed into lead_enrich, lead_score, or crm_export.', inputSchema: z.object({}), annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }, }, async () => { try { const result = await seedDemoLeads(); const lines = [ `Seeded ${result.leads} demo leads:`, ` Qualified: ${result.qualified}`, ` Scored: ${result.scored}`, ` New (unscored): ${result.new}`, ` Disqualified: ${result.disqualified}`, ` Avg score: ${result.avg_score ?? 'n/a'}`, ``, `Sample lead ids (use with lead_enrich, lead_score):`, ...result.sample_lead_ids.map((id) => ` - ${id}`), ]; return { content: [{ type: 'text' as const, text: lines.join('\n') }], structuredContent: result as unknown as Record<string, unknown>, }; } catch (error) { return handleToolError(error); } } ); - src/services/demo-seed.ts:84-188 (handler)The core handler function 'seedDemoLeads' that generates and persists 14 realistic demo leads across 5 archetypes (hot, warm, cold, raw, disqualified) with appropriate enrichment, scoring, and status.
export async function seedDemoLeads(store?: Storage): Promise<DemoSeedResult> { const s = store ?? defaultStorage; const now = new Date(); const nowMs = now.getTime(); const leads: Lead[] = []; let qualified = 0, scored = 0, newCount = 0, disqualified = 0; let scoreSum = 0, scoreN = 0; for (let i = 0; i < LEAD_SPECS.length; i++) { const spec = LEAD_SPECS[i]; const { score, status } = scoreForArchetype(spec.archetype); // Spread creation over last ~60 days const ageDays = 2 + (i * 4) % 58; const createdAt = new Date(nowMs - ageDays * 86_400_000); const enrichedAt = spec.archetype === 'raw' ? null : new Date(createdAt.getTime() + 3_600_000); const scoredAt = score != null ? new Date((enrichedAt ?? createdAt).getTime() + 60_000) : null; const hasCompany = spec.archetype !== 'raw' && spec.companyName !== ''; const company: CompanyInfo | undefined = hasCompany ? { name: spec.companyName, domain: spec.domain, industry: spec.industry, size: spec.size, country: spec.country, description: `${spec.companyName} — ${spec.industry} company, ${spec.size} employees.`, linkedin_url: `https://linkedin.com/company/${spec.domain.replace(/\./g, '-')}`, tech_stack: spec.industry === 'saas' || spec.industry === 'technology' ? ['TypeScript', 'Node.js', 'React', 'PostgreSQL'] : spec.industry === 'fintech' ? ['Python', 'Go', 'Kubernetes', 'Redis'] : ['JavaScript', 'WordPress'], } : undefined; const firstLower = spec.first.toLowerCase(); const lastLower = spec.last.toLowerCase(); const emailDomain = spec.domain || 'example.test'; const email = `${firstLower}.${lastLower}@${emailDomain}`; const lead: Lead = { id: randomUUID(), email, first_name: spec.first, last_name: spec.last, full_name: `${spec.first} ${spec.last}`, phone: spec.archetype === 'hot' || spec.archetype === 'warm' ? `+1-555-${String(1000 + i).padStart(4, '0')}` : undefined, job_title: spec.title || undefined, company, source: spec.source, source_detail: spec.source === 'landing_page' ? '/pricing' : spec.source === 'website_form' ? '/contact' : undefined, tags: spec.archetype === 'hot' ? ['enterprise', 'priority'] : spec.archetype === 'warm' ? ['follow-up'] : [], custom_fields: spec.archetype === 'hot' ? { budget: '50k-100k', timeline: 'Q2-Q3' } : {}, score, score_breakdown: score != null ? { total: score, job_title_score: spec.archetype === 'hot' ? 95 : spec.archetype === 'warm' ? 70 : 30, company_size_score: spec.size === '11-50' || spec.size === '51-200' || spec.size === '201-500' ? 85 : 40, industry_score: ['saas', 'fintech', 'technology', 'ecommerce', 'marketing', 'consulting'].includes(spec.industry) ? 90 : 45, engagement_score: spec.archetype === 'hot' ? 80 : spec.archetype === 'warm' ? 55 : 25, recency_score: Math.max(0, 100 - ageDays * 2), custom_rules_score: 0, details: [ { rule: 'high_value_title', points: spec.archetype === 'hot' ? 20 : 0, reason: `Title "${spec.title}" ${spec.archetype === 'hot' ? 'matches' : 'does not match'} high-value list` }, { rule: 'preferred_size', points: (spec.size === '11-50' || spec.size === '51-200' || spec.size === '201-500') ? 15 : -5, reason: `Size ${spec.size}` }, ], } : null, status, created_at: createdAt.toISOString(), updated_at: (scoredAt ?? enrichedAt ?? createdAt).toISOString(), enriched_at: enrichedAt ? enrichedAt.toISOString() : null, scored_at: scoredAt ? scoredAt.toISOString() : null, exported_at: null, }; leads.push(lead); if (status === 'qualified') qualified++; else if (status === 'scored') scored++; else if (status === 'disqualified') disqualified++; else if (status === 'new') newCount++; if (score != null) { scoreSum += score; scoreN++; } } // Persist for (const l of leads) await s.addLead(l); const avgScore = scoreN > 0 ? Math.round((scoreSum / scoreN) * 10) / 10 : null; return { leads: leads.length, qualified, scored, new: newCount, disqualified, avg_score: avgScore, sample_lead_ids: leads.slice(0, 5).map((l) => l.id), message: `Seeded ${leads.length} leads (${qualified} qualified, ${scored} scored, ${newCount} new, ${disqualified} disqualified). Try: lead_list with status filter, or crm_export with min_score=60.`, }; } - src/services/demo-seed.ts:64-73 (schema)The DemoSeedResult interface defining the return shape: counts by status, average score, and sample lead IDs.
export interface DemoSeedResult { leads: number; qualified: number; scored: number; new: number; disqualified: number; avg_score: number | null; sample_lead_ids: string[]; message: string; } - src/services/demo-seed.ts:14-25 (schema)The LeadSpec interface and LEAD_SPECS array defining the 14 seed lead specifications (name, title, company, archetype, etc.).
interface LeadSpec { first: string; last: string; title: string; companyName: string; domain: string; industry: string; size: CompanySize; country: string; source: LeadSource; archetype: 'hot' | 'warm' | 'cold' | 'raw' | 'disqualified'; } - src/services/demo-seed.ts:53-61 (helper)Helper function 'scoreForArchetype' mapping archetype strings to score and status values.
function scoreForArchetype(a: LeadSpec['archetype']): { score: number | null; status: LeadStatus } { switch (a) { case 'hot': return { score: 88, status: 'qualified' }; case 'warm': return { score: 68, status: 'scored' }; case 'cold': return { score: 32, status: 'scored' }; case 'raw': return { score: null, status: 'new' }; case 'disqualified': return { score: 15, status: 'disqualified' }; } }