linkedin.ts•4.5 kB
import { Tool, CallToolResult } from '@modelcontextprotocol/sdk/types.js';
import { LinkedInScraperClient } from '../client.js';
import { ScrapeProfileArgs } from '../types.js';
/**
* Tool definition for LinkedIn profile scraping
*/
export const scrapeProfileToolDefinition: Tool = {
name: "scrape_linkedin_profile",
description: "Scrape a LinkedIn profile to extract comprehensive profile data including experience, education, skills, and contact information. Requires manual input of LinkedIn credentials.",
inputSchema: {
type: "object",
properties: {
url: {
type: "string",
description: "The LinkedIn profile URL to scrape (e.g., https://www.linkedin.com/in/username/)"
},
email: {
type: "string",
description: "LinkedIn account email for authentication"
},
password: {
type: "string",
description: "LinkedIn account password for authentication"
},
headless: {
type: "boolean",
description: "Run browser in headless mode (default: false). Set to true for server environments.",
default: false
}
},
required: ["url", "email", "password"],
},
};
/**
* Type guard for scrape profile arguments
*/
function isScrapeProfileArgs(args: unknown): args is ScrapeProfileArgs {
return (
typeof args === "object" &&
args !== null &&
"url" in args &&
"email" in args &&
"password" in args &&
typeof (args as any).url === "string" &&
typeof (args as any).email === "string" &&
typeof (args as any).password === "string"
);
}
/**
* Handles LinkedIn profile scraping tool calls
*/
export async function handleScrapeProfileTool(args: unknown): Promise<CallToolResult> {
try {
if (!args) {
throw new Error("No arguments provided");
}
if (!isScrapeProfileArgs(args)) {
throw new Error("Invalid arguments for scrape_linkedin_profile. Required: url, email, password");
}
// Validate URL format
if (!args.url.includes('linkedin.com')) {
throw new Error("Invalid LinkedIn URL. Must contain 'linkedin.com'");
}
// Validate email format
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(args.email)) {
throw new Error("Invalid email format");
}
const client = new LinkedInScraperClient();
const result = await client.scrapeProfile(
args.url,
args.email,
args.password,
args.headless || false
);
if (!result.success) {
return {
content: [
{
type: "text",
text: `LinkedIn scraping failed: ${result.error}`,
},
],
isError: true,
};
}
// Format the successful result
const profile = result.profile!;
const formattedResult = {
success: true,
timestamp: result.timestamp,
profile: {
url: profile.url,
name: profile.name,
headline: profile.headline,
location: profile.location,
about: profile.about,
experience_count: profile.experiences.length,
experiences: profile.experiences,
education_count: profile.education.length,
education: profile.education,
skills_count: profile.skills.length,
skills: profile.skills,
websites: profile.websites,
email: profile.email
}
};
return {
content: [
{
type: "text",
text: `Successfully scraped LinkedIn profile!\n\n${JSON.stringify(formattedResult, null, 2)}`,
},
],
isError: false,
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Error scraping LinkedIn profile: ${error instanceof Error ? error.message : String(error)}`,
},
],
isError: true,
};
}
}