muzakkirhussain011 commited on
Commit
819d651
·
1 Parent(s): ff6d640

Add application files

Browse files
Files changed (3) hide show
  1. app.py +82 -44
  2. mcp/agents/autonomous_agent_hf.py +103 -22
  3. 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 badge-researched">FOUND</span>
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
- IMPORTANT: You MUST use the tools to save data. Follow these steps for EACH prospect:
883
-
884
- 1. Use search_web to find {num_prospects} companies that would be ideal customers for {client_name}
885
- 2. For EACH company found, you MUST:
886
- a. Use save_prospect tool with these parameters:
887
- - prospect_id: unique ID like "prospect_1"
888
- - company_id: unique ID like "company_1"
889
- - company_name: the company name
890
- - company_domain: the company website domain
891
- - fit_score: 1-100 score
892
- - metadata: {{"summary": "company description", "industry": "their industry", "fit_reason": "why they're a good fit"}}
893
-
894
- b. Use save_contact tool for 2-3 decision makers with:
895
- - contact_id: unique ID like "contact_1"
896
- - company_id: same company_id as above
897
- - company_name: THE ACTUAL COMPANY NAME (e.g., "Acme Corp", NOT "company_1")
898
- - email: their email using format "[email protected]"
899
- - first_name: their first name
900
- - last_name: their last name
901
- - title: their job title (CEO, VP Sales, CTO, Marketing Director, etc.)
902
-
903
- c. Use send_email tool to draft a PERSONALIZED outreach email:
904
- - to: the contact email (use format: [email protected])
905
- - subject: Compelling subject mentioning BOTH {client_name} and the prospect company name
906
- - body: A personalized email that includes:
907
- * Greeting using the contact's first name
908
- * Specific reference to the prospect company's business/recent news
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
- IMPORTANT EMAIL GUIDELINES:
916
- - DO NOT write generic emails. Each email must be specific to the prospect.
917
- - Reference the prospect's actual business, industry, or recent activities
918
- - Explain why {client_name} is relevant to THEIR specific situation
919
- - Keep emails concise (3-4 paragraphs max)
920
 
921
- Focus on finding companies that:
922
- - Would benefit from {client_name}'s products/services
923
- - Are in industries that match {client_name}'s target market
 
 
924
 
925
- After processing all {num_prospects} prospects, provide a brief summary of what you found."""
 
 
 
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')}\n"
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 (Model Context Protocol) tools that let you:
176
- - Search the web for company information and news
177
- - Save prospects, companies, contacts, and facts to a database
178
- - Send emails and manage email threads
179
- - Schedule meetings and generate calendar invites
180
 
181
- CRITICAL: You MUST call tools to save data. Do NOT just describe what you would do - actually call the tools!
182
 
183
- TOOL CALL FORMAT - You must output valid JSON for each tool call:
184
- ```json
 
 
 
 
 
 
 
185
  {"prospect_id": "prospect_1", "company_id": "company_1", "company_name": "Acme Corp", "company_domain": "acme.com", "fit_score": 85}
186
- ```
187
 
188
- Example workflow:
189
- 1. Call search_web to find companies
190
- 2. For EACH company found, call save_prospect with JSON parameters
191
- 3. For EACH contact, call save_contact with JSON like:
192
- {"contact_id": "contact_1", "company_id": "company_1", "email": "[email protected]", "first_name": "John", "last_name": "Smith", "title": "CEO"}
193
- 4. Call send_email to draft outreach
 
 
194
 
195
- IMPORTANT RULES:
196
- - ALWAYS use tool calls with proper JSON parameters - never just describe the action
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 tool calls, provide a brief summary."""
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 (decision-maker) for a company.",
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": {