index.ts•38.7 kB
#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
Tool,
} from "@modelcontextprotocol/sdk/types.js";
import axios, { AxiosInstance } from "axios";
import dotenv from "dotenv";
dotenv.config();
const APOLLO_API_KEY = process.env.APOLLO_API_KEY;
const APOLLO_BASE_URL = "https://api.apollo.io/v1";
if (!APOLLO_API_KEY) {
console.error("Error: APOLLO_API_KEY environment variable is required");
process.exit(1);
}
class ApolloMCPServer {
private server: Server;
private axiosInstance: AxiosInstance;
constructor() {
this.server = new Server(
{
name: "apollo-mcp-server",
version: "1.0.0",
},
{
capabilities: {
tools: {},
},
}
);
this.axiosInstance = axios.create({
baseURL: APOLLO_BASE_URL,
headers: {
"Content-Type": "application/json",
"Cache-Control": "no-cache",
"X-Api-Key": APOLLO_API_KEY,
},
});
this.setupHandlers();
}
private setupHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: this.getTools(),
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case "search_people":
return await this.searchPeople(args);
case "search_organizations":
return await this.searchOrganizations(args);
case "enrich_person":
return await this.enrichPerson(args);
case "enrich_organization":
return await this.enrichOrganization(args);
case "find_email":
return await this.findEmail(args);
case "list_sequences":
return await this.listSequences(args);
case "get_sequence":
return await this.getSequence(args);
case "analyze_sequence":
return await this.analyzeSequence(args);
case "add_to_sequence":
return await this.addToSequence(args);
case "remove_from_sequence":
return await this.removeFromSequence(args);
case "get_lists":
return await this.getLists(args);
case "get_list_contacts":
return await this.getListContacts(args);
case "analyze_list":
return await this.analyzeList(args);
case "create_contact":
return await this.createContact(args);
case "update_contact":
return await this.updateContact(args);
case "create_account":
return await this.createAccount(args);
case "get_account":
return await this.getAccount(args);
case "search_job_postings":
return await this.searchJobPostings(args);
case "get_person_activity":
return await this.getPersonActivity(args);
default:
throw new Error(`Unknown tool: ${name}`);
}
} catch (error: any) {
return {
content: [
{
type: "text",
text: `Error: ${error.message}\n${error.response?.data ? JSON.stringify(error.response.data, null, 2) : ""}`,
},
],
};
}
});
}
private getTools(): Tool[] {
return [
{
name: "search_people",
description:
"Search for people/contacts in Apollo's database with advanced filters. Use this to find prospects, leads, or specific individuals based on criteria like job title, company, location, industry, seniority, etc.",
inputSchema: {
type: "object",
properties: {
q_keywords: {
type: "string",
description: "Search keywords for person name or title",
},
person_titles: {
type: "array",
items: { type: "string" },
description: 'Job titles (e.g., ["CEO", "CTO", "VP Sales"])',
},
person_seniorities: {
type: "array",
items: { type: "string" },
description:
'Seniority levels: "senior", "manager", "director", "vp", "c_suite", "owner", "partner"',
},
organization_ids: {
type: "array",
items: { type: "string" },
description: "Filter by specific organization IDs",
},
organization_locations: {
type: "array",
items: { type: "string" },
description: 'Locations (e.g., ["San Francisco, CA", "New York, NY"])',
},
organization_industry_tag_ids: {
type: "array",
items: { type: "string" },
description: "Industry tags to filter by",
},
person_locations: {
type: "array",
items: { type: "string" },
description: "Person locations",
},
page: {
type: "number",
description: "Page number (default: 1)",
},
per_page: {
type: "number",
description: "Results per page (default: 25, max: 100)",
},
},
},
},
{
name: "search_organizations",
description:
"Search for companies/organizations in Apollo's database. Filter by industry, size, location, revenue, technology, and more. Great for building targeted account lists.",
inputSchema: {
type: "object",
properties: {
q_organization_name: {
type: "string",
description: "Organization name search",
},
organization_locations: {
type: "array",
items: { type: "string" },
description: 'Locations (e.g., ["San Francisco, CA"])',
},
organization_industry_tag_ids: {
type: "array",
items: { type: "string" },
description: "Industry tag IDs",
},
organization_num_employees_ranges: {
type: "array",
items: { type: "string" },
description:
'Employee count ranges: "1-10", "11-50", "51-200", "201-500", "501-1000", "1001-5000", "5001-10000", "10001+"',
},
revenue_range: {
type: "object",
properties: {
min: { type: "number" },
max: { type: "number" },
},
description: "Revenue range filter",
},
organization_keywords: {
type: "array",
items: { type: "string" },
description: "Keywords to search in organization data",
},
page: {
type: "number",
description: "Page number (default: 1)",
},
per_page: {
type: "number",
description: "Results per page (default: 25, max: 100)",
},
},
},
},
{
name: "enrich_person",
description:
"Enrich a person's data with email, phone, social profiles, employment info, and more. Provide either email, name+domain, or LinkedIn URL.",
inputSchema: {
type: "object",
properties: {
email: {
type: "string",
description: "Person's email address",
},
first_name: {
type: "string",
description: "First name (use with domain)",
},
last_name: {
type: "string",
description: "Last name (use with domain)",
},
domain: {
type: "string",
description: "Company domain (e.g., apollo.io)",
},
linkedin_url: {
type: "string",
description: "LinkedIn profile URL",
},
reveal_personal_emails: {
type: "boolean",
description: "Include personal email addresses",
},
},
},
},
{
name: "enrich_organization",
description:
"Enrich an organization's data with detailed company information, employee count, revenue, technologies used, funding, and more. Provide domain name.",
inputSchema: {
type: "object",
properties: {
domain: {
type: "string",
description: "Company domain (e.g., apollo.io)",
},
},
required: ["domain"],
},
},
{
name: "find_email",
description:
"Find and verify email addresses for a person. Provide name and company domain or LinkedIn URL.",
inputSchema: {
type: "object",
properties: {
first_name: {
type: "string",
description: "First name",
},
last_name: {
type: "string",
description: "Last name",
},
domain: {
type: "string",
description: "Company domain",
},
linkedin_url: {
type: "string",
description: "LinkedIn profile URL",
},
},
},
},
{
name: "list_sequences",
description:
"List all email sequences in your Apollo account. Sequences are automated email campaigns.",
inputSchema: {
type: "object",
properties: {
page: {
type: "number",
description: "Page number",
},
},
},
},
{
name: "get_sequence",
description:
"Get detailed information about a specific sequence including steps, stats, and settings.",
inputSchema: {
type: "object",
properties: {
id: {
type: "string",
description: "Sequence ID",
},
},
required: ["id"],
},
},
{
name: "analyze_sequence",
description:
"Analyze a sequence's performance with detailed metrics: open rates, reply rates, bounce rates, contacts added, active contacts, and step-by-step analytics.",
inputSchema: {
type: "object",
properties: {
id: {
type: "string",
description: "Sequence ID to analyze",
},
},
required: ["id"],
},
},
{
name: "add_to_sequence",
description:
"Add contacts to a sequence. Provide sequence ID and contact email addresses or contact IDs.",
inputSchema: {
type: "object",
properties: {
sequence_id: {
type: "string",
description: "Sequence ID",
},
contact_ids: {
type: "array",
items: { type: "string" },
description: "Array of contact IDs to add",
},
contact_emails: {
type: "array",
items: { type: "string" },
description: "Array of contact emails to add",
},
mailbox_id: {
type: "string",
description: "Mailbox ID to send from (optional)",
},
},
required: ["sequence_id"],
},
},
{
name: "remove_from_sequence",
description:
"Remove contacts from a sequence. Provide sequence ID and contact IDs.",
inputSchema: {
type: "object",
properties: {
sequence_id: {
type: "string",
description: "Sequence ID",
},
contact_ids: {
type: "array",
items: { type: "string" },
description: "Array of contact IDs to remove",
},
},
required: ["sequence_id", "contact_ids"],
},
},
{
name: "get_lists",
description:
"Get all contact lists in your Apollo account. Lists are collections of saved contacts.",
inputSchema: {
type: "object",
properties: {
page: {
type: "number",
description: "Page number",
},
},
},
},
{
name: "get_list_contacts",
description:
"Scrape/retrieve all contacts from a specific list with full details including emails, titles, companies, etc.",
inputSchema: {
type: "object",
properties: {
id: {
type: "string",
description: "List ID",
},
page: {
type: "number",
description: "Page number (default: 1)",
},
per_page: {
type: "number",
description: "Results per page (default: 100)",
},
},
required: ["id"],
},
},
{
name: "analyze_list",
description:
"Analyze a contact list with detailed breakdown: total contacts, job titles distribution, seniority levels, companies, locations, industries, and data completeness metrics.",
inputSchema: {
type: "object",
properties: {
id: {
type: "string",
description: "List ID to analyze",
},
},
required: ["id"],
},
},
{
name: "create_contact",
description:
"Create a new contact in Apollo with details like name, email, title, company, etc.",
inputSchema: {
type: "object",
properties: {
first_name: {
type: "string",
description: "First name",
},
last_name: {
type: "string",
description: "Last name",
},
email: {
type: "string",
description: "Email address",
},
title: {
type: "string",
description: "Job title",
},
organization_name: {
type: "string",
description: "Company name",
},
linkedin_url: {
type: "string",
description: "LinkedIn URL",
},
phone_numbers: {
type: "array",
items: { type: "string" },
description: "Phone numbers",
},
},
required: ["first_name", "last_name"],
},
},
{
name: "update_contact",
description: "Update an existing contact's information in Apollo.",
inputSchema: {
type: "object",
properties: {
id: {
type: "string",
description: "Contact ID",
},
first_name: {
type: "string",
description: "First name",
},
last_name: {
type: "string",
description: "Last name",
},
email: {
type: "string",
description: "Email address",
},
title: {
type: "string",
description: "Job title",
},
linkedin_url: {
type: "string",
description: "LinkedIn URL",
},
},
required: ["id"],
},
},
{
name: "create_account",
description:
"Create a new account/organization in Apollo with company details.",
inputSchema: {
type: "object",
properties: {
name: {
type: "string",
description: "Company name",
},
domain: {
type: "string",
description: "Company domain",
},
phone_number: {
type: "string",
description: "Company phone",
},
website_url: {
type: "string",
description: "Website URL",
},
},
required: ["name"],
},
},
{
name: "get_account",
description:
"Get detailed information about an account/organization by ID or domain.",
inputSchema: {
type: "object",
properties: {
id: {
type: "string",
description: "Account ID",
},
domain: {
type: "string",
description: "Company domain",
},
},
},
},
{
name: "search_job_postings",
description:
"Search for job postings to identify companies that are hiring and find buying signals.",
inputSchema: {
type: "object",
properties: {
q_keywords: {
type: "string",
description: "Keywords to search in job postings",
},
organization_ids: {
type: "array",
items: { type: "string" },
description: "Filter by organization IDs",
},
page: {
type: "number",
description: "Page number",
},
},
},
},
{
name: "get_person_activity",
description:
"Get activity history and engagement data for a specific person/contact.",
inputSchema: {
type: "object",
properties: {
id: {
type: "string",
description: "Person/Contact ID",
},
},
required: ["id"],
},
},
];
}
// People Search
private async searchPeople(args: any) {
const response = await this.axiosInstance.post("/mixed_people/search", args);
const people = response.data.people || [];
const pagination = response.data.pagination || {};
let result = `Found ${pagination.total_entries || people.length} people\n`;
result += `Page ${pagination.page || 1} of ${pagination.total_pages || 1}\n\n`;
people.forEach((person: any, index: number) => {
result += `${index + 1}. ${person.first_name} ${person.last_name}\n`;
result += ` ID: ${person.id}\n`;
result += ` Title: ${person.title || "N/A"}\n`;
result += ` Company: ${person.organization?.name || "N/A"}\n`;
result += ` Location: ${person.city ? `${person.city}, ${person.state || ""}` : "N/A"}\n`;
result += ` Email: ${person.email || "N/A"}\n`;
result += ` LinkedIn: ${person.linkedin_url || "N/A"}\n`;
result += ` Seniority: ${person.seniority || "N/A"}\n\n`;
});
return {
content: [
{
type: "text",
text: result,
},
],
};
}
// Organization Search
private async searchOrganizations(args: any) {
const response = await this.axiosInstance.post("/mixed_companies/search", args);
const organizations = response.data.organizations || [];
const pagination = response.data.pagination || {};
let result = `Found ${pagination.total_entries || organizations.length} organizations\n`;
result += `Page ${pagination.page || 1} of ${pagination.total_pages || 1}\n\n`;
organizations.forEach((org: any, index: number) => {
result += `${index + 1}. ${org.name}\n`;
result += ` ID: ${org.id}\n`;
result += ` Domain: ${org.website_url || org.primary_domain || "N/A"}\n`;
result += ` Industry: ${org.industry || "N/A"}\n`;
result += ` Employees: ${org.estimated_num_employees || "N/A"}\n`;
result += ` Location: ${org.city ? `${org.city}, ${org.state || org.country}` : "N/A"}\n`;
result += ` Revenue: ${org.annual_revenue ? `$${org.annual_revenue}` : "N/A"}\n`;
result += ` Phone: ${org.phone || "N/A"}\n\n`;
});
return {
content: [
{
type: "text",
text: result,
},
],
};
}
// Enrich Person
private async enrichPerson(args: any) {
const response = await this.axiosInstance.post("/people/match", args);
const person = response.data.person;
if (!person) {
return {
content: [
{
type: "text",
text: "No person found with the provided information.",
},
],
};
}
let result = `Person Enrichment Results:\n\n`;
result += `Name: ${person.first_name} ${person.last_name}\n`;
result += `ID: ${person.id}\n`;
result += `Title: ${person.title || "N/A"}\n`;
result += `Email: ${person.email || "N/A"}\n`;
result += `Phone: ${person.phone_numbers?.[0]?.raw_number || "N/A"}\n`;
result += `LinkedIn: ${person.linkedin_url || "N/A"}\n`;
result += `Location: ${person.city ? `${person.city}, ${person.state || ""}` : "N/A"}\n`;
result += `Seniority: ${person.seniority || "N/A"}\n\n`;
if (person.organization) {
result += `Company Information:\n`;
result += ` Name: ${person.organization.name}\n`;
result += ` Domain: ${person.organization.website_url || "N/A"}\n`;
result += ` Industry: ${person.organization.industry || "N/A"}\n`;
result += ` Employees: ${person.organization.estimated_num_employees || "N/A"}\n`;
}
return {
content: [
{
type: "text",
text: result,
},
],
};
}
// Enrich Organization
private async enrichOrganization(args: any) {
const response = await this.axiosInstance.post("/organizations/enrich", args);
const org = response.data.organization;
if (!org) {
return {
content: [
{
type: "text",
text: "No organization found with the provided domain.",
},
],
};
}
let result = `Organization Enrichment Results:\n\n`;
result += `Name: ${org.name}\n`;
result += `ID: ${org.id}\n`;
result += `Domain: ${org.website_url || org.primary_domain || "N/A"}\n`;
result += `Industry: ${org.industry || "N/A"}\n`;
result += `Description: ${org.short_description || "N/A"}\n`;
result += `Founded: ${org.founded_year || "N/A"}\n`;
result += `Employees: ${org.estimated_num_employees || "N/A"}\n`;
result += `Revenue: ${org.annual_revenue ? `$${org.annual_revenue}` : "N/A"}\n`;
result += `Location: ${org.city ? `${org.city}, ${org.state || org.country}` : "N/A"}\n`;
result += `Phone: ${org.phone || "N/A"}\n`;
result += `LinkedIn: ${org.linkedin_url || "N/A"}\n`;
result += `Facebook: ${org.facebook_url || "N/A"}\n`;
result += `Twitter: ${org.twitter_url || "N/A"}\n\n`;
if (org.technologies) {
result += `Technologies Used: ${org.technologies.join(", ")}\n`;
}
return {
content: [
{
type: "text",
text: result,
},
],
};
}
// Find Email
private async findEmail(args: any) {
const response = await this.axiosInstance.post("/people/match", args);
const person = response.data.person;
if (!person) {
return {
content: [
{
type: "text",
text: "No email found for the provided information.",
},
],
};
}
let result = `Email Found:\n\n`;
result += `Name: ${person.first_name} ${person.last_name}\n`;
result += `Email: ${person.email || "Not available"}\n`;
result += `Status: ${person.email_status || "N/A"}\n`;
result += `Company: ${person.organization?.name || "N/A"}\n`;
result += `Title: ${person.title || "N/A"}\n`;
return {
content: [
{
type: "text",
text: result,
},
],
};
}
// List Sequences
private async listSequences(args: any) {
const response = await this.axiosInstance.get("/emailer_campaigns", {
params: args,
});
const sequences = response.data.emailer_campaigns || [];
let result = `Email Sequences (${sequences.length}):\n\n`;
sequences.forEach((seq: any, index: number) => {
result += `${index + 1}. ${seq.name}\n`;
result += ` ID: ${seq.id}\n`;
result += ` Status: ${seq.active ? "Active" : "Inactive"}\n`;
result += ` Contacts: ${seq.num_steps || 0} steps\n`;
result += ` Created: ${seq.created_at ? new Date(seq.created_at).toLocaleDateString() : "N/A"}\n\n`;
});
return {
content: [
{
type: "text",
text: result,
},
],
};
}
// Get Sequence
private async getSequence(args: any) {
const response = await this.axiosInstance.get(`/emailer_campaigns/${args.id}`);
const seq = response.data.emailer_campaign;
let result = `Sequence Details:\n\n`;
result += `Name: ${seq.name}\n`;
result += `ID: ${seq.id}\n`;
result += `Status: ${seq.active ? "Active" : "Inactive"}\n`;
result += `Created: ${seq.created_at ? new Date(seq.created_at).toLocaleDateString() : "N/A"}\n`;
result += `Steps: ${seq.num_steps || 0}\n\n`;
if (seq.emailer_steps && seq.emailer_steps.length > 0) {
result += `Sequence Steps:\n`;
seq.emailer_steps.forEach((step: any, index: number) => {
result += `\nStep ${index + 1}:\n`;
result += ` Type: ${step.type || "Email"}\n`;
result += ` Wait: ${step.wait_time || 0} days\n`;
result += ` Subject: ${step.subject || "N/A"}\n`;
});
}
return {
content: [
{
type: "text",
text: result,
},
],
};
}
// Analyze Sequence
private async analyzeSequence(args: any) {
const response = await this.axiosInstance.get(`/emailer_campaigns/${args.id}`);
const seq = response.data.emailer_campaign;
let result = `Sequence Analysis: ${seq.name}\n\n`;
result += `=== Overview ===\n`;
result += `ID: ${seq.id}\n`;
result += `Status: ${seq.active ? "Active" : "Inactive"}\n`;
result += `Created: ${seq.created_at ? new Date(seq.created_at).toLocaleDateString() : "N/A"}\n`;
result += `Total Steps: ${seq.num_steps || 0}\n\n`;
result += `=== Performance Metrics ===\n`;
result += `Total Contacts Added: ${seq.unique_scheduled || 0}\n`;
result += `Active Contacts: ${seq.num_contacted_people || 0}\n`;
result += `Bounced: ${seq.bounce_rate ? `${(seq.bounce_rate * 100).toFixed(2)}%` : "0%"}\n`;
result += `Replied: ${seq.reply_rate ? `${(seq.reply_rate * 100).toFixed(2)}%` : "0%"}\n`;
result += `Opened: ${seq.open_rate ? `${(seq.open_rate * 100).toFixed(2)}%` : "0%"}\n`;
result += `Clicked: ${seq.click_rate ? `${(seq.click_rate * 100).toFixed(2)}%` : "0%"}\n\n`;
if (seq.emailer_steps && seq.emailer_steps.length > 0) {
result += `=== Step-by-Step Breakdown ===\n`;
seq.emailer_steps.forEach((step: any, index: number) => {
result += `\nStep ${index + 1}: ${step.type || "Email"}\n`;
result += ` Subject: ${step.subject || "N/A"}\n`;
result += ` Wait Time: ${step.wait_time || 0} days\n`;
result += ` Max Emails: ${step.max_emails_per_day || "Unlimited"}\n`;
});
}
result += `\n=== Insights ===\n`;
if (seq.reply_rate && seq.reply_rate > 0.1) {
result += `✓ Strong reply rate - this sequence is performing well\n`;
} else if (seq.reply_rate && seq.reply_rate < 0.05) {
result += `⚠ Low reply rate - consider reviewing messaging and targeting\n`;
}
if (seq.bounce_rate && seq.bounce_rate > 0.05) {
result += `⚠ High bounce rate - verify email quality\n`;
}
if (seq.open_rate && seq.open_rate < 0.3) {
result += `⚠ Low open rate - test different subject lines\n`;
}
return {
content: [
{
type: "text",
text: result,
},
],
};
}
// Add to Sequence
private async addToSequence(args: any) {
const response = await this.axiosInstance.post(
`/emailer_campaigns/${args.sequence_id}/add_contact_ids`,
{
contact_ids: args.contact_ids,
emailer_campaign_id: args.sequence_id,
send_email_from_email_account_id: args.mailbox_id,
}
);
return {
content: [
{
type: "text",
text: `Successfully added ${args.contact_ids?.length || args.contact_emails?.length || 0} contact(s) to sequence ${args.sequence_id}`,
},
],
};
}
// Remove from Sequence
private async removeFromSequence(args: any) {
const response = await this.axiosInstance.post(
`/emailer_campaigns/${args.sequence_id}/remove_contact_ids`,
{
contact_ids: args.contact_ids,
}
);
return {
content: [
{
type: "text",
text: `Successfully removed ${args.contact_ids.length} contact(s) from sequence ${args.sequence_id}`,
},
],
};
}
// Get Lists
private async getLists(args: any) {
const response = await this.axiosInstance.get("/contact_lists", {
params: args,
});
const lists = response.data.contact_lists || [];
let result = `Contact Lists (${lists.length}):\n\n`;
lists.forEach((list: any, index: number) => {
result += `${index + 1}. ${list.name}\n`;
result += ` ID: ${list.id}\n`;
result += ` Contacts: ${list.num_contacts || 0}\n`;
result += ` Created: ${list.created_at ? new Date(list.created_at).toLocaleDateString() : "N/A"}\n\n`;
});
return {
content: [
{
type: "text",
text: result,
},
],
};
}
// Get List Contacts
private async getListContacts(args: any) {
const page = args.page || 1;
const perPage = args.per_page || 100;
const response = await this.axiosInstance.get(`/contact_lists/${args.id}/contacts`, {
params: { page, per_page: perPage },
});
const contacts = response.data.contacts || [];
const pagination = response.data.pagination || {};
let result = `List Contacts (${pagination.total_entries || contacts.length} total)\n`;
result += `Page ${pagination.page || 1} of ${pagination.total_pages || 1}\n\n`;
contacts.forEach((contact: any, index: number) => {
result += `${index + 1}. ${contact.first_name} ${contact.last_name}\n`;
result += ` ID: ${contact.id}\n`;
result += ` Email: ${contact.email || "N/A"}\n`;
result += ` Title: ${contact.title || "N/A"}\n`;
result += ` Company: ${contact.account?.name || "N/A"}\n`;
result += ` Phone: ${contact.phone_numbers?.[0]?.raw_number || "N/A"}\n`;
result += ` LinkedIn: ${contact.linkedin_url || "N/A"}\n\n`;
});
return {
content: [
{
type: "text",
text: result,
},
],
};
}
// Analyze List
private async analyzeList(args: any) {
// Fetch list details
const listResponse = await this.axiosInstance.get(`/contact_lists/${args.id}`);
const list = listResponse.data.contact_list;
// Fetch contacts
const contactsResponse = await this.axiosInstance.get(
`/contact_lists/${args.id}/contacts`,
{
params: { per_page: 1000 },
}
);
const contacts = contactsResponse.data.contacts || [];
let result = `List Analysis: ${list.name}\n\n`;
result += `=== Overview ===\n`;
result += `Total Contacts: ${list.num_contacts || contacts.length}\n`;
result += `Created: ${list.created_at ? new Date(list.created_at).toLocaleDateString() : "N/A"}\n\n`;
// Analyze job titles
const titles: { [key: string]: number } = {};
const seniorities: { [key: string]: number } = {};
const companies: { [key: string]: number } = {};
const locations: { [key: string]: number } = {};
let emailCount = 0;
let phoneCount = 0;
let linkedinCount = 0;
contacts.forEach((contact: any) => {
if (contact.title) {
titles[contact.title] = (titles[contact.title] || 0) + 1;
}
if (contact.seniority) {
seniorities[contact.seniority] = (seniorities[contact.seniority] || 0) + 1;
}
if (contact.account?.name) {
companies[contact.account.name] = (companies[contact.account.name] || 0) + 1;
}
if (contact.city) {
const location = `${contact.city}, ${contact.state || contact.country || ""}`;
locations[location] = (locations[location] || 0) + 1;
}
if (contact.email) emailCount++;
if (contact.phone_numbers?.length > 0) phoneCount++;
if (contact.linkedin_url) linkedinCount++;
});
result += `=== Data Completeness ===\n`;
result += `Contacts with Email: ${emailCount} (${((emailCount / contacts.length) * 100).toFixed(1)}%)\n`;
result += `Contacts with Phone: ${phoneCount} (${((phoneCount / contacts.length) * 100).toFixed(1)}%)\n`;
result += `Contacts with LinkedIn: ${linkedinCount} (${((linkedinCount / contacts.length) * 100).toFixed(1)}%)\n\n`;
result += `=== Top Job Titles ===\n`;
Object.entries(titles)
.sort((a, b) => b[1] - a[1])
.slice(0, 10)
.forEach(([title, count]) => {
result += `${title}: ${count}\n`;
});
result += `\n=== Seniority Distribution ===\n`;
Object.entries(seniorities)
.sort((a, b) => b[1] - a[1])
.forEach(([seniority, count]) => {
result += `${seniority}: ${count}\n`;
});
result += `\n=== Top Companies ===\n`;
Object.entries(companies)
.sort((a, b) => b[1] - a[1])
.slice(0, 10)
.forEach(([company, count]) => {
result += `${company}: ${count}\n`;
});
result += `\n=== Top Locations ===\n`;
Object.entries(locations)
.sort((a, b) => b[1] - a[1])
.slice(0, 10)
.forEach(([location, count]) => {
result += `${location}: ${count}\n`;
});
return {
content: [
{
type: "text",
text: result,
},
],
};
}
// Create Contact
private async createContact(args: any) {
const response = await this.axiosInstance.post("/contacts", args);
const contact = response.data.contact;
return {
content: [
{
type: "text",
text: `Contact created successfully!\nID: ${contact.id}\nName: ${contact.first_name} ${contact.last_name}\nEmail: ${contact.email || "N/A"}`,
},
],
};
}
// Update Contact
private async updateContact(args: any) {
const { id, ...updateData } = args;
const response = await this.axiosInstance.put(`/contacts/${id}`, updateData);
const contact = response.data.contact;
return {
content: [
{
type: "text",
text: `Contact updated successfully!\nID: ${contact.id}\nName: ${contact.first_name} ${contact.last_name}`,
},
],
};
}
// Create Account
private async createAccount(args: any) {
const response = await this.axiosInstance.post("/accounts", args);
const account = response.data.account;
return {
content: [
{
type: "text",
text: `Account created successfully!\nID: ${account.id}\nName: ${account.name}\nDomain: ${account.domain || "N/A"}`,
},
],
};
}
// Get Account
private async getAccount(args: any) {
const endpoint = args.id
? `/accounts/${args.id}`
: `/accounts/search?q_organization_domains[]=${args.domain}`;
const response = await this.axiosInstance.get(endpoint);
const account = args.id
? response.data.account
: response.data.accounts?.[0];
if (!account) {
return {
content: [
{
type: "text",
text: "Account not found.",
},
],
};
}
let result = `Account Details:\n\n`;
result += `Name: ${account.name}\n`;
result += `ID: ${account.id}\n`;
result += `Domain: ${account.domain || account.website_url || "N/A"}\n`;
result += `Industry: ${account.industry || "N/A"}\n`;
result += `Employees: ${account.estimated_num_employees || "N/A"}\n`;
result += `Phone: ${account.phone || "N/A"}\n`;
return {
content: [
{
type: "text",
text: result,
},
],
};
}
// Search Job Postings
private async searchJobPostings(args: any) {
const response = await this.axiosInstance.post("/job_postings/search", args);
const jobPostings = response.data.job_postings || [];
let result = `Job Postings Found: ${jobPostings.length}\n\n`;
jobPostings.slice(0, 20).forEach((job: any, index: number) => {
result += `${index + 1}. ${job.title}\n`;
result += ` Company: ${job.organization?.name || "N/A"}\n`;
result += ` Location: ${job.city || "N/A"}\n`;
result += ` Posted: ${job.posted_at ? new Date(job.posted_at).toLocaleDateString() : "N/A"}\n`;
result += ` URL: ${job.url || "N/A"}\n\n`;
});
return {
content: [
{
type: "text",
text: result,
},
],
};
}
// Get Person Activity
private async getPersonActivity(args: any) {
const response = await this.axiosInstance.get(`/people/${args.id}/activities`);
const activities = response.data.activities || [];
let result = `Activity History:\n\n`;
activities.forEach((activity: any, index: number) => {
result += `${index + 1}. ${activity.type}\n`;
result += ` Date: ${activity.created_at ? new Date(activity.created_at).toLocaleDateString() : "N/A"}\n`;
result += ` Details: ${activity.note || "N/A"}\n\n`;
});
if (activities.length === 0) {
result += "No activity found for this contact.\n";
}
return {
content: [
{
type: "text",
text: result,
},
],
};
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error("Apollo.io MCP Server running on stdio");
}
}
const server = new ApolloMCPServer();
server.run().catch(console.error);