# Directory Structure ``` ├── .gitignore ├── .python-version ├── Dockerfile ├── LICENSE ├── pyproject.toml ├── README.md ├── smithery.yaml └── src └── mcp_linkedin ├── __init__.py └── client.py ``` # Files -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- ``` 1 | 3.13 2 | ``` -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` 1 | explore.py 2 | __pycache__ ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown 1 | # MCP LinkedIn 2 | 3 | A Model Context Protocol (MCP) server that provides tools to interact with LinkedIn's Feeds and Job API. 4 | 5 | This is using unofficial LinkedIn API via [Linkedin-api](https://github.com/tomquirk/linkedin-api). Use at your own risk. 6 | 7 | <a href="https://smithery.ai/server/mcp-linkedin"><img alt="Smithery Badge" src="https://smithery.ai/badge/mcp-linkedin"></a> 8 | <a href="https://glama.ai/mcp/servers/dvbdubl2bg"><img width="380" height="200" src="https://glama.ai/mcp/servers/dvbdubl2bg/badge" alt="mcp-linkedin MCP server" /></a> 9 | 10 | ### Installing via Smithery 11 | 12 | To install LinkedIn Interaction Server for Claude Desktop automatically via [Smithery](https://smithery.ai/server/mcp-linkedin): 13 | 14 | ```bash 15 | npx -y @smithery/cli install mcp-linkedin --client claude 16 | ``` 17 | 18 | ## Configuration 19 | 20 | ```json 21 | { 22 | "mcpServers": { 23 | "linkedin": { 24 | "command": "uvx", 25 | "args": ["--from", "git+https://github.com/adhikasp/mcp-linkedin", "mcp-linkedin"], 26 | "env": { 27 | "LINKEDIN_EMAIL": "your_linkedin_email", 28 | "LINKEDIN_PASSWORD": "your_linkedin_password" 29 | } 30 | } 31 | } 32 | } 33 | ``` 34 | 35 | ## Sample usage 36 | 37 | Using [mcp-client-cli](https://github.com/adhikasp/mcp-client-cli) 38 | 39 | ### Get feed posts 40 | 41 | ``` 42 | $ llm whats happening in linkedin feed? Do not mention names 43 | 44 | I'll retrieve the recent LinkedIn feed posts without mentioning any specific names. 45 | 46 | Tool Calls: 47 | get_feed_posts 48 | Args: 49 | limit: 10 50 | 51 | Based on the LinkedIn feed, here are the key highlights without mentioning specific names: 52 | 53 | 1. Data Science & Cybersecurity Insights: 54 | - A post about a massive dataset from a major tech company 55 | - Includes 13 million pieces of evidence 56 | - Covers SIEM telemetry from 6,100 organizations 57 | - Represents 441 MITRE ATT&CK techniques 58 | 59 | 2. DevOps Community Event: 60 | - A local DevOps meetup was held at a tech company office 61 | - Discussed topics like AWS Lambda, Kubernetes scaling, Terraform 62 | - Highlighted the importance of networking and continuous learning 63 | - Shared experiences of transitioning between tech roles 64 | 65 | 3. Linux Engineering Perspectives: 66 | - Insights into what makes a great Linux engineer 67 | - Key skills include: 68 | * Command-line troubleshooting 69 | * Process management 70 | * Networking fundamentals 71 | * System architecture understanding 72 | * Task automation 73 | * Shell scripting 74 | 75 | 4. Professional Networking: 76 | - Some posts about job seeking 77 | - Discussions about career transitions 78 | - Sharing professional experiences and learnings 79 | 80 | 5. Tech Community Reflections: 81 | - Posts about inspirational figures 82 | - Discussions on professional growth 83 | - Reflections on learning and personal development 84 | 85 | The feed showcases a mix of technical insights, professional networking, career development, and community engagement across various tech domains. 86 | ``` 87 | 88 | ### Search for jobs 89 | 90 | ``` 91 | $ llm search for 3 data engineer jobs in jakarta. For each job check if it a good match for me by analyzing it against my resume in file resume.md. Do not mention company name in your result but only generic area where the company business is about 92 | 93 | I'll help you find data engineer jobs in Jakarta and analyze them against your resume. I'll break this down into steps: 94 | 95 | 1. First, I'll search for data engineer jobs in Jakarta 96 | 2. Then, I'll read your resume 97 | 3. Finally, I'll analyze the job matches 98 | 99 | Let's start by searching for jobs: 100 | 101 | Tool Calls: 102 | search_jobs 103 | Args: 104 | keywords: data engineer 105 | location: Jakarta 106 | limit: 3 107 | 108 | 109 | Now, I'll read your resume: 110 | 111 | Tool Calls: 112 | read_file 113 | Args: 114 | path: resume.md 115 | Secure MCP Filesystem Server running on stdio 116 | Allowed directories: [ '/home/adhikasp', '/mnt/e' ] 117 | 118 | 119 | Now, I'll analyze the jobs against your resume: 120 | 121 | 1. Job in Financial Technology Sector (Lead Backend Engineer): 122 | - Match Score: 85% 123 | - Strengths: 124 | * Requires experience in developing scalable microservices (aligns with your Grab experience) 125 | * Strong focus on cloud infrastructure (AWS, GCP) - matches your cloud skills 126 | * Requires experience with event-driven architecture and message brokers 127 | * Requires Go or Python programming (your proficient languages) 128 | - Potential Gaps: 129 | * Specific focus on credit risk and fraud detection might require additional domain knowledge 130 | * Emphasis on team leadership (6+ years experience leading teams) 131 | 132 | 2. Job in Financial Technology Sector (DevOps Engineer): 133 | - Match Score: 75% 134 | - Strengths: 135 | * Requires cloud platform experience (AWS, GCP) 136 | * Needs containerization skills (Docker, Kubernetes) 137 | * Requires scripting in Python and Bash 138 | * Needs infrastructure as code experience 139 | - Potential Gaps: 140 | * More DevOps focused compared to your backend engineering background 141 | * Specific monitoring tools might require additional learning 142 | 143 | 3. Job in Enterprise Collaboration Software (Solution Engineer): 144 | - Match Score: 60% 145 | - Strengths: 146 | * Requires strong communication skills 147 | * Technical consulting background 148 | * Experience with enterprise software 149 | - Potential Gaps: 150 | * Less technical compared to your current role 151 | * More sales-oriented solution engineering 152 | * Less hands-on technical development 153 | 154 | Recommendation: 155 | The first job (Lead Backend Engineer) appears to be the best match for your skills and experience. It closely aligns with your current work, particularly in terms of backend development, cloud infrastructure, and working with distributed systems. The role's requirements in Go/Python, microservices, and event-driven architecture are very compatible with your professional background. 156 | 157 | Would you like me to elaborate on any of these job analyses or provide more detailed insights? 158 | ``` 159 | ``` -------------------------------------------------------------------------------- /src/mcp_linkedin/__init__.py: -------------------------------------------------------------------------------- ```python 1 | ``` -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- ```toml 1 | [build-system] 2 | requires = ["hatchling"] 3 | build-backend = "hatchling.build" 4 | 5 | [project] 6 | name = "mcp-linkedin" 7 | version = "0.1.1" 8 | description = "MCP server to interact with LinkedIn" 9 | readme = "README.md" 10 | requires-python = ">=3.7" 11 | dependencies = [ 12 | "linkedin-api", 13 | "fastmcp", 14 | "requests", 15 | "uvicorn", 16 | ] 17 | authors = [ 18 | { name = "Adhika Setya Pramudita", email = "[email protected]" } 19 | ] 20 | 21 | [project.urls] 22 | Homepage = "https://github.com/adhikasp/mcp-linkedin" 23 | 24 | [project.scripts] 25 | mcp-linkedin = "mcp_linkedin.client:mcp.run" ``` -------------------------------------------------------------------------------- /smithery.yaml: -------------------------------------------------------------------------------- ```yaml 1 | # Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml 2 | 3 | startCommand: 4 | type: stdio 5 | configSchema: 6 | # JSON Schema defining the configuration options for the MCP. 7 | type: object 8 | required: 9 | - linkedinEmail 10 | - linkedinPassword 11 | properties: 12 | linkedinEmail: 13 | type: string 14 | description: Your LinkedIn email address 15 | linkedinPassword: 16 | type: string 17 | description: Your LinkedIn password 18 | commandFunction: 19 | # A function that produces the CLI command to start the MCP on stdio. 20 | |- 21 | (config) => ({command: 'uvicorn', args: ['mcp_linkedin.client:mcp.run', '--host', '0.0.0.0', '--port', '8000'], env: {LINKEDIN_EMAIL: config.linkedinEmail, LINKEDIN_PASSWORD: config.linkedinPassword}}) ``` -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- ```dockerfile 1 | # Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile 2 | # Use the official Python image with a slim variant 3 | FROM python:3.9-slim 4 | 5 | # Set the working directory in the container 6 | WORKDIR /app 7 | 8 | # Copy the pyproject.toml file and src directory into the container 9 | COPY pyproject.toml /app/ 10 | COPY src /app/src 11 | 12 | # Install the dependencies specified in the pyproject.toml 13 | RUN pip install --upgrade pip 14 | RUN pip install hatchling 15 | RUN pip install . 16 | 17 | # Set environment variables for LinkedIn credentials 18 | # These should be set during container run or through a Docker secret mechanism 19 | ENV LINKEDIN_EMAIL=your_linkedin_email 20 | ENV LINKEDIN_PASSWORD=your_linkedin_password 21 | 22 | # Set the entry point for the container 23 | ENTRYPOINT ["uvicorn", "mcp_linkedin.client:mcp.run", "--host", "0.0.0.0", "--port", "8000"] ``` -------------------------------------------------------------------------------- /src/mcp_linkedin/client.py: -------------------------------------------------------------------------------- ```python 1 | from linkedin_api import Linkedin 2 | from fastmcp import FastMCP 3 | import os 4 | import logging 5 | 6 | mcp = FastMCP("mcp-linkedin") 7 | logger = logging.getLogger(__name__) 8 | 9 | def get_client(): 10 | return Linkedin(os.getenv("LINKEDIN_EMAIL"), os.getenv("LINKEDIN_PASSWORD"), debug=True) 11 | 12 | @mcp.tool() 13 | def get_feed_posts(limit: int = 10, offset: int = 0) -> str: 14 | """ 15 | Retrieve LinkedIn feed posts. 16 | 17 | :return: List of feed post details 18 | """ 19 | client = get_client() 20 | try: 21 | post_urns = client.get_feed_posts(limit=limit, offset=offset) 22 | except Exception as e: 23 | logger.error(f"Error: {e}") 24 | return f"Error: {e}" 25 | 26 | posts = "" 27 | for urn in post_urns: 28 | posts += f"Post by {urn["author_name"]}: {urn["content"]}\n" 29 | 30 | return posts 31 | 32 | @mcp.tool() 33 | def search_jobs(keywords: str, limit: int = 3, offset: int = 0, location: str = '') -> str: 34 | """ 35 | Search for jobs on LinkedIn. 36 | 37 | :param keywords: Job search keywords 38 | :param limit: Maximum number of job results 39 | :param location: Optional location filter 40 | :return: List of job details 41 | """ 42 | client = get_client() 43 | jobs = client.search_jobs( 44 | keywords=keywords, 45 | location_name=location, 46 | limit=limit, 47 | offset=offset, 48 | ) 49 | job_results = "" 50 | for job in jobs: 51 | job_id = job["entityUrn"].split(":")[-1] 52 | job_data = client.get_job(job_id=job_id) 53 | 54 | job_title = job_data["title"] 55 | company_name = job_data["companyDetails"]["com.linkedin.voyager.deco.jobs.web.shared.WebCompactJobPostingCompany"]["companyResolutionResult"]["name"] 56 | job_description = job_data["description"]["text"] 57 | job_location = job_data["formattedLocation"] 58 | 59 | job_results += f"Job by {job_title} at {company_name} in {job_location}: {job_description}\n\n" 60 | 61 | return job_results 62 | 63 | if __name__ == "__main__": 64 | print(search_jobs(keywords="data engineer", location="Jakarta", limit=2)) ```