@agentx-ai/apollo-io-mcp-server
Version:
AgentX MCP server for Apollo.io API with comprehensive people and organization enrichment capabilities
650 lines (625 loc) • 22.9 kB
text/typescript
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import {
CallToolRequestSchema,
ErrorCode,
ListToolsRequestSchema,
McpError,
Tool,
} from "@modelcontextprotocol/sdk/types.js";
import { ApolloClient } from "./apollo-client.js";
import dotenv from "dotenv";
import { parseArgs } from "node:util";
// Load environment variables
dotenv.config();
// Parse command line arguments
const { values } = parseArgs({
options: {
"api-key": { type: "string" },
},
});
// Initialize Apollo.io client
const apiKey = values["api-key"] || process.env.APOLLO_IO_API_KEY;
if (!apiKey) {
throw new Error("APOLLO_IO_API_KEY environment variable is required");
}
export class ApolloServer {
// Core server properties
private server: Server;
private apollo: ApolloClient;
constructor() {
this.server = new Server(
{
name: "apollo-io-manager",
version: "1.0.0",
},
{
capabilities: {
resources: {},
tools: {},
},
}
);
this.apollo = new ApolloClient(apiKey);
this.setupToolHandlers();
this.setupErrorHandling();
}
private setupErrorHandling(): void {
this.server.onerror = (error) => {
console.error("[MCP Error]", error);
};
process.on("SIGINT", async () => {
await this.server.close();
process.exit(0);
});
process.on("uncaughtException", (error) => {
console.error("Uncaught exception:", error);
});
process.on("unhandledRejection", (reason, promise) => {
console.error("Unhandled rejection at:", promise, "reason:", reason);
});
}
private setupToolHandlers(): void {
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
// Define available tools
const tools: Tool[] = [
{
name: "people_enrichment",
description:
"Use the People Enrichment endpoint to enrich data for 1 person, at least one parameter is required.",
inputSchema: {
type: "object",
properties: {
first_name: {
type: "string",
description: "Person's first name",
},
last_name: {
type: "string",
description: "Person's last name",
},
email: {
type: "string",
description: "Person's email address",
},
domain: {
type: "string",
description: "Company domain",
},
organization_name: {
type: "string",
description: "Organization name",
},
linkedin_url: {
type: "string",
description: "Person's LinkedIn profile URL",
},
},
},
},
{
name: "organization_enrichment",
description:
"Use the Organization Enrichment endpoint to enrich data for 1 company",
inputSchema: {
type: "object",
properties: {
domain: {
type: "string",
description:
"The domain of the company that you want to enrich. Do not include www., the @ symbol, or similar (e.g., 'apollo.io' or 'microsoft.com')",
},
},
required: ["domain"],
},
},
{
name: "people_search",
description:
"Use the People Search endpoint to find people with comprehensive filtering options, at least one parameter is required.",
inputSchema: {
type: "object",
properties: {
person_titles: {
type: "array",
items: { type: "string" },
description:
"Job titles held by the people you want to find (e.g., 'sales development representative', 'marketing manager', 'research analyst')",
},
include_similar_titles: {
type: "boolean",
description:
"Whether to include people with similar job titles (default: true)",
},
q_keywords: {
type: "string",
description: "A string of words to filter the results",
},
person_locations: {
type: "array",
items: { type: "string" },
description:
"The location where people live (e.g., 'california', 'ireland', 'chicago')",
},
person_seniorities: {
type: "array",
items: { type: "string" },
description:
"Job seniority levels (e.g., 'owner', 'founder', 'c_suite', 'partner', 'vp', 'head', 'director', 'manager', 'senior', 'entry', 'intern')",
},
organization_locations: {
type: "array",
items: { type: "string" },
description:
"The location of the company headquarters for a person's current employer (e.g., 'texas', 'tokyo', 'spain')",
},
q_organization_domains_list: {
type: "array",
items: { type: "string" },
description:
"The domain name for the person's employer (e.g., 'apollo.io', 'microsoft.com')",
},
contact_email_status: {
type: "array",
items: { type: "string" },
description:
"Email statuses to filter by (e.g., 'verified', 'unverified', 'likely to engage', 'unavailable')",
},
organization_ids: {
type: "array",
items: { type: "string" },
description:
"Apollo IDs for specific companies (employers) to include in search results",
},
organization_num_employees_ranges: {
type: "array",
items: { type: "string" },
description:
"Employee count ranges for the person's current company (e.g., '1,10', '250,500', '10000,20000')",
},
revenue_range: {
type: "object",
properties: {
min: {
type: "integer",
description:
"Minimum revenue the person's current employer generates (no currency symbols, commas, or decimals)",
},
max: {
type: "integer",
description:
"Maximum revenue the person's current employer generates (no currency symbols, commas, or decimals)",
},
},
description: "Revenue range for the person's current employer",
},
currently_using_all_of_technology_uids: {
type: "array",
items: { type: "string" },
description:
"Find people based on ALL technologies their current employer uses (use underscores for spaces, e.g., 'salesforce', 'google_analytics', 'wordpress_org')",
},
currently_using_any_of_technology_uids: {
type: "array",
items: { type: "string" },
description:
"Find people based on ANY of the technologies their current employer uses (use underscores for spaces, e.g., 'salesforce', 'google_analytics', 'wordpress_org')",
},
currently_not_using_any_of_technology_uids: {
type: "array",
items: { type: "string" },
description:
"Exclude people based on technologies their current employer uses (use underscores for spaces, e.g., 'salesforce', 'google_analytics', 'wordpress_org')",
},
q_organization_job_titles: {
type: "array",
items: { type: "string" },
description:
"Job titles listed in active job postings at the person's current employer (e.g., 'sales manager', 'research analyst')",
},
organization_job_locations: {
type: "array",
items: { type: "string" },
description:
"Locations of jobs being actively recruited by the person's employer (e.g., 'atlanta', 'japan')",
},
organization_num_jobs_range: {
type: "object",
properties: {
min: {
type: "integer",
description:
"Minimum number of active job postings at the person's current employer",
},
max: {
type: "integer",
description:
"Maximum number of active job postings at the person's current employer",
},
},
description:
"Range for number of job postings active at the person's current employer",
},
organization_job_posted_at_range: {
type: "object",
properties: {
min: {
type: "string",
format: "date",
description:
"Earliest date when jobs were posted by the person's current employer (YYYY-MM-DD)",
},
max: {
type: "string",
format: "date",
description:
"Latest date when jobs were posted by the person's current employer (YYYY-MM-DD)",
},
},
description:
"Date range for when jobs were posted by the person's current employer",
},
page: {
type: "integer",
description: "Page number for pagination (default: 1)",
},
per_page: {
type: "integer",
description:
"Number of results per page (max: 100, default: 25)",
},
},
},
},
{
name: "organization_search",
description:
"Use the Organization Search endpoint to find organizations with comprehensive filtering options, at least one parameter is required.",
inputSchema: {
type: "object",
properties: {
q_organization_domains_list: {
type: "array",
items: { type: "string" },
description: "List of organization domains to search for",
},
organization_locations: {
type: "array",
items: { type: "string" },
description:
"The location of the company headquarters (e.g., 'texas', 'tokyo', 'spain')",
},
organization_not_locations: {
type: "array",
items: { type: "string" },
description:
"Exclude companies from search results based on location (e.g., 'minnesota', 'ireland', 'seoul')",
},
organization_num_employees_ranges: {
type: "array",
items: { type: "string" },
description:
"Employee count ranges with upper and lower numbers separated by comma (e.g., '1,10', '250,500', '10000,20000')",
},
revenue_range: {
type: "object",
properties: {
min: {
type: "integer",
description:
"Minimum revenue amount (no currency symbols, commas, or decimals)",
},
max: {
type: "integer",
description:
"Maximum revenue amount (no currency symbols, commas, or decimals)",
},
},
description:
"Search for organizations based on their revenue range",
},
currently_using_any_of_technology_uids: {
type: "array",
items: { type: "string" },
description:
"Technologies the organization currently uses (use underscores for spaces, e.g., 'salesforce', 'google_analytics', 'wordpress_org')",
},
q_organization_keyword_tags: {
type: "array",
items: { type: "string" },
description:
"Filter search results based on keywords associated with companies (e.g., 'mining', 'sales strategy', 'consulting')",
},
q_organization_name: {
type: "string",
description:
"Filter search results to include a specific company name (partial matches accepted)",
},
organization_ids: {
type: "array",
items: { type: "string" },
description:
"Apollo IDs for specific companies to include in search results",
},
latest_funding_amount_range: {
type: "object",
properties: {
min: {
type: "integer",
description:
"Minimum amount from most recent funding round (no currency symbols, commas, or decimals)",
},
max: {
type: "integer",
description:
"Maximum amount from most recent funding round (no currency symbols, commas, or decimals)",
},
},
description:
"Funding amount range for the company's most recent funding round",
},
total_funding_range: {
type: "object",
properties: {
min: {
type: "integer",
description:
"Minimum total funding amount across all rounds (no currency symbols, commas, or decimals)",
},
max: {
type: "integer",
description:
"Maximum total funding amount across all rounds (no currency symbols, commas, or decimals)",
},
},
description:
"Total funding amount range across all funding rounds",
},
latest_funding_date_range: {
type: "object",
properties: {
min: {
type: "string",
format: "date",
description:
"Earliest date of most recent funding round (YYYY-MM-DD)",
},
max: {
type: "string",
format: "date",
description:
"Latest date of most recent funding round (YYYY-MM-DD)",
},
},
description:
"Date range for when the company received its most recent funding round",
},
q_organization_job_titles: {
type: "array",
items: { type: "string" },
description:
"Job titles listed in active job postings at the company (e.g., 'sales manager', 'research analyst')",
},
organization_job_locations: {
type: "array",
items: { type: "string" },
description:
"Locations of jobs being actively recruited by the company (e.g., 'atlanta', 'japan')",
},
organization_num_jobs_range: {
type: "object",
properties: {
min: {
type: "integer",
description:
"Minimum number of active job postings at the company",
},
max: {
type: "integer",
description:
"Maximum number of active job postings at the company",
},
},
description:
"Range for number of job postings active at the company",
},
organization_job_posted_at_range: {
type: "object",
properties: {
min: {
type: "string",
format: "date",
description:
"Earliest date when jobs were posted by the company (YYYY-MM-DD)",
},
max: {
type: "string",
format: "date",
description:
"Latest date when jobs were posted by the company (YYYY-MM-DD)",
},
},
description:
"Date range for when jobs were posted by the company",
},
page: {
type: "integer",
description: "Page number for pagination (default: 1)",
},
per_page: {
type: "integer",
description:
"Number of results per page (max: 100, default: 25)",
},
},
},
},
{
name: "organization_job_postings",
description:
"Use the Organization Job Postings endpoint to find job postings for a specific organization",
inputSchema: {
type: "object",
properties: {
organization_id: {
type: "string",
description: "Apollo.io organization ID",
},
},
required: ["organization_id"],
},
},
{
name: "get_person_email",
description: "Get email address for a person using their Apollo ID",
inputSchema: {
type: "object",
properties: {
apollo_id: {
type: "string",
description: "Apollo.io person ID",
},
},
required: ["apollo_id"],
},
},
{
name: "employees_of_company",
description:
"Find employees of a company using company name or website/LinkedIn URL",
inputSchema: {
type: "object",
properties: {
company: {
type: "string",
description: "Company name",
},
website_url: {
type: "string",
description: "Company website URL",
},
linkedin_url: {
type: "string",
description: "Company LinkedIn URL",
},
},
required: ["company"],
},
},
];
return { tools };
});
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
try {
const args = request.params.arguments ?? {};
switch (request.params.name) {
case "people_enrichment": {
const result = await this.apollo.peopleEnrichment(args);
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
}
case "organization_enrichment": {
const result = await this.apollo.organizationEnrichment(
args.domain as string
);
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
}
case "people_search": {
const result = await this.apollo.peopleSearch(args);
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
}
case "organization_search": {
const result = await this.apollo.organizationSearch(args);
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
}
case "organization_job_postings": {
const result = await this.apollo.organizationJobPostings(
args.organization_id as string
);
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
}
case "get_person_email": {
const result = await this.apollo.getPersonEmail(
args.apollo_id as string
);
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
}
case "employees_of_company": {
const result = await this.apollo.employeesOfCompany(args as any);
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
}
default:
throw new McpError(
ErrorCode.MethodNotFound,
`Unknown tool: ${request.params.name}`
);
}
} catch (error: any) {
console.error(`Error executing tool ${request.params.name}:`, error);
return {
content: [
{
type: "text",
text: `Apollo.io API error: ${error.message}`,
},
],
isError: true,
};
}
});
}
async connect(transport: any): Promise<void> {
await this.server.connect(transport);
console.log("Apollo.io MCP server started");
}
getServer(): Server {
return this.server;
}
}