Skip to main content
Glama
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