Skip to main content
Glama
enzoemir1

leadpipe-mcp

Seed Demo Leads

lead_demo_seed

Seed 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

TableJSON Schema
NameRequiredDescriptionDefault

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);
        }
      }
    );
  • 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.`,
      };
    }
  • 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;
    }
  • 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';
    }
  • 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' };
      }
    }
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations provide limited behavioral detail (readOnlyHint=false, destructiveHint=false). The description adds valuable context: each call appends a fresh batch with new UUIDs, returns counts by status and sample IDs. No contradictions.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Concise paragraph front-loading the core action. Every sentence adds value: counts, archetypes, enrichment state, downstream tools, use case, safety, return info. No waste.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Despite no output schema, the description fully explains return values (counts by status plus sample_lead_ids) and mentions use with other tools. Complete for a demo seeding tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

No parameters exist in the input schema, so the description cannot add meaning beyond schema. With 100% coverage, baseline is 4. No additional parameter info needed.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: 'Populate the pipeline with a realistic demo dataset: 14 leads across 5 archetypes'. It uses specific verbs and resources, distinguishing it from sibling tools like lead_ingest and lead_batch_ingest.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly states when to use: 'Use this to evaluate LeadPipe via MCP Inspector without Hunter, HubSpot, or Pipedrive API keys'. Also notes it is safe to call multiple times. Could improve by suggesting alternatives for real data, but current guidance is clear.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/enzoemir1/leadpipe-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server