Skip to main content
Glama
linkedin-mcp

linkedin-mcp

by quxlzx
README.md
# linkedin-mcp

An MCP (Model Context Protocol) server that lets your AI assistant read and edit **your own** LinkedIn profile — by driving a real, logged-in Chrome through the Chrome DevTools Protocol (CDP).

Your assistant never sees your password: you log in once, by hand, in a visible Chrome window. Everything after that is the same UI you'd click yourself, automated.

```
AI client (Hermes / Claude Desktop / Cursor / Codex / ChatGPT)
        │  MCP (stdio or streamable HTTP)
        ▼
  server.py  ──CDP──▶  Chrome (your logged-in LinkedIn session)
```

> ## ⚠️ Read this first
> Automating LinkedIn **violates LinkedIn's User Agreement**. Accounts that automate get rate-limited, checkpointed, or restricted. This project is for learning and personal, low-volume use (fixing your headline, adding a project) — not scraping, bulk actions, or engagement farming. **You own the risk.** Keep edits rare, deliberate, and human-approved (every write tool requires `confirm=True` for exactly this reason).

## Features

| Tool | What it does |
|---|---|
| `auth_status` / `connect_tunnel` | CDP endpoint health; optional SSH tunnel to remote Chrome |
| `login_check` | Is the session logged in? |
| `profile_read` | Full profile text: About, Experience, Education, Skills, Projects, Organizations |
| `headline_update` / `about_update` | Edit intro headline / About section |
| `experience_update` | Edit an experience entry's title/description |
| `project_add` / `project_update` / `project_delete` | Manage projects |
| `skills_add` / `skills_delete` | Manage skills (newest-first ordering) |
| `organization_update` | Edit volunteer/organization entries |
| `dom_dump` / `screenshot` | Debug helpers for when LinkedIn changes its DOM |

All writes are gated: they refuse unless called with `confirm=True`, so your client must show you the change first.

## Setup

### 1. Prerequisites

- Python 3.11+
- Google Chrome on the machine that will run the browser
- An MCP client (Hermes, Claude Desktop, Cursor, Codex CLI, ChatGPT — see `CHATGPT.md`)

```bash
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
```

### 2. Start Chrome with a debug port (dedicated profile)

Use a **separate Chrome profile** so automation never touches your personal browser and your LinkedIn login persists across restarts:

**macOS / Linux**
```bash
google-chrome --remote-debugging-port=9222 \
  --user-data-dir="$HOME/.linkedin-mcp-profile" \
  --no-first-run https://www.linkedin.com/login
# macOS: "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" ...
```

**Windows (headless/SSH-launched)** — processes started from an SSH session are killed when the session closes and don't appear on your desktop. Use a one-shot scheduled task instead:
```bat
schtasks /Create /TN linkedin-mcp-chrome ^
 /TR "\"C:\Program Files\Google\Chrome\Application\chrome.exe\" --remote-debugging-port=9222 --user-data-dir=C:\linkedin-mcp-profile --remote-allow-origins=* --no-first-run https://www.linkedin.com/login" ^
 /SC ONCE /ST 00:00 /F
schtasks /Run /TN linkedin-mcp-chrome
```

**Chrome 136+ note:** remote debugging is ignored on the *default* profile — the custom `--user-data-dir` above is required, not optional.

### 3. Log in once

A Chrome window opens on LinkedIn's login page. Log in yourself. The profile directory persists — you won't do this again unless LinkedIn challenges you.

### 4. Configure

```bash
cp .env.example .env   # then edit
```

| Variable | Meaning |
|---|---|
| `LINKEDIN_PROFILE_URL` | Your profile URL, e.g. `https://www.linkedin.com/in/your-vanity-url/` (**required**) |
| `CDP_URL` | Where Chrome's debug port lives (default `http://127.0.0.1:9222`) |
| `SSH_HOST` | Optional: ssh alias of the machine running Chrome (tunnel via `connect_tunnel`) |

### 5. Wire into your client

**Hermes** (`~/.hermes/config.yaml`):
```yaml
mcp_servers:
  linkedin:
    command: /path/to/linkedin-mcp/.venv/bin/python
    args: [/path/to/linkedin-mcp/server.py]
    enabled: true
```

**Claude Desktop / Cursor / Codex** (stdio):
```json
{
  "mcpServers": {
    "linkedin": {
      "command": "/path/to/.venv/bin/python",
      "args": ["/path/to/server.py"],
      "env": { "LINKEDIN_PROFILE_URL": "https://www.linkedin.com/in/you/" }
    }
  }
}
```

**Remote / HTTP mode** — recommended config is **OAuth** (works with ChatGPT's "Connect" flow, Claude, and every serious MCP client):
```bash
OAUTH_USERNAME=you \
OAUTH_PASSWORD='generate-a-long-random-password' \
OAUTH_SECRET=$(python3 -c "import secrets;print(secrets.token_urlsafe(32))") \
LINKEDIN_MCP_PATH=$(python3 -c "import secrets;print(secrets.token_urlsafe(16))") \
PUBLIC_URL=https://your-tunnel-host \
.venv/bin/python server.py --http 8085 --public-url https://your-tunnel-host
```
Clients will pop your login page once, then hold a 30-day token. See **CHATGPT.md**.

Legacy alternative: `LINKEDIN_MCP_TOKEN=<bearer>` static token, or a bare secret `LINKEDIN_MCP_PATH` with no auth (not recommended).

## How it works (and why the selectors look weird)

LinkedIn's 2026 "workspace" profile UI is adversarial to automation:
- edit dialogs are native `<dialog open>` elements — `[role=dialog]` attribute queries find nothing
- sections lazy-mount only when the `main#workspace` container scrolls (window scroll does nothing)
- per-card edit buttons are hidden until hovered or revealed by a generic control
- dialog inputs often have **no aria-label** — fields are identified by their current value
- project dialogs have no URL field anymore (put repo links in the description)
- skills list is newest-first: to reorder, delete the block and re-add in reverse

`browser.py` encodes all of this. When LinkedIn changes again, `dom_dump` is your friend.

## Privacy

- `state/`, tokens, screenshots, and `.env` are gitignored — keep it that way
- the server stores nothing about you beyond the Chrome profile on your machine
- never commit your vanity URL into forks of this repo

## Troubleshooting

| Symptom | Fix |
|---|---|
| `connect_over_cdp` fails | Chrome isn't running with `--remote-debugging-port`, or the tunnel is down (`auth_status`) |
| Clicks do nothing | You're on a LinkedIn UI variant — `dom_dump("dialog[open]")` and adjust |
| Login loop / checkpoint | LinkedIn flagged the session — slow down, log in manually, wait |
| Windows: Chrome dies after SSH | Use the scheduled-task launcher above |

## License

MIT — see LICENSE. Not affiliated with or endorsed by LinkedIn.

Maintenance

ActivityMaintained
ResponsivenessSyncing