#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
import database from './database-pg.js';
import LinkedInAutomation from './linkedin.js';
import AIService from './ai.js';
import { z } from 'zod';
import dotenv from 'dotenv';
dotenv.config();
const server = new Server(
{ name: 'linkedin-lead-automation', version: '1.0.0' },
{ capabilities: { tools: {} } }
);
let linkedinBot = null;
let aiService = null;
// Initialize services
async function initializeServices() {
await database.init();
if (process.env.GCP_PROJECT_ID || process.env.ANTHROPIC_API_KEY) {
aiService = new AIService({
projectId: process.env.GCP_PROJECT_ID,
location: process.env.GCP_LOCATION,
modelId: process.env.ANTHROPIC_MODEL_ID
});
}
}
// Tool Definitions
const tools = [
{
name: 'connect_browser',
description: 'Connect to Chrome browser via CDP for LinkedIn automation',
inputSchema: {
type: 'object',
properties: {
cdp_url: {
type: 'string',
description: 'Chrome DevTools Protocol URL (e.g., http://localhost:9222)'
}
},
required: ['cdp_url']
}
},
{
name: 'setup_session',
description: 'Authenticate LinkedIn session using li_at cookie',
inputSchema: {
type: 'object',
properties: {
api_key: {
type: 'string',
description: 'Your API key'
},
li_at_cookie: {
type: 'string',
description: 'LinkedIn li_at cookie value from browser'
}
},
required: ['api_key', 'li_at_cookie']
}
},
{
name: 'search_leads',
description: 'Search LinkedIn for leads matching criteria',
inputSchema: {
type: 'object',
properties: {
api_key: {
type: 'string',
description: 'Your API key'
},
keywords: {
type: 'string',
description: 'Search keywords (job titles, industries, etc.)'
},
location: {
type: 'string',
description: 'Geographic location filter'
},
limit: {
type: 'number',
description: 'Maximum number of results (default: 25)',
default: 25
}
},
required: ['api_key', 'keywords']
}
},
{
name: 'analyze_profile',
description: 'Extract and analyze complete LinkedIn profile data',
inputSchema: {
type: 'object',
properties: {
api_key: {
type: 'string',
description: 'Your API key'
},
profile_url: {
type: 'string',
description: 'LinkedIn profile URL'
}
},
required: ['api_key', 'profile_url']
}
},
{
name: 'score_lead',
description: 'AI-powered lead scoring based on profile data (0-100 scale)',
inputSchema: {
type: 'object',
properties: {
api_key: {
type: 'string',
description: 'Your API key'
},
profile_url: {
type: 'string',
description: 'LinkedIn profile URL to score'
}
},
required: ['api_key', 'profile_url']
}
},
{
name: 'generate_message',
description: 'Generate hyper-personalized message for a lead',
inputSchema: {
type: 'object',
properties: {
api_key: {
type: 'string',
description: 'Your API key'
},
profile_url: {
type: 'string',
description: 'LinkedIn profile URL'
},
value_proposition: {
type: 'string',
description: 'Your value proposition/pitch'
},
message_type: {
type: 'string',
enum: ['connection', 'direct'],
description: 'Type of message (connection request or direct message)'
}
},
required: ['api_key', 'profile_url', 'value_proposition', 'message_type']
}
},
{
name: 'send_message',
description: 'Send message to LinkedIn profile',
inputSchema: {
type: 'object',
properties: {
api_key: {
type: 'string',
description: 'Your API key'
},
profile_url: {
type: 'string',
description: 'LinkedIn profile URL'
},
message: {
type: 'string',
description: 'Message text to send'
},
is_connection_request: {
type: 'boolean',
description: 'Whether this is a connection request with note',
default: false
}
},
required: ['api_key', 'profile_url', 'message']
}
},
{
name: 'create_followup_sequence',
description: 'Create automated follow-up sequence for a lead',
inputSchema: {
type: 'object',
properties: {
api_key: {
type: 'string',
description: 'Your API key'
},
profile_url: {
type: 'string',
description: 'LinkedIn profile URL'
},
initial_message: {
type: 'string',
description: 'The initial message sent'
},
num_followups: {
type: 'number',
description: 'Number of follow-up messages (default: 3)',
default: 3
}
},
required: ['api_key', 'profile_url', 'initial_message']
}
},
{
name: 'generate_api_key',
description: 'Generate new API key for accessing the service',
inputSchema: {
type: 'object',
properties: {
tier: {
type: 'string',
enum: ['starter', 'professional', 'agency', 'enterprise'],
description: 'Subscription tier'
}
},
required: ['tier']
}
}
];
// List tools handler
server.setRequestHandler(ListToolsRequestSchema, async () => {
return { tools };
});
// Call tool handler
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case 'generate_api_key': {
const { tier } = args;
const result = await database.createApiKey(tier);
return {
content: [{
type: 'text',
text: JSON.stringify(result, null, 2)
}]
};
}
case 'connect_browser': {
const { cdp_url } = args;
linkedinBot = new LinkedInAutomation();
const result = await linkedinBot.connect(cdp_url);
return {
content: [{
type: 'text',
text: JSON.stringify(result, null, 2)
}]
};
}
case 'setup_session': {
const { api_key, li_at_cookie } = args;
// Validate API key
const key = await database.validateApiKey(api_key);
if (!key) {
return {
content: [{
type: 'text',
text: JSON.stringify({ success: false, error: 'Invalid API key' })
}]
};
}
if (!linkedinBot) {
return {
content: [{
type: 'text',
text: JSON.stringify({ success: false, error: 'Browser not connected. Call connect_browser first.' })
}]
};
}
const result = await linkedinBot.setupSession(li_at_cookie);
if (result.success) {
await database.saveSession(key.id, li_at_cookie);
}
return {
content: [{
type: 'text',
text: JSON.stringify(result, null, 2)
}]
};
}
case 'search_leads': {
const { api_key, ...searchParams } = args;
const key = await database.validateApiKey(api_key);
if (!key) {
return {
content: [{
type: 'text',
text: JSON.stringify({ success: false, error: 'Invalid API key' })
}]
};
}
if (!linkedinBot || !linkedinBot.isConnected) {
return {
content: [{
type: 'text',
text: JSON.stringify({ success: false, error: 'Browser not connected or session not setup' })
}]
};
}
const result = await linkedinBot.searchLeads(searchParams);
return {
content: [{
type: 'text',
text: JSON.stringify(result, null, 2)
}]
};
}
case 'analyze_profile': {
const { api_key, profile_url } = args;
const key = await database.validateApiKey(api_key);
if (!key) {
return {
content: [{
type: 'text',
text: JSON.stringify({ success: false, error: 'Invalid API key' })
}]
};
}
const usage = await database.checkUsageLimit(key.id, 'profile_analysis');
if (!usage.allowed) {
return {
content: [{
type: 'text',
text: JSON.stringify({ success: false, error: usage.reason })
}]
};
}
const result = await linkedinBot.analyzeProfile(profile_url);
if (result.success) {
await database.saveLead({
user_id: key.id,
profile_url,
...result.data,
analyzed_at: new Date().toISOString()
});
}
return {
content: [{
type: 'text',
text: JSON.stringify(result, null, 2)
}]
};
}
case 'score_lead': {
const { api_key, profile_url } = args;
const key = await database.validateApiKey(api_key);
if (!key) {
return {
content: [{
type: 'text',
text: JSON.stringify({ success: false, error: 'Invalid API key' })
}]
};
}
if (!aiService) {
return {
content: [{
type: 'text',
text: JSON.stringify({ success: false, error: 'AI service not configured. Set GCP_PROJECT_ID and ensure gcloud auth is set up.' })
}]
};
}
const lead = await database.getLead(profile_url);
if (!lead) {
return {
content: [{
type: 'text',
text: JSON.stringify({ success: false, error: 'Profile not analyzed yet. Call analyze_profile first.' })
}]
};
}
const result = await aiService.scoreLead(lead);
if (result.success) {
lead.score = result.score;
lead.score_reasoning = result.reasoning;
await database.saveLead(lead);
}
return {
content: [{
type: 'text',
text: JSON.stringify(result, null, 2)
}]
};
}
case 'generate_message': {
const { api_key, profile_url, value_proposition, message_type } = args;
const key = await database.validateApiKey(api_key);
if (!key) {
return {
content: [{
type: 'text',
text: JSON.stringify({ success: false, error: 'Invalid API key' })
}]
};
}
if (!aiService) {
return {
content: [{
type: 'text',
text: JSON.stringify({ success: false, error: 'AI service not configured. Set GCP_PROJECT_ID and ensure gcloud auth is set up.' })
}]
};
}
const lead = await database.getLead(profile_url);
if (!lead) {
return {
content: [{
type: 'text',
text: JSON.stringify({ success: false, error: 'Profile not analyzed yet' })
}]
};
}
const previousMessages = await database.getMessages(lead.id);
const result = await aiService.generateMessage(lead, {
valueProposition: value_proposition,
messageType: message_type,
previousMessages
});
return {
content: [{
type: 'text',
text: JSON.stringify(result, null, 2)
}]
};
}
case 'send_message': {
const { api_key, profile_url, message, is_connection_request } = args;
const key = await database.validateApiKey(api_key);
if (!key) {
return {
content: [{
type: 'text',
text: JSON.stringify({ success: false, error: 'Invalid API key' })
}]
};
}
const usage = await database.checkUsageLimit(key.id, 'message_send');
if (!usage.allowed) {
return {
content: [{
type: 'text',
text: JSON.stringify({ success: false, error: usage.reason })
}]
};
}
const result = await linkedinBot.sendMessage(profile_url, message, is_connection_request);
if (result.success) {
const lead = await database.getLead(profile_url);
if (lead) {
await database.saveMessage({
lead_id: lead.id,
user_id: key.id,
profile_url,
message_text: message,
sent_at: new Date().toISOString(),
sequence_stage: 0,
response_received: false
});
}
}
return {
content: [{
type: 'text',
text: JSON.stringify(result, null, 2)
}]
};
}
case 'create_followup_sequence': {
const { api_key, profile_url, initial_message, num_followups } = args;
const key = await database.validateApiKey(api_key);
if (!key) {
return {
content: [{
type: 'text',
text: JSON.stringify({ success: false, error: 'Invalid API key' })
}]
};
}
const usage = await database.checkUsageLimit(key.id, 'sequence_create');
if (!usage.allowed) {
return {
content: [{
type: 'text',
text: JSON.stringify({ success: false, error: usage.reason })
}]
};
}
if (!aiService) {
return {
content: [{
type: 'text',
text: JSON.stringify({ success: false, error: 'AI service not configured. Set GCP_PROJECT_ID and ensure gcloud auth is set up.' })
}]
};
}
const lead = await database.getLead(profile_url);
if (!lead) {
return {
content: [{
type: 'text',
text: JSON.stringify({ success: false, error: 'Profile not analyzed yet' })
}]
};
}
const sequenceResult = await aiService.generateFollowUpSequence(
lead,
initial_message,
num_followups || 3
);
if (sequenceResult.success) {
await database.createSequence({
lead_id: lead.id,
user_id: key.id,
profile_url,
messages: sequenceResult.sequence,
is_active: true
});
}
return {
content: [{
type: 'text',
text: JSON.stringify(sequenceResult, null, 2)
}]
};
}
default:
return {
content: [{
type: 'text',
text: JSON.stringify({ error: `Unknown tool: ${name}` })
}],
isError: true
};
}
} catch (error) {
return {
content: [{
type: 'text',
text: JSON.stringify({ error: error.message })
}],
isError: true
};
}
});
// Start server
async function main() {
await initializeServices();
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('LinkedIn Lead Automation MCP Server running');
}
main().catch(console.error);