Spaces:
Runtime error
Runtime error
Commit
·
dbbf1a3
1
Parent(s):
6a58058
Add application files
Browse files- app.py +70 -47
- mcp/agents/autonomous_agent_hf.py +185 -9
- mcp/tools/definitions.py +29 -1
app.py
CHANGED
|
@@ -894,52 +894,34 @@ async def discover_prospects(num_prospects: int, progress=gr.Progress()):
|
|
| 894 |
About {client_name}:
|
| 895 |
{client_info}
|
| 896 |
|
| 897 |
-
|
| 898 |
-
|
| 899 |
-
|
| 900 |
-
|
| 901 |
-
|
| 902 |
-
-
|
| 903 |
-
|
| 904 |
-
|
| 905 |
-
|
| 906 |
-
|
| 907 |
-
|
| 908 |
-
|
| 909 |
-
|
| 910 |
-
|
| 911 |
-
|
| 912 |
-
|
| 913 |
-
|
| 914 |
-
|
| 915 |
-
|
| 916 |
-
|
| 917 |
-
|
| 918 |
-
|
| 919 |
-
|
| 920 |
-
|
| 921 |
-
|
| 922 |
-
{{"prospect_id": "prospect_1", "company_id": "company_1", "company_name": "CompanyName", "company_domain": "company.com", "fit_score": 85, "metadata": {{"industry": "...", "fit_reason": "..."}}}}
|
| 923 |
-
|
| 924 |
-
d. Draft personalized email using send_email tool:
|
| 925 |
-
- Use the REAL contact info returned by find_verified_contacts
|
| 926 |
-
- to: actual verified email from the tool result
|
| 927 |
-
- subject: Reference {client_name} AND the prospect's business
|
| 928 |
-
- body: Personalized email with real facts about the company
|
| 929 |
-
- prospect_id: the prospect_id
|
| 930 |
-
|
| 931 |
-
IMPORTANT RULES:
|
| 932 |
-
- CONTACTS FIRST: Always find contacts BEFORE saving a prospect
|
| 933 |
-
- NO CONTACTS = NO SAVE: Skip companies where no verified contacts are found
|
| 934 |
-
- NEVER invent contact names or emails
|
| 935 |
-
- Keep searching until you find {num_prospects} companies WITH verified contacts
|
| 936 |
-
- It's better to have fewer prospects with real contacts than many with none
|
| 937 |
-
|
| 938 |
-
After processing, provide summary:
|
| 939 |
-
- Prospects saved (only those with verified contacts)
|
| 940 |
- Total contacts found
|
| 941 |
-
-
|
| 942 |
-
-
|
| 943 |
|
| 944 |
prospects_found = []
|
| 945 |
contacts_found = []
|
|
@@ -1050,8 +1032,49 @@ After processing, provide summary:
|
|
| 1050 |
else:
|
| 1051 |
output += f" ✅ Contact saved\n"
|
| 1052 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1053 |
elif tool == "find_verified_contacts":
|
| 1054 |
-
# Handle verified contacts from the enhanced contact finder
|
| 1055 |
if isinstance(result, dict):
|
| 1056 |
status = result.get("status", "")
|
| 1057 |
found_contacts = result.get("contacts", [])
|
|
@@ -1071,7 +1094,7 @@ After processing, provide summary:
|
|
| 1071 |
contacts_found.append(contact_data)
|
| 1072 |
output += f" • {contact_data['name']} ({contact_data['title']}) - {contact_data['email']}\n"
|
| 1073 |
elif status == "no_contacts_found":
|
| 1074 |
-
output += f"
|
| 1075 |
else:
|
| 1076 |
output += f" ⚠️ Contact search: {message}\n"
|
| 1077 |
|
|
|
|
| 894 |
About {client_name}:
|
| 895 |
{client_info}
|
| 896 |
|
| 897 |
+
USE THE discover_prospects_with_contacts TOOL - it handles everything automatically:
|
| 898 |
+
- Searches for potential prospect companies
|
| 899 |
+
- Finds verified contacts for each (LinkedIn, company websites, directories, etc.)
|
| 900 |
+
- ONLY saves prospects that have real verified contacts
|
| 901 |
+
- Keeps searching until target is met or max attempts reached
|
| 902 |
+
- Skips companies without contacts automatically
|
| 903 |
+
|
| 904 |
+
STEP 1: Call discover_prospects_with_contacts:
|
| 905 |
+
{{"client_company": "{client_name}", "client_industry": "Brief description of {client_name}'s services and target market", "target_prospects": {num_prospects}, "target_titles": ["CEO", "Founder", "VP Sales", "CTO", "Head of Sales"]}}
|
| 906 |
+
|
| 907 |
+
STEP 2: After discovery completes, for each prospect with contacts, draft personalized email:
|
| 908 |
+
- Use send_email tool with the REAL contact info returned
|
| 909 |
+
- to: actual verified email
|
| 910 |
+
- subject: Reference {client_name} AND the prospect's business
|
| 911 |
+
- body: Personalized email mentioning the contact by name and specific facts about their company
|
| 912 |
+
- prospect_id: the prospect_id from discovery results
|
| 913 |
+
|
| 914 |
+
IMPORTANT:
|
| 915 |
+
- The discover_prospects_with_contacts tool does ALL the hard work
|
| 916 |
+
- It will check multiple companies until it finds {num_prospects} with verified contacts
|
| 917 |
+
- Only prospects WITH contacts are saved (no useless data)
|
| 918 |
+
- NEVER invent contact names or emails - only use what the tool returns
|
| 919 |
+
|
| 920 |
+
After the tool completes, provide a summary of:
|
| 921 |
+
- Prospects saved (with verified contacts)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 922 |
- Total contacts found
|
| 923 |
+
- Companies checked vs skipped
|
| 924 |
+
- Emails drafted"""
|
| 925 |
|
| 926 |
prospects_found = []
|
| 927 |
contacts_found = []
|
|
|
|
| 1032 |
else:
|
| 1033 |
output += f" ✅ Contact saved\n"
|
| 1034 |
|
| 1035 |
+
elif tool == "discover_prospects_with_contacts":
|
| 1036 |
+
# Handle the all-in-one prospect discovery tool
|
| 1037 |
+
if isinstance(result, dict):
|
| 1038 |
+
status = result.get("status", "")
|
| 1039 |
+
discovered_prospects = result.get("prospects", [])
|
| 1040 |
+
total_contacts = result.get("contacts_count", 0)
|
| 1041 |
+
companies_checked = result.get("companies_checked", 0)
|
| 1042 |
+
companies_skipped = result.get("companies_skipped", 0)
|
| 1043 |
+
message = result.get("message", "")
|
| 1044 |
+
|
| 1045 |
+
output += f"\n 📊 **Discovery Complete**\n"
|
| 1046 |
+
output += f" {message}\n\n"
|
| 1047 |
+
|
| 1048 |
+
if discovered_prospects:
|
| 1049 |
+
for p in discovered_prospects:
|
| 1050 |
+
# Add to prospects_found
|
| 1051 |
+
prospect_data = {
|
| 1052 |
+
"name": p.get("company_name", "Unknown"),
|
| 1053 |
+
"domain": p.get("domain", ""),
|
| 1054 |
+
"fit_score": 75,
|
| 1055 |
+
"summary": f"Found with {p.get('contact_count', 0)} verified contacts"
|
| 1056 |
+
}
|
| 1057 |
+
prospects_found.append(prospect_data)
|
| 1058 |
+
|
| 1059 |
+
output += f" ✅ **{p.get('company_name')}** ({p.get('domain')})\n"
|
| 1060 |
+
|
| 1061 |
+
# Add contacts
|
| 1062 |
+
for c in p.get("contacts", []):
|
| 1063 |
+
contact_data = {
|
| 1064 |
+
"name": c.get("name", "Unknown"),
|
| 1065 |
+
"email": c.get("email", ""),
|
| 1066 |
+
"title": c.get("title", ""),
|
| 1067 |
+
"company": p.get("company_name", ""),
|
| 1068 |
+
"verified": True,
|
| 1069 |
+
"source": c.get("source", "web_search")
|
| 1070 |
+
}
|
| 1071 |
+
contacts_found.append(contact_data)
|
| 1072 |
+
output += f" • {c.get('name')} ({c.get('title')}) - {c.get('email')}\n"
|
| 1073 |
+
else:
|
| 1074 |
+
output += f" ⚠️ No prospects with verified contacts found.\n"
|
| 1075 |
+
|
| 1076 |
elif tool == "find_verified_contacts":
|
| 1077 |
+
# Handle verified contacts from the enhanced contact finder (single company)
|
| 1078 |
if isinstance(result, dict):
|
| 1079 |
status = result.get("status", "")
|
| 1080 |
found_contacts = result.get("contacts", [])
|
|
|
|
| 1094 |
contacts_found.append(contact_data)
|
| 1095 |
output += f" • {contact_data['name']} ({contact_data['title']}) - {contact_data['email']}\n"
|
| 1096 |
elif status == "no_contacts_found":
|
| 1097 |
+
output += f" ⏭️ {message}\n"
|
| 1098 |
else:
|
| 1099 |
output += f" ⚠️ Contact search: {message}\n"
|
| 1100 |
|
mcp/agents/autonomous_agent_hf.py
CHANGED
|
@@ -577,7 +577,12 @@ After completing, summarize:
|
|
| 577 |
return None
|
| 578 |
keys = set(params.keys())
|
| 579 |
|
| 580 |
-
# Check for
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 581 |
if "company_name" in keys and "company_domain" in keys and "target_titles" in keys:
|
| 582 |
return "find_verified_contacts"
|
| 583 |
if "company_name" in keys and "company_domain" in keys and "max_contacts" in keys:
|
|
@@ -730,7 +735,178 @@ After completing, summarize:
|
|
| 730 |
"count": len(results[:max_results])
|
| 731 |
}
|
| 732 |
|
| 733 |
-
# ============
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 734 |
elif tool_name == "find_verified_contacts":
|
| 735 |
from services.enhanced_contact_finder import EnhancedContactFinder
|
| 736 |
|
|
@@ -741,7 +917,6 @@ After completing, summarize:
|
|
| 741 |
|
| 742 |
logger.info(f"Finding verified contacts for {company_name} ({company_domain})")
|
| 743 |
|
| 744 |
-
# Use the EnhancedContactFinder which does real web scraping and LinkedIn search
|
| 745 |
contact_finder = EnhancedContactFinder(mcp_registry=self.mcp_registry)
|
| 746 |
|
| 747 |
try:
|
|
@@ -752,7 +927,6 @@ After completing, summarize:
|
|
| 752 |
max_contacts=max_contacts
|
| 753 |
)
|
| 754 |
|
| 755 |
-
# Convert contacts to serializable format
|
| 756 |
contact_list = []
|
| 757 |
for contact in contacts:
|
| 758 |
contact_data = {
|
|
@@ -762,12 +936,11 @@ After completing, summarize:
|
|
| 762 |
"title": contact.title,
|
| 763 |
"company": company_name,
|
| 764 |
"domain": company_domain,
|
| 765 |
-
"verified": True,
|
| 766 |
"source": "web_search_and_scraping"
|
| 767 |
}
|
| 768 |
contact_list.append(contact_data)
|
| 769 |
|
| 770 |
-
# Also save to store automatically
|
| 771 |
await self.mcp_registry.store.save_contact({
|
| 772 |
"id": contact.id,
|
| 773 |
"company_id": company_domain.replace(".", "_"),
|
|
@@ -782,14 +955,16 @@ After completing, summarize:
|
|
| 782 |
"status": "success",
|
| 783 |
"contacts": contact_list,
|
| 784 |
"count": len(contact_list),
|
| 785 |
-
"message": f"Found {len(contact_list)} verified contacts at {company_name}"
|
|
|
|
| 786 |
}
|
| 787 |
else:
|
| 788 |
return {
|
| 789 |
"status": "no_contacts_found",
|
| 790 |
"contacts": [],
|
| 791 |
"count": 0,
|
| 792 |
-
"message": f"No verified contacts found for {company_name}.
|
|
|
|
| 793 |
}
|
| 794 |
|
| 795 |
except Exception as e:
|
|
@@ -798,7 +973,8 @@ After completing, summarize:
|
|
| 798 |
"status": "error",
|
| 799 |
"contacts": [],
|
| 800 |
"count": 0,
|
| 801 |
-
"message": f"Error searching for contacts: {str(e)}"
|
|
|
|
| 802 |
}
|
| 803 |
|
| 804 |
# ============ STORE MCP SERVER ============
|
|
|
|
| 577 |
return None
|
| 578 |
keys = set(params.keys())
|
| 579 |
|
| 580 |
+
# Check for discover_prospects_with_contacts (HIGHEST PRIORITY - all-in-one tool)
|
| 581 |
+
if "client_company" in keys and "client_industry" in keys:
|
| 582 |
+
return "discover_prospects_with_contacts"
|
| 583 |
+
if "client_company" in keys and "target_prospects" in keys:
|
| 584 |
+
return "discover_prospects_with_contacts"
|
| 585 |
+
# Check for find_verified_contacts patterns (single company)
|
| 586 |
if "company_name" in keys and "company_domain" in keys and "target_titles" in keys:
|
| 587 |
return "find_verified_contacts"
|
| 588 |
if "company_name" in keys and "company_domain" in keys and "max_contacts" in keys:
|
|
|
|
| 735 |
"count": len(results[:max_results])
|
| 736 |
}
|
| 737 |
|
| 738 |
+
# ============ OPTIMIZED PROSPECT DISCOVERY WITH CONTACTS ============
|
| 739 |
+
elif tool_name == "discover_prospects_with_contacts":
|
| 740 |
+
from services.enhanced_contact_finder import EnhancedContactFinder
|
| 741 |
+
from urllib.parse import urlparse
|
| 742 |
+
|
| 743 |
+
client_company = tool_input["client_company"]
|
| 744 |
+
client_industry = tool_input["client_industry"]
|
| 745 |
+
target_prospects = tool_input.get("target_prospects", 3)
|
| 746 |
+
target_titles = tool_input.get("target_titles", ["CEO", "Founder", "VP Sales", "CTO", "Head of Sales"])
|
| 747 |
+
|
| 748 |
+
logger.info(f"Discovering {target_prospects} prospects with contacts for {client_company}")
|
| 749 |
+
print(f"\n[PROSPECT DISCOVERY] ========================================")
|
| 750 |
+
print(f"[PROSPECT DISCOVERY] Finding {target_prospects} prospects WITH verified contacts")
|
| 751 |
+
print(f"[PROSPECT DISCOVERY] Client: {client_company}")
|
| 752 |
+
print(f"[PROSPECT DISCOVERY] ========================================")
|
| 753 |
+
|
| 754 |
+
contact_finder = EnhancedContactFinder(mcp_registry=self.mcp_registry)
|
| 755 |
+
|
| 756 |
+
saved_prospects = []
|
| 757 |
+
all_contacts = []
|
| 758 |
+
skipped_companies = []
|
| 759 |
+
companies_checked = 0
|
| 760 |
+
max_companies_to_check = target_prospects * 5 # Check up to 5x to find enough with contacts
|
| 761 |
+
|
| 762 |
+
# Search queries to find potential companies
|
| 763 |
+
search_queries = [
|
| 764 |
+
f"companies that need {client_industry} services",
|
| 765 |
+
f"businesses looking for {client_industry}",
|
| 766 |
+
f"startups {client_industry} customers",
|
| 767 |
+
f"enterprise companies {client_industry}",
|
| 768 |
+
f"growing companies need {client_industry}",
|
| 769 |
+
]
|
| 770 |
+
|
| 771 |
+
seen_domains = set()
|
| 772 |
+
|
| 773 |
+
for query in search_queries:
|
| 774 |
+
if len(saved_prospects) >= target_prospects:
|
| 775 |
+
break
|
| 776 |
+
|
| 777 |
+
try:
|
| 778 |
+
print(f"\n[PROSPECT DISCOVERY] Searching: {query}")
|
| 779 |
+
results = await self.mcp_registry.search.query(query, max_results=10)
|
| 780 |
+
|
| 781 |
+
for result in results:
|
| 782 |
+
if len(saved_prospects) >= target_prospects:
|
| 783 |
+
break
|
| 784 |
+
if companies_checked >= max_companies_to_check:
|
| 785 |
+
break
|
| 786 |
+
|
| 787 |
+
url = result.get('url', '')
|
| 788 |
+
title = result.get('title', '')
|
| 789 |
+
|
| 790 |
+
# Extract domain from URL
|
| 791 |
+
try:
|
| 792 |
+
parsed = urlparse(url)
|
| 793 |
+
domain = parsed.netloc.replace('www.', '')
|
| 794 |
+
if not domain or domain in seen_domains:
|
| 795 |
+
continue
|
| 796 |
+
seen_domains.add(domain)
|
| 797 |
+
except:
|
| 798 |
+
continue
|
| 799 |
+
|
| 800 |
+
# Extract company name from title
|
| 801 |
+
company_name = title.split(' - ')[0].split(' | ')[0].strip()
|
| 802 |
+
if not company_name or len(company_name) < 3:
|
| 803 |
+
continue
|
| 804 |
+
|
| 805 |
+
# Skip if it's a directory/aggregator site
|
| 806 |
+
skip_domains = ['linkedin.com', 'facebook.com', 'twitter.com', 'wikipedia.org',
|
| 807 |
+
'crunchbase.com', 'zoominfo.com', 'yelp.com', 'glassdoor.com']
|
| 808 |
+
if any(skip in domain for skip in skip_domains):
|
| 809 |
+
continue
|
| 810 |
+
|
| 811 |
+
companies_checked += 1
|
| 812 |
+
print(f"\n[PROSPECT DISCOVERY] Checking ({companies_checked}/{max_companies_to_check}): {company_name} ({domain})")
|
| 813 |
+
|
| 814 |
+
# Find contacts for this company
|
| 815 |
+
try:
|
| 816 |
+
contacts = await contact_finder.find_real_contacts(
|
| 817 |
+
company_name=company_name,
|
| 818 |
+
domain=domain,
|
| 819 |
+
target_titles=target_titles,
|
| 820 |
+
max_contacts=3
|
| 821 |
+
)
|
| 822 |
+
|
| 823 |
+
if contacts and len(contacts) > 0:
|
| 824 |
+
# Save prospect
|
| 825 |
+
prospect_id = f"prospect_{len(saved_prospects) + 1}"
|
| 826 |
+
company_id = domain.replace(".", "_")
|
| 827 |
+
|
| 828 |
+
prospect_data = {
|
| 829 |
+
"id": prospect_id,
|
| 830 |
+
"company": {
|
| 831 |
+
"id": company_id,
|
| 832 |
+
"name": company_name,
|
| 833 |
+
"domain": domain
|
| 834 |
+
},
|
| 835 |
+
"fit_score": 75,
|
| 836 |
+
"status": "new",
|
| 837 |
+
"metadata": {"source": "automated_discovery"}
|
| 838 |
+
}
|
| 839 |
+
|
| 840 |
+
await self.mcp_registry.store.save_prospect(prospect_data)
|
| 841 |
+
|
| 842 |
+
# Save contacts
|
| 843 |
+
contact_list = []
|
| 844 |
+
for contact in contacts:
|
| 845 |
+
contact_data = {
|
| 846 |
+
"id": contact.id,
|
| 847 |
+
"name": contact.name,
|
| 848 |
+
"email": contact.email,
|
| 849 |
+
"title": contact.title,
|
| 850 |
+
"company": company_name,
|
| 851 |
+
"domain": domain,
|
| 852 |
+
"verified": True,
|
| 853 |
+
"source": "web_search_and_scraping"
|
| 854 |
+
}
|
| 855 |
+
contact_list.append(contact_data)
|
| 856 |
+
all_contacts.append(contact_data)
|
| 857 |
+
|
| 858 |
+
await self.mcp_registry.store.save_contact({
|
| 859 |
+
"id": contact.id,
|
| 860 |
+
"company_id": company_id,
|
| 861 |
+
"email": contact.email,
|
| 862 |
+
"first_name": contact.name.split()[0] if contact.name else "",
|
| 863 |
+
"last_name": contact.name.split()[-1] if len(contact.name.split()) > 1 else "",
|
| 864 |
+
"title": contact.title
|
| 865 |
+
})
|
| 866 |
+
|
| 867 |
+
saved_prospects.append({
|
| 868 |
+
"prospect_id": prospect_id,
|
| 869 |
+
"company_name": company_name,
|
| 870 |
+
"domain": domain,
|
| 871 |
+
"contacts": contact_list,
|
| 872 |
+
"contact_count": len(contact_list)
|
| 873 |
+
})
|
| 874 |
+
|
| 875 |
+
print(f"[PROSPECT DISCOVERY] ✅ SAVED: {company_name} with {len(contacts)} contacts")
|
| 876 |
+
else:
|
| 877 |
+
skipped_companies.append({"name": company_name, "domain": domain, "reason": "no_contacts"})
|
| 878 |
+
print(f"[PROSPECT DISCOVERY] ⏭️ SKIPPED: {company_name} (no verified contacts)")
|
| 879 |
+
|
| 880 |
+
except Exception as e:
|
| 881 |
+
logger.debug(f"Error checking {company_name}: {str(e)}")
|
| 882 |
+
skipped_companies.append({"name": company_name, "domain": domain, "reason": str(e)})
|
| 883 |
+
continue
|
| 884 |
+
|
| 885 |
+
except Exception as e:
|
| 886 |
+
logger.debug(f"Search error: {str(e)}")
|
| 887 |
+
continue
|
| 888 |
+
|
| 889 |
+
print(f"\n[PROSPECT DISCOVERY] ========================================")
|
| 890 |
+
print(f"[PROSPECT DISCOVERY] DISCOVERY COMPLETE")
|
| 891 |
+
print(f"[PROSPECT DISCOVERY] ========================================")
|
| 892 |
+
print(f"[PROSPECT DISCOVERY] Prospects saved: {len(saved_prospects)}/{target_prospects}")
|
| 893 |
+
print(f"[PROSPECT DISCOVERY] Total contacts: {len(all_contacts)}")
|
| 894 |
+
print(f"[PROSPECT DISCOVERY] Companies checked: {companies_checked}")
|
| 895 |
+
print(f"[PROSPECT DISCOVERY] Companies skipped: {len(skipped_companies)}")
|
| 896 |
+
print(f"[PROSPECT DISCOVERY] ========================================\n")
|
| 897 |
+
|
| 898 |
+
return {
|
| 899 |
+
"status": "success" if len(saved_prospects) > 0 else "no_prospects_found",
|
| 900 |
+
"prospects": saved_prospects,
|
| 901 |
+
"prospects_count": len(saved_prospects),
|
| 902 |
+
"contacts_count": len(all_contacts),
|
| 903 |
+
"companies_checked": companies_checked,
|
| 904 |
+
"companies_skipped": len(skipped_companies),
|
| 905 |
+
"target_met": len(saved_prospects) >= target_prospects,
|
| 906 |
+
"message": f"Found {len(saved_prospects)} prospects with {len(all_contacts)} verified contacts. Checked {companies_checked} companies, skipped {len(skipped_companies)} (no contacts)."
|
| 907 |
+
}
|
| 908 |
+
|
| 909 |
+
# ============ VERIFIED CONTACT FINDER (Single Company) ============
|
| 910 |
elif tool_name == "find_verified_contacts":
|
| 911 |
from services.enhanced_contact_finder import EnhancedContactFinder
|
| 912 |
|
|
|
|
| 917 |
|
| 918 |
logger.info(f"Finding verified contacts for {company_name} ({company_domain})")
|
| 919 |
|
|
|
|
| 920 |
contact_finder = EnhancedContactFinder(mcp_registry=self.mcp_registry)
|
| 921 |
|
| 922 |
try:
|
|
|
|
| 927 |
max_contacts=max_contacts
|
| 928 |
)
|
| 929 |
|
|
|
|
| 930 |
contact_list = []
|
| 931 |
for contact in contacts:
|
| 932 |
contact_data = {
|
|
|
|
| 936 |
"title": contact.title,
|
| 937 |
"company": company_name,
|
| 938 |
"domain": company_domain,
|
| 939 |
+
"verified": True,
|
| 940 |
"source": "web_search_and_scraping"
|
| 941 |
}
|
| 942 |
contact_list.append(contact_data)
|
| 943 |
|
|
|
|
| 944 |
await self.mcp_registry.store.save_contact({
|
| 945 |
"id": contact.id,
|
| 946 |
"company_id": company_domain.replace(".", "_"),
|
|
|
|
| 955 |
"status": "success",
|
| 956 |
"contacts": contact_list,
|
| 957 |
"count": len(contact_list),
|
| 958 |
+
"message": f"Found {len(contact_list)} verified contacts at {company_name}",
|
| 959 |
+
"should_save_prospect": True
|
| 960 |
}
|
| 961 |
else:
|
| 962 |
return {
|
| 963 |
"status": "no_contacts_found",
|
| 964 |
"contacts": [],
|
| 965 |
"count": 0,
|
| 966 |
+
"message": f"No verified contacts found for {company_name}. Skip this prospect.",
|
| 967 |
+
"should_save_prospect": False
|
| 968 |
}
|
| 969 |
|
| 970 |
except Exception as e:
|
|
|
|
| 973 |
"status": "error",
|
| 974 |
"contacts": [],
|
| 975 |
"count": 0,
|
| 976 |
+
"message": f"Error searching for contacts: {str(e)}",
|
| 977 |
+
"should_save_prospect": False
|
| 978 |
}
|
| 979 |
|
| 980 |
# ============ STORE MCP SERVER ============
|
mcp/tools/definitions.py
CHANGED
|
@@ -201,9 +201,37 @@ MCP_TOOLS: List[Dict[str, Any]] = [
|
|
| 201 |
"required": ["fact_id", "company_id", "fact_type", "content"]
|
| 202 |
}
|
| 203 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 204 |
{
|
| 205 |
"name": "find_verified_contacts",
|
| 206 |
-
"description": "Find REAL verified decision-makers at a company
|
| 207 |
"input_schema": {
|
| 208 |
"type": "object",
|
| 209 |
"properties": {
|
|
|
|
| 201 |
"required": ["fact_id", "company_id", "fact_type", "content"]
|
| 202 |
}
|
| 203 |
},
|
| 204 |
+
{
|
| 205 |
+
"name": "discover_prospects_with_contacts",
|
| 206 |
+
"description": "Find prospect companies WITH verified decision-maker contacts. This tool searches for companies, finds their contacts, and ONLY saves prospects that have real verified contacts. It keeps searching until the target number of valid prospects is found. Use this as your PRIMARY tool for prospect discovery.",
|
| 207 |
+
"input_schema": {
|
| 208 |
+
"type": "object",
|
| 209 |
+
"properties": {
|
| 210 |
+
"client_company": {
|
| 211 |
+
"type": "string",
|
| 212 |
+
"description": "Your client company name (who you're finding prospects for)"
|
| 213 |
+
},
|
| 214 |
+
"client_industry": {
|
| 215 |
+
"type": "string",
|
| 216 |
+
"description": "Brief description of what the client does and their target market"
|
| 217 |
+
},
|
| 218 |
+
"target_prospects": {
|
| 219 |
+
"type": "integer",
|
| 220 |
+
"description": "Number of prospects WITH contacts to find (default: 3)",
|
| 221 |
+
"default": 3
|
| 222 |
+
},
|
| 223 |
+
"target_titles": {
|
| 224 |
+
"type": "array",
|
| 225 |
+
"items": {"type": "string"},
|
| 226 |
+
"description": "Job titles to search for (default: ['CEO', 'Founder', 'VP Sales', 'CTO'])"
|
| 227 |
+
}
|
| 228 |
+
},
|
| 229 |
+
"required": ["client_company", "client_industry"]
|
| 230 |
+
}
|
| 231 |
+
},
|
| 232 |
{
|
| 233 |
"name": "find_verified_contacts",
|
| 234 |
+
"description": "Find REAL verified decision-makers at a specific company. Searches LinkedIn, company websites, directories, press releases, and social media. Only returns contacts with verified email addresses found on the web.",
|
| 235 |
"input_schema": {
|
| 236 |
"type": "object",
|
| 237 |
"properties": {
|