LinkedIn MCP Server
README.md
# LinkedIn MCP Server
A Model Context Protocol (MCP) server that automates LinkedIn job search and profile editing via Playwright browser automation. Built to help DS/ML job seekers keep their LinkedIn profile sharp and run targeted job searches without touching the UI.
---
## Features
### Job Search
- Search jobs by keywords, location, date, and type
- One-click DS/ML internship and new-grad job search presets
- Fetch full job descriptions from listing URLs
### Profile Editing
- Update experience entries (title, company, dates, description)
- Update education entries (GPA, description)
- Update project entries (name, description)
- Update headline and about section
---
## Setup
### 1. Install dependencies
```bash
pip install -r requirements.txt
playwright install chromium
```
### 2. Log in to LinkedIn
Run the server and call `manual_login` via Claude Code. This opens a real browser window — log in manually, then save the session:
```python
# Claude Code will call:
manual_login() # opens browser → you log in manually
check_session() # verifies session is active
```
The session is saved to `session.json` and reused automatically on future runs.
### 3. Register as a Claude Code MCP server
Add this to your Claude Code MCP config (`~/.claude/mcp_servers.json` or via `claude mcp add`):
```json
{
"linkedin": {
"command": "python",
"args": ["server.py"],
"cwd": "/path/to/linkedin_mcp"
}
}
```
Or run ad-hoc:
```bash
python server.py
```
---
## Project Structure
```
linkedin_mcp/
├── server.py # FastMCP server — all tools + resume/profile data
├── browser.py # Playwright automation class (LinkedInBrowser)
├── requirements.txt # Python dependencies
├── .gitignore
└── README.md
```
---
## Tools Reference
| Tool | Description |
|------|-------------|
| `check_session` | Check if LinkedIn session is still active |
| `manual_login` | Open browser for manual login and save session |
| `get_profile` | Scrape current profile (name, headline, about, experience, education, skills) |
| `suggest_profile_improvements` | Compare profile against resume data and return suggestions |
| `update_experience` | Update all experience entries with titles, dates, and descriptions |
| `update_education` | Update education entries with GPA and descriptions |
| `update_projects` | Update all project entries with names and descriptions |
| `apply_profile_improvements` | Update headline and about section |
| `search_jobs` | Search jobs by keyword, location, date filter, and job type |
| `search_recent_internships` | Search DS/ML/AI internships posted in the last week |
| `search_new_grad_jobs` | Search entry-level DS/ML full-time roles |
| `get_job_details` | Fetch full job description from a listing URL |
---
## Key Technical Notes
- **React inputs** — uses Playwright `.fill()`, not JS `.value` assignment (JS bypasses React state)
- **Obfuscated field IDs** — field IDs are dynamically resolved by finding label elements by text, then reading their `htmlFor` attribute
- **LinkedIn edit URLs** — discovered dynamically from `<a>` tags on details pages; edit form renders immediately when navigated to directly
- **SPA routing** — LinkedIn uses `history.pushState`; polls `page.url` instead of waiting for Playwright navigation events
- **Experience description** — `contenteditable` div filled via `execCommand('insertText')`; education and project descriptions are regular `<textarea>` elements
---
## Dependencies
| Package | Purpose |
|---------|---------|
| `mcp>=1.0.0` | Model Context Protocol server framework (FastMCP) |
| `playwright>=1.40.0` | Browser automation |
| `python-dotenv>=1.0.0` | Optional env file support |
---
## Notes
- `session.json` stores your LinkedIn auth cookies — keep it private, it is excluded from git via `.gitignore`
- Debug screenshots (`*.png`) are written to the project folder at runtime and are also gitignored
- Tested on LinkedIn as of June 2026; LinkedIn's DOM can change — if selectors break, check `browser.py` and update aria-labels or placeholder text
This server cannot be deployed
Maintenance
ActivityInactive
ResponsivenessNo issues