Skip to main content
Glama
leads.py15.7 kB
""" Instantly MCP Server - Lead Tools 11 tools for lead and lead list management operations. The most comprehensive tool category with bulk operations and custom variables. """ import json from typing import Any, Optional from ..client import get_client from ..models.leads import ( ListLeadsInput, GetLeadInput, CreateLeadInput, UpdateLeadInput, ListLeadListsInput, CreateLeadListInput, UpdateLeadListInput, GetVerificationStatsInput, BulkAddLeadsInput, DeleteLeadInput, DeleteLeadListInput, MoveLeadsInput, ) async def list_leads(params: Optional[ListLeadsInput] = None) -> str: """ List leads with cursor-based pagination (100 per page). PAGINATION: If response contains pagination.next_starting_after, there are MORE results. Call again with starting_after=<that value> to get next page. Continue until pagination.next_starting_after is null. Filter values: - FILTER_VAL_CONTACTED: Leads that have been contacted - FILTER_VAL_NOT_CONTACTED: Leads not yet contacted - FILTER_VAL_COMPLETED: Leads that completed sequence - FILTER_VAL_ACTIVE: Currently active leads Use distinct_contacts=true to deduplicate by email. """ client = get_client() # Handle case where params is None (for OpenAI/non-Claude clients) # Set default limit=100 to return more results by default if params is None: params = ListLeadsInput(limit=100) # Build request body for POST /leads/list body: dict[str, Any] = {} # Default to 100 results if no limit specified body["limit"] = params.limit or 100 if params.campaign: body["campaign"] = params.campaign if params.list_id: body["list_id"] = params.list_id if params.list_ids: body["list_ids"] = params.list_ids if params.status: body["status"] = params.status if params.created_after: body["created_after"] = params.created_after if params.created_before: body["created_before"] = params.created_before if params.search: body["search"] = params.search if params.filter: body["filter"] = params.filter if params.distinct_contacts is not None: body["distinct_contacts"] = params.distinct_contacts if params.starting_after: body["starting_after"] = params.starting_after result = await client.post("/leads/list", json=body) # Add pagination guidance for LLMs if isinstance(result, dict): pagination = result.get("pagination", {}) next_cursor = pagination.get("next_starting_after") if next_cursor: result["_pagination_hint"] = f"MORE RESULTS AVAILABLE. Call list_leads with starting_after='{next_cursor}' to get next page." return json.dumps(result, indent=2) async def get_lead(params: GetLeadInput) -> str: """ Get lead details by ID. Returns comprehensive lead information including: - Contact details (email, name, company, phone) - Custom variables - Campaign/list membership - Sequence status and history - Interest status """ client = get_client() result = await client.get(f"/leads/{params.lead_id}") return json.dumps(result, indent=2) async def create_lead(params: CreateLeadInput) -> str: """ Create a single lead with custom variables. Use skip_if_in_campaign=true to prevent duplicates (recommended). Custom variables must match field names defined in the campaign. Example: {"industry": "Technology", "company_size": "50-100"} For bulk imports (10+ leads), use add_leads_to_campaign_or_list_bulk instead. """ client = get_client() body: dict[str, Any] = { "email": params.email, } if params.campaign: body["campaign"] = params.campaign if params.first_name: body["first_name"] = params.first_name if params.last_name: body["last_name"] = params.last_name if params.company_name: body["company_name"] = params.company_name if params.phone: body["phone"] = params.phone if params.website: body["website"] = params.website if params.personalization: body["personalization"] = params.personalization if params.lt_interest_status is not None: body["lt_interest_status"] = params.lt_interest_status if params.pl_value_lead: body["pl_value_lead"] = params.pl_value_lead if params.list_id: body["list_id"] = params.list_id if params.assigned_to: body["assigned_to"] = params.assigned_to if params.skip_if_in_workspace is not None: body["skip_if_in_workspace"] = params.skip_if_in_workspace if params.skip_if_in_campaign is not None: body["skip_if_in_campaign"] = params.skip_if_in_campaign if params.skip_if_in_list is not None: body["skip_if_in_list"] = params.skip_if_in_list if params.blocklist_id: body["blocklist_id"] = params.blocklist_id if params.verify_leads_on_import is not None: body["verify_leads_on_import"] = params.verify_leads_on_import if params.custom_variables: body["custom_variables"] = params.custom_variables result = await client.post("/leads", json=body) return json.dumps(result, indent=2) async def update_lead(params: UpdateLeadInput) -> str: """ Update lead (partial update). ⚠️ IMPORTANT: custom_variables REPLACES the entire object! To preserve existing custom variables: 1. First call get_lead to retrieve current values 2. Merge your changes with existing values 3. Pass the complete merged object Example: If lead has {"industry": "Tech"} and you want to add {"size": "Large"}, you must pass {"industry": "Tech", "size": "Large"} - not just {"size": "Large"} """ client = get_client() body: dict[str, Any] = {} if params.personalization is not None: body["personalization"] = params.personalization if params.website is not None: body["website"] = params.website if params.last_name is not None: body["last_name"] = params.last_name if params.first_name is not None: body["first_name"] = params.first_name if params.company_name is not None: body["company_name"] = params.company_name if params.phone is not None: body["phone"] = params.phone if params.lt_interest_status is not None: body["lt_interest_status"] = params.lt_interest_status if params.pl_value_lead is not None: body["pl_value_lead"] = params.pl_value_lead if params.assigned_to is not None: body["assigned_to"] = params.assigned_to if params.custom_variables is not None: body["custom_variables"] = params.custom_variables result = await client.patch(f"/leads/{params.lead_id}", json=body) return json.dumps(result, indent=2) async def list_lead_lists(params: Optional[ListLeadListsInput] = None) -> str: """ List lead lists with cursor-based pagination (100 per page). PAGINATION: If response contains pagination.next_starting_after, there are MORE results. Call again with starting_after=<that value> to get next page. Continue until pagination.next_starting_after is null. Lead lists are containers for organizing leads outside of campaigns. Use has_enrichment_task filter to find lists with auto-enrichment enabled. """ client = get_client() # Handle case where params is None (for OpenAI/non-Claude clients) # Set default limit=100 to return more results by default if params is None: params = ListLeadListsInput(limit=100) query_params = {} if params.limit: query_params["limit"] = params.limit else: # Default to 100 results if no limit specified query_params["limit"] = 100 if params.starting_after: query_params["starting_after"] = params.starting_after if params.has_enrichment_task is not None: query_params["has_enrichment_task"] = params.has_enrichment_task if params.search: query_params["search"] = params.search result = await client.get("/lead-lists", params=query_params) # Add pagination guidance for LLMs if isinstance(result, dict): pagination = result.get("pagination", {}) next_cursor = pagination.get("next_starting_after") if next_cursor: result["_pagination_hint"] = f"MORE RESULTS AVAILABLE. Call list_lead_lists with starting_after='{next_cursor}' to get next page." return json.dumps(result, indent=2) async def create_lead_list(params: CreateLeadListInput) -> str: """ Create a lead list. Set has_enrichment_task=true to enable automatic lead enrichment. Enrichment adds company info, social profiles, and other data. """ client = get_client() body: dict[str, Any] = { "name": params.name, } if params.has_enrichment_task is not None: body["has_enrichment_task"] = params.has_enrichment_task if params.owned_by: body["owned_by"] = params.owned_by result = await client.post("/lead-lists", json=body) return json.dumps(result, indent=2) async def update_lead_list(params: UpdateLeadListInput) -> str: """ Update lead list name, enrichment settings, or owner. """ client = get_client() body: dict[str, Any] = {} if params.name is not None: body["name"] = params.name if params.has_enrichment_task is not None: body["has_enrichment_task"] = params.has_enrichment_task if params.owned_by is not None: body["owned_by"] = params.owned_by result = await client.patch(f"/lead-lists/{params.list_id}", json=body) return json.dumps(result, indent=2) async def get_verification_stats_for_lead_list(params: GetVerificationStatsInput) -> str: """ Get email verification statistics for a lead list. Returns breakdown of verification results: - Valid emails - Invalid/bounced emails - Risky emails - Unknown status """ client = get_client() result = await client.get(f"/lead-lists/{params.list_id}/verification-stats") return json.dumps(result, indent=2) async def add_leads_to_campaign_or_list_bulk(params: BulkAddLeadsInput) -> str: """ Bulk add up to 1,000 leads. 10-100x faster than create_lead. Provide EITHER campaign_id OR list_id (not both). Each lead requires at minimum an email address. Custom variables must match campaign field definitions. Use skip_if_in_campaign=true to prevent duplicates (recommended). """ client = get_client() body: dict[str, Any] = { "leads": [lead.model_dump(exclude_none=True) for lead in params.leads], } if params.campaign_id: body["campaign_id"] = params.campaign_id if params.list_id: body["list_id"] = params.list_id if params.blocklist_id: body["blocklist_id"] = params.blocklist_id if params.assigned_to: body["assigned_to"] = params.assigned_to if params.verify_leads_on_import is not None: body["verify_leads_on_import"] = params.verify_leads_on_import if params.skip_if_in_workspace is not None: body["skip_if_in_workspace"] = params.skip_if_in_workspace if params.skip_if_in_campaign is not None: body["skip_if_in_campaign"] = params.skip_if_in_campaign if params.skip_if_in_list is not None: body["skip_if_in_list"] = params.skip_if_in_list result = await client.post("/leads/add", json=body) return json.dumps(result, indent=2) async def delete_lead(params: DeleteLeadInput) -> str: """ 🗑️ PERMANENTLY delete a lead. CANNOT UNDO! This action: - Removes the lead from all campaigns and lists - Deletes all lead data and history - Cannot be reversed Confirm with user before executing! """ client = get_client() result = await client.delete(f"/leads/{params.lead_id}") return json.dumps({"success": True, "deleted": params.lead_id, **result}, indent=2) async def move_leads_to_campaign_or_list(params: MoveLeadsInput) -> str: """ Move or copy leads between campaigns/lists. Runs as background job for large operations. Source selection (use one): - ids: Specific lead IDs to move - search + campaign/list_id: Filter leads by search term - filter + campaign/list_id: Filter by status Destination (use one): - to_campaign_id: Target campaign - to_list_id: Target list Set copy_leads=true to copy instead of move. """ client = get_client() body: dict[str, Any] = {} if params.to_campaign_id: body["to_campaign_id"] = params.to_campaign_id if params.to_list_id: body["to_list_id"] = params.to_list_id if params.ids: body["ids"] = params.ids if params.search: body["search"] = params.search if params.filter: body["filter"] = params.filter if params.campaign: body["campaign"] = params.campaign if params.list_id: body["list_id"] = params.list_id if params.in_campaign is not None: body["in_campaign"] = params.in_campaign if params.in_list is not None: body["in_list"] = params.in_list if params.queries: body["queries"] = params.queries if params.excluded_ids: body["excluded_ids"] = params.excluded_ids if params.contacts: body["contacts"] = params.contacts if params.check_duplicates_in_campaigns is not None: body["check_duplicates_in_campaigns"] = params.check_duplicates_in_campaigns if params.skip_leads_in_verification is not None: body["skip_leads_in_verification"] = params.skip_leads_in_verification if params.limit is not None: body["limit"] = params.limit if params.assigned_to: body["assigned_to"] = params.assigned_to if params.esp_code is not None: body["esp_code"] = params.esp_code if params.esg_code is not None: body["esg_code"] = params.esg_code if params.copy_leads is not None: body["copy_leads"] = params.copy_leads if params.check_duplicates is not None: body["check_duplicates"] = params.check_duplicates result = await client.post("/leads/move", json=body) return json.dumps(result, indent=2) async def delete_lead_list(params: DeleteLeadListInput) -> str: """ 🚨 PERMANENTLY delete a lead list. CANNOT UNDO! ⚠️ REQUIRES USER CONFIRMATION before executing! This action: - Permanently removes the lead list - Leads in the list may be orphaned (check your workflow) - Cannot be reversed Before calling this tool, you MUST: 1. Confirm with the user that they want to delete this list 2. Verify the list_id is correct 3. Warn them this action cannot be undone """ client = get_client() result = await client.delete(f"/lead-lists/{params.list_id}") return json.dumps({ "success": True, "deleted_list_id": params.list_id, "message": "Lead list permanently deleted", **result }, indent=2) # Export all lead tools LEAD_TOOLS = [ list_leads, get_lead, create_lead, update_lead, list_lead_lists, create_lead_list, update_lead_list, get_verification_stats_for_lead_list, add_leads_to_campaign_or_list_bulk, delete_lead, delete_lead_list, move_leads_to_campaign_or_list, ]

Implementation Reference

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/bcharleson/instantly-mcp-python'

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