Spaces:
Runtime error
Runtime error
Commit
·
819d651
1
Parent(s):
ff6d640
Add application files
Browse files- app.py +82 -44
- mcp/agents/autonomous_agent_hf.py +103 -22
- mcp/tools/definitions.py +29 -1
app.py
CHANGED
|
@@ -589,8 +589,20 @@ def get_contacts_html() -> str:
|
|
| 589 |
</div>
|
| 590 |
"""
|
| 591 |
|
| 592 |
-
html = ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 593 |
for c in reversed(knowledge_base["contacts"]):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 594 |
html += f"""
|
| 595 |
<div class="prospect-card" style="padding: 16px 20px;">
|
| 596 |
<div style="display: flex; justify-content: space-between; align-items: center;">
|
|
@@ -598,9 +610,9 @@ def get_contacts_html() -> str:
|
|
| 598 |
<div style="font-weight: 600; color: var(--text-primary);">👤 {c.get("name", "Unknown")}</div>
|
| 599 |
<div style="font-size: 13px; color: var(--text-secondary); margin-top: 4px;">{c.get("title", "Unknown title")}</div>
|
| 600 |
<div style="font-size: 13px; color: var(--text-secondary);">🏢 {c.get("company", "Unknown company")}</div>
|
| 601 |
-
{f'<div style="font-size: 13px; color: var(--primary-blue); margin-top: 4px;">📧 {c.get("email")}</div>' if c.get("email") else ''}
|
| 602 |
</div>
|
| 603 |
-
<span class="prospect-card-badge
|
| 604 |
</div>
|
| 605 |
</div>
|
| 606 |
"""
|
|
@@ -879,50 +891,50 @@ async def discover_prospects(num_prospects: int, progress=gr.Progress()):
|
|
| 879 |
About {client_name}:
|
| 880 |
{client_info}
|
| 881 |
|
| 882 |
-
|
| 883 |
-
|
| 884 |
-
|
| 885 |
-
|
| 886 |
-
|
| 887 |
-
|
| 888 |
-
|
| 889 |
-
|
| 890 |
-
|
| 891 |
-
|
| 892 |
-
|
| 893 |
-
|
| 894 |
-
|
| 895 |
-
|
| 896 |
-
|
| 897 |
-
|
| 898 |
-
|
| 899 |
-
-
|
| 900 |
-
-
|
| 901 |
-
-
|
| 902 |
-
|
| 903 |
-
|
| 904 |
-
|
| 905 |
-
-
|
| 906 |
-
-
|
| 907 |
-
|
| 908 |
-
|
| 909 |
-
* Clear explanation of how {client_name} can help THIS specific company
|
| 910 |
-
* Mention their industry and specific challenges they face
|
| 911 |
-
* A concrete call-to-action (demo, call, meeting)
|
| 912 |
-
* Professional sign-off
|
| 913 |
- prospect_id: the prospect_id from step a
|
| 914 |
|
| 915 |
-
|
| 916 |
-
-
|
| 917 |
-
-
|
| 918 |
-
-
|
| 919 |
-
- Keep emails concise (3-4 paragraphs max)
|
| 920 |
|
| 921 |
-
|
| 922 |
-
-
|
| 923 |
-
-
|
|
|
|
|
|
|
| 924 |
|
| 925 |
-
After processing
|
|
|
|
|
|
|
|
|
|
| 926 |
|
| 927 |
prospects_found = []
|
| 928 |
contacts_found = []
|
|
@@ -1033,6 +1045,31 @@ After processing all {num_prospects} prospects, provide a brief summary of what
|
|
| 1033 |
else:
|
| 1034 |
output += f" ✅ Contact saved\n"
|
| 1035 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1036 |
elif tool == "send_email":
|
| 1037 |
output += f" ✅ Email drafted\n"
|
| 1038 |
# Mark prospect as having email drafted
|
|
@@ -1102,10 +1139,11 @@ After processing all {num_prospects} prospects, provide a brief summary of what
|
|
| 1102 |
|
| 1103 |
if contacts_found:
|
| 1104 |
output += "\n### 👥 Decision Makers Found\n\n"
|
|
|
|
| 1105 |
for c in contacts_found:
|
| 1106 |
output += f"- **{c.get('name', 'Unknown')}** - {c.get('title', 'Unknown')} at {c.get('company', 'Unknown')}\n"
|
| 1107 |
if c.get('email'):
|
| 1108 |
-
output += f" - Email: {c.get('email')}
|
| 1109 |
|
| 1110 |
if emails_drafted:
|
| 1111 |
output += "\n### ✉️ Emails Drafted\n\n"
|
|
|
|
| 589 |
</div>
|
| 590 |
"""
|
| 591 |
|
| 592 |
+
html = """
|
| 593 |
+
<div style="background: var(--info-bg, #d1ecf1); border: 1px solid var(--info-border, #bee5eb); border-radius: 8px; padding: 12px 16px; margin-bottom: 16px;">
|
| 594 |
+
<div style="font-size: 13px; color: var(--info-text, #0c5460);">
|
| 595 |
+
<strong>ℹ️ Contact Sources:</strong> Names are sourced from LinkedIn searches and company team pages.
|
| 596 |
+
Email addresses use common business formats. Contacts marked <strong>VERIFIED</strong> were found in public web sources.
|
| 597 |
+
</div>
|
| 598 |
+
</div>
|
| 599 |
+
"""
|
| 600 |
for c in reversed(knowledge_base["contacts"]):
|
| 601 |
+
is_verified = c.get("verified", False)
|
| 602 |
+
source = c.get("source", "unknown")
|
| 603 |
+
verified_badge = "VERIFIED" if is_verified else "ESTIMATED"
|
| 604 |
+
badge_class = "badge-engaged" if is_verified else "badge-researched"
|
| 605 |
+
source_text = "from web search" if is_verified else "estimated"
|
| 606 |
html += f"""
|
| 607 |
<div class="prospect-card" style="padding: 16px 20px;">
|
| 608 |
<div style="display: flex; justify-content: space-between; align-items: center;">
|
|
|
|
| 610 |
<div style="font-weight: 600; color: var(--text-primary);">👤 {c.get("name", "Unknown")}</div>
|
| 611 |
<div style="font-size: 13px; color: var(--text-secondary); margin-top: 4px;">{c.get("title", "Unknown title")}</div>
|
| 612 |
<div style="font-size: 13px; color: var(--text-secondary);">🏢 {c.get("company", "Unknown company")}</div>
|
| 613 |
+
{f'<div style="font-size: 13px; color: var(--primary-blue); margin-top: 4px;">📧 {c.get("email")} <span style="font-size: 11px; color: var(--text-secondary);">({source_text})</span></div>' if c.get("email") else ''}
|
| 614 |
</div>
|
| 615 |
+
<span class="prospect-card-badge {badge_class}">{verified_badge}</span>
|
| 616 |
</div>
|
| 617 |
</div>
|
| 618 |
"""
|
|
|
|
| 891 |
About {client_name}:
|
| 892 |
{client_info}
|
| 893 |
|
| 894 |
+
CRITICAL: Use the find_verified_contacts tool to get REAL contacts. NEVER make up contact names or emails.
|
| 895 |
+
|
| 896 |
+
WORKFLOW - Follow these steps:
|
| 897 |
+
|
| 898 |
+
STEP 1: Find prospect companies
|
| 899 |
+
- Use search_web to find {num_prospects} companies that would benefit from {client_name}'s services
|
| 900 |
+
- Look for REAL, INDEPENDENT companies (not acquired/merged ones)
|
| 901 |
+
- Example: Deliverr was acquired by Shopify - skip such companies
|
| 902 |
+
|
| 903 |
+
STEP 2: For EACH prospect company found:
|
| 904 |
+
a. Save the prospect using save_prospect tool:
|
| 905 |
+
{{"prospect_id": "prospect_1", "company_id": "company_1", "company_name": "ActualCompanyName", "company_domain": "actualcompany.com", "fit_score": 85, "metadata": {{"industry": "...", "fit_reason": "..."}}}}
|
| 906 |
+
|
| 907 |
+
b. Find REAL contacts using find_verified_contacts tool (THIS IS REQUIRED):
|
| 908 |
+
{{"company_name": "ActualCompanyName", "company_domain": "actualcompany.com", "target_titles": ["CEO", "Founder", "VP Sales", "CTO"], "max_contacts": 3}}
|
| 909 |
+
|
| 910 |
+
This tool will:
|
| 911 |
+
- Search LinkedIn for real executives
|
| 912 |
+
- Scrape company team/about pages
|
| 913 |
+
- Return ONLY verified names found on the web
|
| 914 |
+
- Automatically save contacts to the database
|
| 915 |
+
|
| 916 |
+
c. If find_verified_contacts returns contacts, draft email using send_email tool:
|
| 917 |
+
- Use the REAL contact info returned by find_verified_contacts
|
| 918 |
+
- to: contact email from the tool result
|
| 919 |
+
- subject: Reference {client_name} AND the prospect's business
|
| 920 |
+
- body: Personalized email with real facts
|
|
|
|
|
|
|
|
|
|
|
|
|
| 921 |
- prospect_id: the prospect_id from step a
|
| 922 |
|
| 923 |
+
d. If find_verified_contacts returns no contacts:
|
| 924 |
+
- Note: "No verified contacts found - leadership info not publicly available"
|
| 925 |
+
- Do NOT make up contacts
|
| 926 |
+
- Skip email for this prospect
|
|
|
|
| 927 |
|
| 928 |
+
IMPORTANT RULES:
|
| 929 |
+
- ALWAYS use find_verified_contacts to get contacts - NEVER invent names
|
| 930 |
+
- The find_verified_contacts tool does real web scraping to find actual executives
|
| 931 |
+
- If no contacts are found, that's OK - report it honestly
|
| 932 |
+
- Companies with no public leadership info should still be saved as prospects
|
| 933 |
|
| 934 |
+
After processing {num_prospects} prospects, provide summary:
|
| 935 |
+
- Prospects saved (with company names)
|
| 936 |
+
- Verified contacts found (or "none found" per company)
|
| 937 |
+
- Emails drafted (only for companies with verified contacts)"""
|
| 938 |
|
| 939 |
prospects_found = []
|
| 940 |
contacts_found = []
|
|
|
|
| 1045 |
else:
|
| 1046 |
output += f" ✅ Contact saved\n"
|
| 1047 |
|
| 1048 |
+
elif tool == "find_verified_contacts":
|
| 1049 |
+
# Handle verified contacts from the enhanced contact finder
|
| 1050 |
+
if isinstance(result, dict):
|
| 1051 |
+
status = result.get("status", "")
|
| 1052 |
+
found_contacts = result.get("contacts", [])
|
| 1053 |
+
message = result.get("message", "")
|
| 1054 |
+
|
| 1055 |
+
if status == "success" and found_contacts:
|
| 1056 |
+
output += f" ✅ Found {len(found_contacts)} verified contacts:\n"
|
| 1057 |
+
for c in found_contacts:
|
| 1058 |
+
contact_data = {
|
| 1059 |
+
"name": c.get("name", "Unknown"),
|
| 1060 |
+
"email": c.get("email", ""),
|
| 1061 |
+
"title": c.get("title", ""),
|
| 1062 |
+
"company": c.get("company", current_prospect_name or ""),
|
| 1063 |
+
"verified": c.get("verified", True),
|
| 1064 |
+
"source": c.get("source", "web_search")
|
| 1065 |
+
}
|
| 1066 |
+
contacts_found.append(contact_data)
|
| 1067 |
+
output += f" • {contact_data['name']} ({contact_data['title']}) - {contact_data['email']}\n"
|
| 1068 |
+
elif status == "no_contacts_found":
|
| 1069 |
+
output += f" ℹ️ {message}\n"
|
| 1070 |
+
else:
|
| 1071 |
+
output += f" ⚠️ Contact search: {message}\n"
|
| 1072 |
+
|
| 1073 |
elif tool == "send_email":
|
| 1074 |
output += f" ✅ Email drafted\n"
|
| 1075 |
# Mark prospect as having email drafted
|
|
|
|
| 1139 |
|
| 1140 |
if contacts_found:
|
| 1141 |
output += "\n### 👥 Decision Makers Found\n\n"
|
| 1142 |
+
output += "> ⚠️ **Note:** Names are from web searches. Emails are *estimated* ([email protected] format). Please verify before outreach.\n\n"
|
| 1143 |
for c in contacts_found:
|
| 1144 |
output += f"- **{c.get('name', 'Unknown')}** - {c.get('title', 'Unknown')} at {c.get('company', 'Unknown')}\n"
|
| 1145 |
if c.get('email'):
|
| 1146 |
+
output += f" - Email: {c.get('email')} *(estimated)*\n"
|
| 1147 |
|
| 1148 |
if emails_drafted:
|
| 1149 |
output += "\n### ✉️ Emails Drafted\n\n"
|
mcp/agents/autonomous_agent_hf.py
CHANGED
|
@@ -172,33 +172,38 @@ class AutonomousMCPAgentHF:
|
|
| 172 |
# System prompt for the agent
|
| 173 |
system_prompt = """You are an autonomous AI agent for B2B sales automation.
|
| 174 |
|
| 175 |
-
You have access to MCP
|
| 176 |
-
- Search the web for company information
|
| 177 |
-
- Save
|
| 178 |
-
-
|
| 179 |
-
-
|
| 180 |
|
| 181 |
-
CRITICAL:
|
| 182 |
|
| 183 |
-
|
| 184 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 185 |
{"prospect_id": "prospect_1", "company_id": "company_1", "company_name": "Acme Corp", "company_domain": "acme.com", "fit_score": 85}
|
| 186 |
-
```
|
| 187 |
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
|
|
|
|
|
|
| 194 |
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
- Use realistic email formats: [email protected]
|
| 198 |
-
- Each tool call must have all required fields
|
| 199 |
-
- Do NOT hallucinate or make up contact details without calling save_contact tool
|
| 200 |
|
| 201 |
-
After completing
|
| 202 |
|
| 203 |
# Initialize conversation
|
| 204 |
messages = [
|
|
@@ -567,13 +572,18 @@ After completing tool calls, provide a brief summary."""
|
|
| 567 |
return None
|
| 568 |
keys = set(params.keys())
|
| 569 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 570 |
# Check for save_prospect patterns
|
| 571 |
if "prospect_id" in keys or ("company_name" in keys and "fit_score" in keys):
|
| 572 |
return "save_prospect"
|
| 573 |
# Check for save_company patterns
|
| 574 |
if "company_id" in keys and ("name" in keys or "domain" in keys) and "prospect_id" not in keys:
|
| 575 |
return "save_company"
|
| 576 |
-
# Check for save_contact patterns
|
| 577 |
if "contact_id" in keys or ("email" in keys and ("first_name" in keys or "last_name" in keys)):
|
| 578 |
return "save_contact"
|
| 579 |
# Check for send_email patterns
|
|
@@ -715,6 +725,77 @@ After completing tool calls, provide a brief summary."""
|
|
| 715 |
"count": len(results[:max_results])
|
| 716 |
}
|
| 717 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 718 |
# ============ STORE MCP SERVER ============
|
| 719 |
elif tool_name == "save_prospect":
|
| 720 |
prospect_data = {
|
|
|
|
| 172 |
# System prompt for the agent
|
| 173 |
system_prompt = """You are an autonomous AI agent for B2B sales automation.
|
| 174 |
|
| 175 |
+
You have access to MCP tools including:
|
| 176 |
+
- search_web: Search the web for company information
|
| 177 |
+
- save_prospect: Save a prospect company to the database
|
| 178 |
+
- find_verified_contacts: Find REAL decision-makers using LinkedIn search and web scraping (USE THIS!)
|
| 179 |
+
- send_email: Draft outreach emails
|
| 180 |
|
| 181 |
+
CRITICAL: Use find_verified_contacts to get REAL contacts. NEVER make up names or emails!
|
| 182 |
|
| 183 |
+
REQUIRED WORKFLOW for each prospect:
|
| 184 |
+
1. search_web to find prospect companies
|
| 185 |
+
2. save_prospect with company info
|
| 186 |
+
3. find_verified_contacts to get REAL executives (this does LinkedIn + web scraping)
|
| 187 |
+
4. send_email ONLY using contacts returned by find_verified_contacts
|
| 188 |
+
|
| 189 |
+
TOOL CALL FORMAT - output valid JSON:
|
| 190 |
+
|
| 191 |
+
For prospects:
|
| 192 |
{"prospect_id": "prospect_1", "company_id": "company_1", "company_name": "Acme Corp", "company_domain": "acme.com", "fit_score": 85}
|
|
|
|
| 193 |
|
| 194 |
+
For finding REAL contacts (REQUIRED):
|
| 195 |
+
{"company_name": "Acme Corp", "company_domain": "acme.com", "target_titles": ["CEO", "Founder", "VP Sales"], "max_contacts": 3}
|
| 196 |
+
|
| 197 |
+
The find_verified_contacts tool will:
|
| 198 |
+
- Search LinkedIn for real executives
|
| 199 |
+
- Scrape company team/about pages
|
| 200 |
+
- Return ONLY real names found on the web
|
| 201 |
+
- Automatically save verified contacts
|
| 202 |
|
| 203 |
+
If find_verified_contacts returns no contacts, report: "No verified contacts found for [Company]"
|
| 204 |
+
Do NOT invent contacts - only use what find_verified_contacts returns.
|
|
|
|
|
|
|
|
|
|
| 205 |
|
| 206 |
+
After completing, summarize prospects and verified contacts found."""
|
| 207 |
|
| 208 |
# Initialize conversation
|
| 209 |
messages = [
|
|
|
|
| 572 |
return None
|
| 573 |
keys = set(params.keys())
|
| 574 |
|
| 575 |
+
# Check for find_verified_contacts patterns (PRIORITY - use this instead of save_contact)
|
| 576 |
+
if "company_name" in keys and "company_domain" in keys and "target_titles" in keys:
|
| 577 |
+
return "find_verified_contacts"
|
| 578 |
+
if "company_name" in keys and "company_domain" in keys and "max_contacts" in keys:
|
| 579 |
+
return "find_verified_contacts"
|
| 580 |
# Check for save_prospect patterns
|
| 581 |
if "prospect_id" in keys or ("company_name" in keys and "fit_score" in keys):
|
| 582 |
return "save_prospect"
|
| 583 |
# Check for save_company patterns
|
| 584 |
if "company_id" in keys and ("name" in keys or "domain" in keys) and "prospect_id" not in keys:
|
| 585 |
return "save_company"
|
| 586 |
+
# Check for save_contact patterns (only for contacts returned by find_verified_contacts)
|
| 587 |
if "contact_id" in keys or ("email" in keys and ("first_name" in keys or "last_name" in keys)):
|
| 588 |
return "save_contact"
|
| 589 |
# Check for send_email patterns
|
|
|
|
| 725 |
"count": len(results[:max_results])
|
| 726 |
}
|
| 727 |
|
| 728 |
+
# ============ VERIFIED CONTACT FINDER ============
|
| 729 |
+
elif tool_name == "find_verified_contacts":
|
| 730 |
+
from services.enhanced_contact_finder import EnhancedContactFinder
|
| 731 |
+
|
| 732 |
+
company_name = tool_input["company_name"]
|
| 733 |
+
company_domain = tool_input["company_domain"]
|
| 734 |
+
target_titles = tool_input.get("target_titles", ["CEO", "Founder", "VP Sales", "CTO", "Head of Sales"])
|
| 735 |
+
max_contacts = tool_input.get("max_contacts", 3)
|
| 736 |
+
|
| 737 |
+
logger.info(f"Finding verified contacts for {company_name} ({company_domain})")
|
| 738 |
+
|
| 739 |
+
# Use the EnhancedContactFinder which does real web scraping and LinkedIn search
|
| 740 |
+
contact_finder = EnhancedContactFinder(mcp_registry=self.mcp_registry)
|
| 741 |
+
|
| 742 |
+
try:
|
| 743 |
+
contacts = await contact_finder.find_real_contacts(
|
| 744 |
+
company_name=company_name,
|
| 745 |
+
domain=company_domain,
|
| 746 |
+
target_titles=target_titles,
|
| 747 |
+
max_contacts=max_contacts
|
| 748 |
+
)
|
| 749 |
+
|
| 750 |
+
# Convert contacts to serializable format
|
| 751 |
+
contact_list = []
|
| 752 |
+
for contact in contacts:
|
| 753 |
+
contact_data = {
|
| 754 |
+
"id": contact.id,
|
| 755 |
+
"name": contact.name,
|
| 756 |
+
"email": contact.email,
|
| 757 |
+
"title": contact.title,
|
| 758 |
+
"company": company_name,
|
| 759 |
+
"domain": company_domain,
|
| 760 |
+
"verified": True, # These come from real web searches
|
| 761 |
+
"source": "web_search_and_scraping"
|
| 762 |
+
}
|
| 763 |
+
contact_list.append(contact_data)
|
| 764 |
+
|
| 765 |
+
# Also save to store automatically
|
| 766 |
+
await self.mcp_registry.store.save_contact({
|
| 767 |
+
"id": contact.id,
|
| 768 |
+
"company_id": company_domain.replace(".", "_"),
|
| 769 |
+
"email": contact.email,
|
| 770 |
+
"first_name": contact.name.split()[0] if contact.name else "",
|
| 771 |
+
"last_name": contact.name.split()[-1] if contact.name and len(contact.name.split()) > 1 else "",
|
| 772 |
+
"title": contact.title
|
| 773 |
+
})
|
| 774 |
+
|
| 775 |
+
if contact_list:
|
| 776 |
+
return {
|
| 777 |
+
"status": "success",
|
| 778 |
+
"contacts": contact_list,
|
| 779 |
+
"count": len(contact_list),
|
| 780 |
+
"message": f"Found {len(contact_list)} verified contacts at {company_name}"
|
| 781 |
+
}
|
| 782 |
+
else:
|
| 783 |
+
return {
|
| 784 |
+
"status": "no_contacts_found",
|
| 785 |
+
"contacts": [],
|
| 786 |
+
"count": 0,
|
| 787 |
+
"message": f"No verified contacts found for {company_name}. Leadership information may not be publicly available."
|
| 788 |
+
}
|
| 789 |
+
|
| 790 |
+
except Exception as e:
|
| 791 |
+
logger.error(f"Error finding contacts for {company_name}: {str(e)}")
|
| 792 |
+
return {
|
| 793 |
+
"status": "error",
|
| 794 |
+
"contacts": [],
|
| 795 |
+
"count": 0,
|
| 796 |
+
"message": f"Error searching for contacts: {str(e)}"
|
| 797 |
+
}
|
| 798 |
+
|
| 799 |
# ============ STORE MCP SERVER ============
|
| 800 |
elif tool_name == "save_prospect":
|
| 801 |
prospect_data = {
|
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": "save_contact",
|
| 206 |
-
"description": "Save a contact person
|
| 207 |
"input_schema": {
|
| 208 |
"type": "object",
|
| 209 |
"properties": {
|
|
|
|
| 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 using LinkedIn search, team page scraping, and web research. This tool searches the web to find actual executives - DO NOT make up contact names. Use this INSTEAD of save_contact to get real contacts.",
|
| 207 |
+
"input_schema": {
|
| 208 |
+
"type": "object",
|
| 209 |
+
"properties": {
|
| 210 |
+
"company_name": {
|
| 211 |
+
"type": "string",
|
| 212 |
+
"description": "The company name to find contacts for"
|
| 213 |
+
},
|
| 214 |
+
"company_domain": {
|
| 215 |
+
"type": "string",
|
| 216 |
+
"description": "The company website domain (e.g., 'acme.com')"
|
| 217 |
+
},
|
| 218 |
+
"target_titles": {
|
| 219 |
+
"type": "array",
|
| 220 |
+
"items": {"type": "string"},
|
| 221 |
+
"description": "List of job titles to search for (e.g., ['CEO', 'VP Sales', 'CTO'])"
|
| 222 |
+
},
|
| 223 |
+
"max_contacts": {
|
| 224 |
+
"type": "integer",
|
| 225 |
+
"description": "Maximum number of contacts to find (default: 3)",
|
| 226 |
+
"default": 3
|
| 227 |
+
}
|
| 228 |
+
},
|
| 229 |
+
"required": ["company_name", "company_domain"]
|
| 230 |
+
}
|
| 231 |
+
},
|
| 232 |
{
|
| 233 |
"name": "save_contact",
|
| 234 |
+
"description": "Save a contact person that was found by find_verified_contacts. Only use this for contacts returned by find_verified_contacts - NEVER make up contact information.",
|
| 235 |
"input_schema": {
|
| 236 |
"type": "object",
|
| 237 |
"properties": {
|