linkedin-mcp
by ml-lubich
README.md
# linkedin-mcp
`linkedin-mcp` is an unofficial LinkedIn CLI **and MCP server** in one Python
package. Every command and every MCP tool call the same shared core
(`linkedin_mcp/core.py`), so the two surfaces never drift apart.
It consolidates two prior tools into one:
- **[linkedin-cli](https://github.com/frizynn/linkedin-cli)** (by
[frizynn](https://github.com/frizynn), MIT) — the Voyager-API read/write
surface: `feed`, `search`, `profile`, `profile-posts`, `activity`, `post`,
`react`, `unreact`, `save`, `unsave`, `comment`. This repo forked that
project and built on top of it.
- **[linkedin-agent](https://github.com/ml-lubich/linkedin-agent)** — the
CDP surface that drives your own already-logged-in Chrome for messaging,
referrals, and posting: `doctor`, `classify`, `post-cdp`, `messages`,
`referral`. That project is now absorbed into this one.
It is built around a real LinkedIn web session (Voyager) or your own
logged-in Chrome (CDP) — not OAuth. That makes it practical for personal
automation, but session handling, browser behavior, and endpoint stability
all matter.
## What it does
**Voyager API surface** (a real session, `requests`/Playwright fallback):
- Read the authenticated home feed, search people/posts, fetch a profile or
its posts, inspect an activity, emit JSON for scripting.
- Write: publish a post, react/unreact, save/unsave, comment — all through a
Playwright browser-automation fallback.
**CDP agent surface** (your own already-open, already-logged-in Chrome, via
[own-chrome](https://github.com/ml-lubich/own-chrome)):
- `doctor` — environment/session health check.
- `classify` — is an inbound message hiring-shaped, excluded, already-referred?
- `post-cdp draft` / `post-cdp publish` — lint-then-publish a feed post.
- `messages read` / `messages send` — read/reply in the open thread, with an
optional file attachment.
- `referral draft` / `referral send` — draft/send a referral message with a
resume attached, paced and deduped through a rate governor.
**MCP server** (`linkedin-mcp serve`): every command above as an MCP tool,
for Claude/Cursor/any MCP client.
## Confirm gate — nothing sends without it
Every write/send/publish command requires an explicit `--confirm` (CLI) or
`confirm: true` (MCP tool). Two families, two mechanisms:
- **Voyager writes** (`post`, `react`, `unreact`, `save`, `unsave`, `comment`)
refuse instantly — `ConfirmRequiredError`, nothing touched — without it.
- **CDP agent writes** (`post-cdp publish`, `messages send`, `referral send`)
still fill the browser compose box/composer as a preview, then refuse to
click Send/Post — `SendNotConfirmedError` — without it.
## Installation
```bash
git clone https://github.com/ml-lubich/linkedin-mcp.git
cd linkedin-mcp
uv tool install --force --from . linkedin-mcp
```
Installs two identical console scripts, `linkedin` and `linkedin-mcp`. If you
previously had the standalone tools installed, remove them so their command
names don't shadow this one:
```bash
uv tool uninstall linkedin-cli
uv tool uninstall linkedin-agent
```
Install Playwright's browser if you use the Voyager write fallback:
```bash
uv run playwright install chromium
```
## Quick start (Voyager side)
```bash
export LINKEDIN_COOKIE_HEADER='li_at=...; JSESSIONID="ajax:..."; ...'
linkedin auth-status
linkedin feed --max 10
linkedin profile satyanadella
linkedin search "AI engineer" --max 10
```
## Quick start (CDP agent side)
Chrome must be listening on its debugging port (default `9222`); quit Chrome
fully first, then (macOS, since Chrome 136+ ignores the flag on the default
profile):
```bash
open -a "Google Chrome" --args --remote-debugging-port=9222 --user-data-dir="$HOME/chrome-debug"
```
```bash
linkedin doctor # environment/session check
linkedin messages read --limit 20 # read the open thread
linkedin messages send "thanks, that works" --confirm
linkedin post-cdp draft "shipped a small thing this week" # anti-cringe lint, no browser
linkedin post-cdp publish "shipped a small thing this week" --confirm
```
Config for the CDP side lives at `~/.config/linkedin-agent/config.toml`
(unchanged from linkedin-agent — copy `config.example.toml` from that repo).
It holds the CDP port, your display name, the referral identity, and
exclusion lists. Nothing personal is hardcoded here.
## MCP server
```bash
linkedin-mcp serve # stdio transport
```
Claude Code / Cursor config:
```json
{
"mcpServers": {
"linkedin": { "command": "linkedin-mcp", "args": ["serve"] }
}
}
```
21 tools are exposed, one per CLI command (dashes/spaces -> underscores),
except `serve` (starts the server itself) and `auth env` (the one command
that reads a raw cookie value back out — never exposed as a tool; capture the
session with the `auth_capture` tool or `linkedin auth capture` instead).
`tests/test_mcp_parity.py` fails the build if a command and its tool ever
drift apart.
## Authentication (Voyager side)
Resolved in this order:
1. `LINKEDIN_COOKIE_HEADER` (most reliable)
2. `LINKEDIN_LI_AT` + `LINKEDIN_JSESSIONID`
3. Browser cookie extraction (Chrome, Chromium, Brave, Edge, Firefox)
4. `linkedin auth capture` — CDP-based, for Chrome 127+, where its app-bound
cookie encryption blocks #3. Polls a CDP-attached Chrome until logged in,
then saves the cookie to `~/.config/linkedin-mcp/cookies` (mode `0600`).
Never prints a cookie value. `linkedin auth env` is the one command that
does, deliberately, to export `LINKEDIN_COOKIE_HEADER`.
```bash
export LINKEDIN_LI_AT='AQ...'
export LINKEDIN_JSESSIONID='"ajax:123456789"'
export LINKEDIN_BROWSER='chrome'
export LINKEDIN_HEADLESS='1'
export LINKEDIN_PROXY='http://127.0.0.1:7890'
export LINKEDIN_CONFIG="$PWD/config.yaml"
```
## Commands
```bash
linkedin auth-status
linkedin auth capture
linkedin auth env
linkedin feed --max 20 --json
linkedin search "product manager" --max 10
linkedin profile satyanadella --json
linkedin profile-posts satyanadella --max 20
linkedin activity urn:li:activity:123
linkedin post "hello from linkedin-mcp" --confirm
linkedin react urn:li:activity:123 --type like --confirm
linkedin unreact urn:li:activity:123 --confirm
linkedin save urn:li:activity:123 --confirm
linkedin unsave urn:li:activity:123 --confirm
linkedin comment urn:li:activity:123 "nice post" --confirm
linkedin doctor
linkedin classify "InMail: Senior AI Engineer role" --name "Jordan Lee"
linkedin post-cdp draft "..."
linkedin post-cdp publish "..." --confirm
linkedin messages read --limit 40
linkedin messages send "..." --confirm
linkedin referral draft "Jordan Lee" "..." --headline "Talent @ Acme"
linkedin referral send "Jordan Lee" "<thread url>" --confirm
linkedin-mcp serve
```
## Skill
This repo ships one Codex/Claude skill in [`skills/linkedin-mcp/`](./skills/linkedin-mcp/)
covering command selection, the confirm gate, auth troubleshooting, and write
workflows for both surfaces.
## Configuration (Voyager side)
The repository includes a sample [`config.yaml`](./config.yaml):
```yaml
fetch:
count: 20
filter:
enabled: false
mode: "recent"
browser:
preferred: "chrome"
fallback_enabled: true
headless: true
rate_limit:
request_delay: 1.25
max_retries: 3
retry_base_delay: 3.0
write_delay_min: 1.5
write_delay_max: 4.0
timeout: 20.0
```
## Development
```bash
uv sync --extra dev
uv run playwright install chromium
```
Run checks (see [`docs/TESTING.md`](./docs/TESTING.md) for the full gate):
```bash
uv run ruff check .
uv run pytest -q --cov=linkedin_mcp
```
## Important notes
- Unofficial, not affiliated with LinkedIn.
- LinkedIn can change internal web endpoints without notice.
- Session cookies are credentials. Treat them like passwords — never commit
them, print them, or paste them into an issue/PR.
- Never invent a referral target, resume, or pitch — they come only from
config/env vars.
- Do not use this for spam, scraping at abusive rates, or automating
repeated posting/engagement loops.
## Security and Privacy
See [`SECURITY.md`](./SECURITY.md) for reporting guidance.
## Contributing
Before opening a pull request, read [`CONTRIBUTING.md`](./CONTRIBUTING.md),
[`CODE_OF_CONDUCT.md`](./CODE_OF_CONDUCT.md), and [`SECURITY.md`](./SECURITY.md).
## License
MIT. See [`LICENSE`](./LICENSE). Portions of this project originate from
[frizynn/linkedin-cli](https://github.com/frizynn/linkedin-cli) (MIT).
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues