{
  "markdown": "<!-- mcp-name: io.github.JohannsenLum/linkedin-api-mcp -->\n\n# linkedin-api-mcp\n\n> **Disclaimer: This is an independent, community project. It is not affiliated with, authorized by, endorsed by, or sponsored by LinkedIn Corporation or Microsoft. \"LinkedIn\" is a registered trademark of LinkedIn Corporation and is used here only descriptively to identify the third-party service this software interoperates with.**\n\nAn MCP server for LinkedIn. It drives a real, headless browser\n([patchright](https://github.com/Kaliiiiiiiiii-Vinyzu/patchright-python), an\nundetected fork of Playwright) using **your own logged-in LinkedIn session cookie**.\nThere is no scraping API, no credential stuffing, no bypass of LinkedIn's login. Your\nagent gets 12 read tools (profiles, companies, jobs, posts, your inbox) and two write\ntools, kept deliberately separate: sending a message and sending a connection\nrequest. Every action goes through a single browser session, one at a time, paced\nagainst limits you control. See [Safety](#safety) below.\n\n**Documentation: [mcp.johannsenlum.com/linkedin](https://mcp.johannsenlum.com/linkedin)**\n\n[![PyPI](https://img.shields.io/pypi/v/linkedin-api-mcp)](https://pypi.org/project/linkedin-api-mcp/)\n[![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue)](https://www.python.org/downloads/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/JohannsenLum/linkedin-api-mcp/blob/main/LICENSE)\n[![MCP Registry](https://img.shields.io/badge/MCP_Registry-io.github.JohannsenLum%2Flinkedin--api--mcp-1f6feb)](https://registry.modelcontextprotocol.io)\n[![GitHub stars](https://img.shields.io/github/stars/JohannsenLum/linkedin-api-mcp?style=social)](https://github.com/JohannsenLum/linkedin-api-mcp)\n\n<p align=\"center\">\n  <img src=\"https://raw.githubusercontent.com/JohannsenLum/linkedin-api-mcp/main/assets/header.svg\"\n       alt=\"Terminal demo of linkedin-api-mcp: an agent calls search_people to find a member, then send_message to message them, then reads the conversation back to confirm the message actually arrived. 14 tools, 68 tests, MIT licence.\"\n       width=\"840\">\n</p>\n\n## Install (one-click)\n\nDeeplinks exist for Cursor and VS Code only; no other client has a documented\ninstall-link format. These prefill the command below, nothing else needs filling\nin since the cookie lives in your OS keyring, not an environment variable.\n\n[![Add to Cursor](https://img.shields.io/badge/Cursor-Add_MCP_Server-000000?style=flat-square&logo=cursor&logoColor=white)](https://cursor.com/en/install-mcp?name=linkedin&config=eyJjb21tYW5kIjoidXZ4IiwiYXJncyI6WyJsaW5rZWRpbi1hcGktbWNwIl19)\n[![Add to VS Code](https://img.shields.io/badge/VS_Code-Install_Server-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://vscode.dev/redirect/mcp/install?name=linkedin&config=%7B%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22linkedin-api-mcp%22%5D%7D)\n\n### All clients\n\n| Client | Deeplink? |\n|---|---|\n| [Claude Code](#config-claude-code) | no, one-line command |\n| [Claude Desktop](#config-claude-desktop) | no |\n| [Cursor](#config-cursor) | yes, above |\n| [VS Code](#config-vscode) | yes, above |\n| [Codex CLI](#config-codex) | no |\n| [Zed](#config-zed) | no |\n| [Windsurf](#config-windsurf) | no (Windsurf only resolves servers in its own registry) |\n\n<a id=\"config-claude-code\"></a>\n<details>\n<summary><strong>Claude Code</strong></summary>\n\n```bash\nclaude mcp add linkedin -- uvx linkedin-api-mcp\n```\n\n</details>\n\n<a id=\"config-claude-desktop\"></a>\n<details>\n<summary><strong>Claude Desktop</strong>: <code>claude_desktop_config.json</code></summary>\n\nmacOS: `~/Library/Application Support/Claude/claude_desktop_config.json`\nWindows: `%APPDATA%\\Claude\\claude_desktop_config.json`\n\nNo one-click install exists for Claude Desktop (it installs `.mcpb` bundles, not\ndeeplinks). Copy this JSON in via **Settings → Developer → Edit Config**:\n\n```jsonc\n{\n  \"mcpServers\": {\n    \"linkedin\": {\n      \"command\": \"uvx\",\n      \"args\": [\"linkedin-api-mcp\"]\n    }\n  }\n}\n```\n\n</details>\n\n<a id=\"config-cursor\"></a>\n<details>\n<summary><strong>Cursor</strong>: <code>~/.cursor/mcp.json</code></summary>\n\nFallback for the button above, or if you'd rather paste it directly:\n\n```jsonc\n{\n  \"mcpServers\": {\n    \"linkedin\": {\n      \"command\": \"uvx\",\n      \"args\": [\"linkedin-api-mcp\"]\n    }\n  }\n}\n```\n\n</details>\n\n<a id=\"config-vscode\"></a>\n<details>\n<summary><strong>VS Code</strong>: <code>.vscode/mcp.json</code></summary>\n\nFallback for the button above, or if you'd rather paste it directly. Note VS\nCode uses a `servers` key, not `mcpServers`:\n\n```jsonc\n{\n  \"servers\": {\n    \"linkedin\": {\n      \"type\": \"stdio\",\n      \"command\": \"uvx\",\n      \"args\": [\"linkedin-api-mcp\"]\n    }\n  }\n}\n```\n\n</details>\n\n<a id=\"config-codex\"></a>\n<details>\n<summary><strong>Codex CLI</strong>: <code>~/.codex/config.toml</code></summary>\n\n```toml\n[mcp_servers.linkedin]\ncommand = \"uvx\"\nargs = [\"linkedin-api-mcp\"]\n```\n\nCodex CLI's config schema has changed across versions and this block is not\nindependently verified against a live install. If it doesn't load, the reliable\npath is running `uvx linkedin-api-mcp` yourself and pointing Codex at whatever\nits current stdio-server config expects.\n\n</details>\n\n<a id=\"config-zed\"></a>\n<details>\n<summary><strong>Zed</strong>: <code>settings.json</code></summary>\n\nNo deeplink exists for Zed. Add this under `context_servers` in your Zed\nsettings:\n\n```jsonc\n{\n  \"context_servers\": {\n    \"linkedin\": {\n      \"source\": \"custom\",\n      \"command\": \"uvx\",\n      \"args\": [\"linkedin-api-mcp\"]\n    }\n  }\n}\n```\n\n</details>\n\n<a id=\"config-windsurf\"></a>\n<details>\n<summary><strong>Windsurf</strong>: <code>~/.codeium/windsurf/mcp_config.json</code></summary>\n\nNo deeplink exists for Windsurf. It only resolves servers from its own\nregistry, so this has to be pasted in manually via **Windsurf Settings → MCP\nServers → Edit raw config**:\n\n```jsonc\n{\n  \"mcpServers\": {\n    \"linkedin\": {\n      \"command\": \"uvx\",\n      \"args\": [\"linkedin-api-mcp\"]\n    }\n  }\n}\n```\n\n</details>\n\nIf you'd rather not rely on the keyring in a given client (a sandboxed\nenvironment, or CI), pass the cookie directly instead of running `auth`:\n\n```jsonc\n{\n  \"mcpServers\": {\n    \"linkedin\": {\n      \"command\": \"uvx\",\n      \"args\": [\"linkedin-api-mcp\"],\n      \"env\": { \"LINKEDIN_COOKIE\": \"your-li_at-value\" }\n    }\n  }\n}\n```\n\nAnywhere you do this, treat that config file with the same care as the cookie\nitself: don't commit it, and restrict its permissions.\n\n## Getting your session cookie\n\nLinkedIn doesn't offer an API key for this kind of access, so the server uses the\nsame session cookie your browser already holds.\n\n1. One-time: install the Chromium build the server drives.\n   ```bash\n   uvx --from linkedin-api-mcp patchright install chromium\n   ```\n2. Log in to [linkedin.com](https://www.linkedin.com) in a normal browser.\n3. Open DevTools (`F12` or `Cmd+Opt+I`) → **Application** → **Cookies** →\n   `https://www.linkedin.com`.\n4. Find the row named `li_at` and copy its **Value**.\n\n> [!WARNING]\n> **This cookie *is* your LinkedIn login.** Anyone who has it can act as you:\n> read your messages, message your connections, see everything your account can\n> see, without needing your password. It survives a password change, and it\n> cannot be revoked from any session list LinkedIn shows you. Never paste it\n> into a chat, a commit, an issue, a screenshot, or anywhere other than\n> `linkedin-api-mcp auth`.\n\n5. Store it:\n   ```bash\n   uvx linkedin-api-mcp auth\n   ```\n   Paste the value in when prompted; the terminal will not echo it back. By\n   default it's stored in your OS keyring (Keychain, Windows Credential Manager,\n   or Secret Service on Linux), never written to a config file.\n6. Verify LinkedIn accepts it before wiring up a client:\n   ```bash\n   uvx linkedin-api-mcp --test\n   ```\n   Prints the server's redacted configuration and confirms the session is live.\n   It never prints the cookie itself.\n\n## Tools\n\n| Tool | Description |\n|---|---|\n| `get_profile` | Fetch a member's profile (headline, about, experience, education, skills) by URL or public identifier. |\n| `get_my_profile` | Fetch the profile of the account this server is logged in as. |\n| `search_people` | Search LinkedIn members by keyword. |\n| `get_inbox` | List recent message thread previews. |\n| `get_conversation` | Fetch the full message history of one thread, by id. |\n| `search_conversations` | Search your message threads by participant or keyword. |\n| `send_message` ✏️ | **Write.** Sends a real message to another member from your account. |\n| `connect` ✏️ | **Write.** Sends a real connection invitation to another member from your account. |\n| `get_company` | Fetch a company page (about, size, industry, recent posts). |\n| `search_companies` | Search LinkedIn companies by keyword. |\n| `search_jobs` | Search job postings by keyword and optional location. |\n| `get_job` | Fetch one job posting in full. |\n| `search_posts` | Search LinkedIn feed posts by keyword. |\n| `linkedin_status` | Check session validity and current usage against the rate limits in [Safety](#safety). |\n\n14 tools: 12 read, 2 write. `send_message` and `connect` are the only two that\nchange anything, and both are visible to another real person the moment they\nrun: there is no draft, preview, or undo step. Everything else only reads what\nyour account can already see. Covered by 68 tests.\n\n## What's verified, and what isn't\n\nThis is a days-old project. Rather than claim everything works, here's what's\nactually been checked against a live account, and what hasn't.\n\n| Status | Tool / behaviour | Note |\n|---|---|---|\n| Verified live | `get_my_profile`, `get_profile`, `search_people`, `get_company`, `search_companies`, `search_jobs`, `search_posts`, `get_inbox` | Called against a real account and returned real data. |\n| Verified live | `send_message` ✏️ | A real message was sent, then the thread was read back to confirm it arrived. |\n| Not yet verified | `connect` ✏️, `get_conversation`, `search_conversations`, `get_job`, `linkedin_status` | Implemented and tested, but not yet exercised against a live account by hand. |\n| Known gap | `conversation_id` from `get_inbox` | Comes back `null`. LinkedIn binds inbox rows to in-memory JS objects rather than URLs, so there's no id to read out of the page. |\n| Known gap | Reaction and comment counts on `search_posts` | Come back `null`. |\n| Known gap | `experience` and `education` on `get_profile` | LinkedIn loads these sections only on scroll; they currently come back empty. |\n\n## Safety\n\n- **A serialised action queue.** Every tool call, including `linkedin_status`,\n  passes through the same queue before it touches the browser: one action at a\n  time, never in parallel.\n- **A minimum interval and an hourly ceiling**, both enforced, not just claimed:\n\n  | Setting | Default | Env var | Effect |\n  |---|---|---|---|\n  | Minimum interval between actions | 2 seconds | `LINKEDIN_MIN_INTERVAL` | Calls are paced against the previous one, not run back-to-back. |\n  | Actions per rolling hour | 120 | `LINKEDIN_MAX_PER_HOUR` | Once hit, further calls fail immediately with a `rate_limited` error rather than queueing or sleeping. |\n\n  The ceiling is local to this server: it exists to stop a looping agent from\n  generating a burst of LinkedIn traffic, not because LinkedIn told us these\n  numbers. Lowering them is always safe; raising them is you deciding you're\n  willing to accept more risk than the defaults assume.\n- **Prompt-injection fencing on all scraped free text.** Anything read off a\n  LinkedIn page (a headline, an about section, a message) passes through your\n  agent as data. It is fenced before your agent sees it, so text on a profile\n  or in a message cannot pose as an instruction.\n- **The cookie lives in your OS keyring**, not a config file, by default. See\n  [Getting your session cookie](#getting-your-session-cookie).\n- **`send_message` proves delivery before reporting success.** It polls the\n  conversation until the sent text actually appears, and raises\n  `send_unconfirmed` rather than a false `{\"sent\": true}` if it doesn't.\n\nOther environment variables the server reads: `LINKEDIN_COOKIE` (overrides the\nkeyring), `LINKEDIN_HEADLESS` (default `true`), `LINKEDIN_NAV_TIMEOUT_MS` (default\n`30000`).\n\n## FAQ\n\n**Is this safe to use? Will I get banned?**\n\nThis tool controls a real browser session; it doesn't exploit undocumented APIs or bypass authentication. LinkedIn's User Agreement prohibits automated access, and accounts using automated tools can be restricted or banned. Use at your own risk; there is no guarantee of account safety. If you encounter any issues, let me know in the Discussions.\n\n**What if my agents execute too many actions?**\n\nTool calls run sequentially through a queue. You are responsible for the volume of automation you run; use it sparingly and prompt your agents responsibly.\n\n## Licence\n\n[MIT](LICENSE) © 2026 Johannsen Lum.\n\nUse it, change it, redistribute it, build something commercial on it: the only\ncondition is that you keep the copyright notice and licence text. It comes with no\nwarranty of any kind.\n\nContributions are accepted under the same licence.\n\n## Contributing\n\nIssues and pull requests are welcome:\n[github.com/JohannsenLum/linkedin-api-mcp](https://github.com/JohannsenLum/linkedin-api-mcp).\nChanges are recorded in [CHANGELOG.md](CHANGELOG.md).\n\n### Running the tests\n\nInstall the development dependencies and run pytest from the repository root:\n\n```bash\npip install -e \".[dev]\"\npython -m pytest -q\n```\n\nThe default test suite does not need Chromium, a LinkedIn account,\n`LINKEDIN_COOKIE`, or network access. Browser interactions are replaced by the test\ndoubles in [`tests/fakes.py`](tests/fakes.py), which parse canned HTML and expose\nthe small Playwright surface used by the tools. For a parsing-test example, see\n[`tests/test_people_parsing.py`](tests/test_people_parsing.py).\n\nThis is an independent project, not affiliated with LinkedIn Corporation. See the\ndisclaimer at the top of this document.\n",
  "bytes": 14071,
  "sha": "83e441c1fc3304ac3011baee2917f840e98808c64311621abd29c072b4e0eeb6",
  "repo_slug": "johannsenlum/linkedin-api-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_johannsenlum_linkedin_api_mcp_55481611/readme"
}