UNPKG

@agentx-ai/apollo-io-mcp-server

Version:

AgentX MCP server for Apollo.io API with comprehensive people and organization enrichment capabilities

597 lines (540 loc) 17.5 kB
import axios, { AxiosInstance } from "axios"; import dotenv from "dotenv"; // Load environment variables dotenv.config(); // Helper function to strip URL const stripUrl = (url?: string): string | undefined => { if (!url) return undefined; try { // Remove protocol (http://, https://) let stripped = url.replace(/^https?:\/\//, ""); // Remove www. stripped = stripped.replace(/^www\./, ""); // Remove trailing slash stripped = stripped.replace(/\/$/, ""); // Convert to lowercase stripped = stripped.toLowerCase(); return stripped; } catch (error) { console.error("Error stripping URL:", error); return url; } }; // Type definitions for Apollo.io API responses export interface PeopleEnrichmentQuery { first_name?: string; last_name?: string; email?: string; domain?: string; organization_name?: string; [key: string]: any; } export interface PeopleSearchQuery { person_titles?: string[]; include_similar_titles?: boolean; q_keywords?: string; person_locations?: string[]; person_seniorities?: string[]; organization_locations?: string[]; q_organization_domains_list?: string[]; contact_email_status?: string[]; organization_ids?: string[]; organization_num_employees_ranges?: string[]; revenue_range?: { min?: number; max?: number; }; currently_using_all_of_technology_uids?: string[]; currently_using_any_of_technology_uids?: string[]; currently_not_using_any_of_technology_uids?: string[]; q_organization_job_titles?: string[]; organization_job_locations?: string[]; organization_num_jobs_range?: { min?: number; max?: number; }; organization_job_posted_at_range?: { min?: string; max?: string; }; page?: number; per_page?: number; [key: string]: any; } export interface OrganizationSearchQuery { q_organization_domains_list?: string[]; organization_locations?: string[]; organization_not_locations?: string[]; organization_num_employees_ranges?: string[]; revenue_range?: { min?: number; max?: number; }; currently_using_any_of_technology_uids?: string[]; q_organization_keyword_tags?: string[]; q_organization_name?: string; organization_ids?: string[]; latest_funding_amount_range?: { min?: number; max?: number; }; total_funding_range?: { min?: number; max?: number; }; latest_funding_date_range?: { min?: string; max?: string; }; q_organization_job_titles?: string[]; organization_job_locations?: string[]; organization_num_jobs_range?: { min?: number; max?: number; }; organization_job_posted_at_range?: { min?: string; max?: string; }; page?: number; per_page?: number; [key: string]: any; } export interface EmployeesOfCompanyQuery { company: string; website_url?: string; linkedin_url?: string; [key: string]: any; } export class ApolloClient { private apiKey: string; private baseUrl: string; private headers: Record<string, string>; private axiosInstance: AxiosInstance; constructor(apiKey?: string) { this.apiKey = apiKey || process.env.APOLLO_IO_API_KEY || ""; if (!this.apiKey) { throw new Error("APOLLO_IO_API_KEY environment variable is required"); } this.baseUrl = "https://api.apollo.io/api/v1"; this.headers = { "Content-Type": "application/json", "Cache-Control": "no-cache", "x-api-key": this.apiKey, }; this.axiosInstance = axios.create({ baseURL: this.baseUrl, headers: this.headers, }); } /** * Use the People Enrichment endpoint to enrich data for 1 person. * https://docs.apollo.io/reference/people-enrichment */ async peopleEnrichment(query: PeopleEnrichmentQuery): Promise<any> { try { const url = `${this.baseUrl}/people/match`; const response = await this.axiosInstance.post(url, query); if (response.status === 200) { return response.data; } else { throw new Error( `People enrichment failed: ${response.status} - ${response.statusText}` ); } } catch (error: any) { if (error.response) { throw new Error( `People enrichment API error: ${error.response.status} - ${ error.response.statusText }. Response: ${JSON.stringify(error.response.data)}` ); } else if (error.request) { throw new Error(`People enrichment network error: ${error.message}`); } else { throw new Error(`People enrichment error: ${error.message}`); } } } /** * Use the Organization Enrichment endpoint to enrich data for 1 company. * https://docs.apollo.io/reference/organization-enrichment */ async organizationEnrichment(domain: string): Promise<any> { try { const url = `${this.baseUrl}/organizations/enrich?domain=${domain}`; const response = await this.axiosInstance.get(url); if (response.status === 200) { return response.data; } else { throw new Error( `Organization enrichment failed for domain '${domain}': ${response.status} - ${response.statusText}` ); } } catch (error: any) { if (error.response) { throw new Error( `Organization enrichment API error for domain '${domain}': ${ error.response.status } - ${error.response.statusText}. Response: ${JSON.stringify( error.response.data )}` ); } else if (error.request) { throw new Error( `Organization enrichment network error for domain '${domain}': ${error.message}` ); } else { throw new Error( `Organization enrichment error for domain '${domain}': ${error.message}` ); } } } /** * Use the People Search endpoint to find people. * https://docs.apollo.io/reference/people-search */ async peopleSearch(query: PeopleSearchQuery): Promise<any> { try { const url = `${this.baseUrl}/mixed_people/search`; const response = await this.axiosInstance.post(url, query); if (response.status === 200) { return response.data; } else { throw new Error( `People search failed: ${response.status} - ${response.statusText}` ); } } catch (error: any) { if (error.response) { throw new Error( `People search API error: ${error.response.status} - ${ error.response.statusText }. Response: ${JSON.stringify(error.response.data)}` ); } else if (error.request) { throw new Error(`People search network error: ${error.message}`); } else { throw new Error(`People search error: ${error.message}`); } } } /** * Use the Organization Search endpoint to find organizations. * https://docs.apollo.io/reference/organization-search */ async organizationSearch(query: OrganizationSearchQuery): Promise<any> { try { const url = `${this.baseUrl}/mixed_companies/search`; const response = await this.axiosInstance.post(url, query); if (response.status === 200) { return response.data; } else { throw new Error( `Organization search failed: ${response.status} - ${response.statusText}` ); } } catch (error: any) { if (error.response) { throw new Error( `Organization search API error: ${error.response.status} - ${ error.response.statusText }. Response: ${JSON.stringify(error.response.data)}` ); } else if (error.request) { throw new Error(`Organization search network error: ${error.message}`); } else { throw new Error(`Organization search error: ${error.message}`); } } } /** * Use the Organization Job Postings endpoint to find job postings for a specific organization. * https://docs.apollo.io/reference/organization-jobs-postings */ async organizationJobPostings(organizationId: string): Promise<any> { try { const url = `${this.baseUrl}/organizations/${organizationId}/job_postings`; const response = await this.axiosInstance.get(url); if (response.status === 200) { return response.data; } else { throw new Error( `Organization job postings failed for ID '${organizationId}': ${response.status} - ${response.statusText}` ); } } catch (error: any) { if (error.response) { throw new Error( `Organization job postings API error for ID '${organizationId}': ${ error.response.status } - ${error.response.statusText}. Response: ${JSON.stringify( error.response.data )}` ); } else if (error.request) { throw new Error( `Organization job postings network error for ID '${organizationId}': ${error.message}` ); } else { throw new Error( `Organization job postings error for ID '${organizationId}': ${error.message}` ); } } } /** * Get email address for a person using their Apollo ID */ async getPersonEmail(apolloId: string): Promise<any> { try { if (!apolloId) { throw new Error("Apollo ID is required"); } const baseUrl = `https://app.apollo.io/api/v1/mixed_people/add_to_my_prospects`; const payload = { entity_ids: [apolloId], analytics_context: "Searcher: Individual Add Button", skip_fetching_people: true, cta_name: "Access email", cacheKey: Date.now(), }; const response = await axios.post(baseUrl, payload, { headers: { "X-Api-Key": this.apiKey, "Content-Type": "application/json", }, }); if (!response.data) { throw new Error("No data received from Apollo API"); } const emails = (response?.data?.contacts ?? []).map( (item: any) => item.email ); return emails; } catch (error: any) { if (error.response) { throw new Error( `Get person email API error for ID '${apolloId}': ${ error.response.status } - ${error.response.statusText}. Response: ${JSON.stringify( error.response.data )}` ); } else if (error.request) { throw new Error( `Get person email network error for ID '${apolloId}': ${error.message}` ); } else { throw new Error( `Get person email error for ID '${apolloId}': ${error.message}` ); } } } /** * Find employees of a company using company name or website/LinkedIn URL */ async employeesOfCompany(query: EmployeesOfCompanyQuery): Promise<any> { try { const { company, website_url, linkedin_url } = query; if (!company) { throw new Error("Company name is required"); } const strippedWebsiteUrl = stripUrl(website_url); const strippedLinkedinUrl = stripUrl(linkedin_url); // First search for the company const companySearchPayload = { q_organization_name: company, page: 1, limit: 100, }; const mixedCompaniesResponse = await axios.post( "https://api.apollo.io/v1/mixed_companies/search", companySearchPayload, { headers: { "Content-Type": "application/json", "X-Api-Key": this.apiKey, }, } ); if (!mixedCompaniesResponse.data) { throw new Error("No data received from Apollo API"); } let organizations = mixedCompaniesResponse.data.organizations; if (organizations.length === 0) { throw new Error(`No organizations found for company '${company}'`); } // Filter companies by website or LinkedIn URL if provided const companyObjs = organizations.filter((item: any) => { const companyLinkedin = stripUrl(item.linkedin_url); const companyWebsite = stripUrl(item.website_url); if ( strippedLinkedinUrl && companyLinkedin && companyLinkedin === strippedLinkedinUrl ) { return true; } else if ( strippedWebsiteUrl && companyWebsite && companyWebsite === strippedWebsiteUrl ) { return true; } return false; }); // If we have filtered results, use the first one, otherwise use the first from the original search const companyObj = companyObjs.length > 0 ? companyObjs[0] : organizations[0]; const companyId = companyObj.id; if (!companyId) { throw new Error(`Could not determine company ID for '${company}'`); } // Now search for employees const peopleSearchPayload: any = { organization_ids: [companyId], page: 1, limit: 100, }; // Add optional filters if provided in the tool config if (query.person_seniorities) { peopleSearchPayload.person_titles = (query.person_seniorities ?? "") .split(",") .map((item: string) => item.trim()); } if (query.contact_email_status) { peopleSearchPayload.contact_email_status_v2 = ( query.contact_email_status ?? "" ) .split(",") .map((item: string) => item.trim()); } const peopleResponse = await axios.post( "https://api.apollo.io/v1/mixed_people/search", peopleSearchPayload, { headers: { "Content-Type": "application/json", "X-Api-Key": this.apiKey, }, } ); if (!peopleResponse.data) { throw new Error("No data received from Apollo API"); } return peopleResponse.data.people || []; } catch (error: any) { if (error.response) { throw new Error( `Employees of company API error for '${query.company}': ${ error.response.status } - ${error.response.statusText}. Response: ${JSON.stringify( error.response.data )}` ); } else if (error.request) { throw new Error( `Employees of company network error for '${query.company}': ${error.message}` ); } else { throw new Error( `Employees of company error for '${query.company}': ${error.message}` ); } } } } // Example usage (for testing) async function main() { try { // Get API key from environment variable const apiKey = process.env.APOLLO_IO_API_KEY; if (!apiKey) { throw new Error("APOLLO_IO_API_KEY environment variable is required"); } const client = new ApolloClient(apiKey); // Example People Enrichment const peopleEnrichmentQuery: PeopleEnrichmentQuery = { first_name: "Tim", last_name: "Zheng", }; const peopleEnrichmentResponse = await client.peopleEnrichment( peopleEnrichmentQuery ); if (peopleEnrichmentResponse) { console.log( "People Enrichment Response:", JSON.stringify(peopleEnrichmentResponse, null, 2) ); } else { console.log("People Enrichment failed."); } // Example Organization Enrichment const organizationEnrichmentQuery = { domain: "agentx.so", }; const organizationEnrichmentResponse = await client.organizationEnrichment( organizationEnrichmentQuery.domain ); if (organizationEnrichmentResponse) { console.log( "Organization Enrichment Response:", JSON.stringify(organizationEnrichmentResponse, null, 2) ); } else { console.log("Organization Enrichment failed."); } // Example People Search const peopleSearchQuery: PeopleSearchQuery = { person_titles: ["Marketing Manager"], person_seniorities: ["vp"], q_organization_domains_list: ["apollo.io"], }; const peopleSearchResponse = await client.peopleSearch(peopleSearchQuery); if (peopleSearchResponse) { console.log( "People Search Response:", JSON.stringify(peopleSearchResponse, null, 2) ); } else { console.log("People Search failed."); } // Example Organization Search const organizationSearchQuery: OrganizationSearchQuery = { organization_locations: ["Japan", "Ireland"], }; const organizationSearchResponse = await client.organizationSearch( organizationSearchQuery ); if (organizationSearchResponse) { console.log( "Organization Search Response:", JSON.stringify(organizationSearchResponse, null, 2) ); } else { console.log("Organization Search failed."); } // Example Organization Job Postings // Note: You need a valid organization ID for this to work const organizationId = "5e60b6381c85b4008c83"; // Replace with a valid organization ID const organizationJobPostingsResponse = await client.organizationJobPostings(organizationId); if (organizationJobPostingsResponse) { console.log( "Organization Job Postings Response:", JSON.stringify(organizationJobPostingsResponse, null, 2) ); } else { console.log("Organization Job Postings failed."); } } catch (error) { console.error("Error:", error); } } // Run the example if this file is executed directly if (process.argv[1] === import.meta.url) { main(); }