LinkedIn MCP Server

pauling-ai/linkedin-mcp-server
3 starsApache-2.0Community

Install to Claude Code

This server doesn't publish a one-line install command. Follow the setup in the source repository.

Summary

Enables natural language interaction with LinkedIn, including profile retrieval, job searching, messaging, and post engagement.

README.md

LinkedIn MCP Server (Fork)

Personal fork of stickerdaniel/linkedin-mcp-server with additional tools for messaging and post engagement.

Added Tools

| Tool | Description | |------|-------------| | send_message | Send a LinkedIn message to a 1st-degree connection | | send_connection_request | Send a connection request, with or without a note | | follow_company | Follow a LinkedIn company page | | get_inbox | Read recent inbox conversations (supports unread_only) | | get_conversation | Read the full message thread with a specific person | | get_post_likers | Get the list of people who reacted to a LinkedIn post | | get_last_post | Get the most recent organic post from a person's profile |

Detail Mode

All page-scraping tools (get_person_profile, search_people, get_company_profile, get_company_posts, get_job_details, search_jobs) accept a detail parameter:

  • "basic" (default) — truncates each section's raw text to BASIC_SECTION_MAX_CHARS (2000 chars). LinkedIn front-loads the most important content (name, headline, location, about), so this captures essentials while staying well within LLM context limits. References, job IDs, and post URNs are always kept.
  • "full" — returns the complete raw page text, current behaviour.

If basic mode doesn't return enough information, call the tool again with detail="full".

The truncation limit is a single constant in linkedin_mcp_server/tools/utils.py:

BASIC_SECTION_MAX_CHARS = 2000

All Tools

People

| Tool | Description | |------|-------------| | get_person_profile | Get profile info with optional sections: experience, education, interests, honors, languages, contact_info, posts. Supports detail param. | | search_people | Search for people by keywords and optional location. Supports detail param. | | get_last_post | Get the most recent organic post from a person (visits profile first, then Posts activity tab; returns text, timestamp, URL, and URN) | | send_connection_request | Send a connection request, with or without a note (~300 char limit); handles profiles where Connect is in the More dropdown | | send_message | Send a message to a 1st-degree connection (also used to reply to existing threads) | | follow_person | Follow a person's profile | | check_follow | Check whether you are following a person | | check_connection | Check whether you are connected to a person |

Companies

| Tool | Description | |------|-------------| | get_company_profile | Get company info with optional sections: posts, jobs. Supports detail param. | | get_company_posts | Get recent posts from a company's feed; returns post URNs plus lightweight posts[] metadata including posted_at. Supports detail param. | | follow_company | Follow a company page | | check_follow_company | Check whether you are following a company |

Jobs

| Tool | Description | |------|-------------| | search_jobs | Search for jobs by keywords and optional location. Supports detail param. | | get_job_details | Get full details of a specific job posting. Supports detail param. |

Messaging

| Tool | Description | |------|-------------| | get_inbox | List recent inbox conversations with name, preview, timestamp, and unread flag; pass unread_only=true to filter | | get_conversation | Read the full message thread with a person by their LinkedIn username |

Posts

| Tool | Description | |------|-------------| | get_post_likers | Get the list of people who reacted to a post (by feed URL or post URN) | | get_last_post | Get the most recent organic post from a person (no likes, no reshares) |

Session

| Tool | Description | |------|-------------| | close_session | Close the browser session and clean up resources |

Setup

Prerequisites: Python 3.12+ and uv installed

# 1. Clone the repo
git clone https://github.com/pauling-ai/linkedin-mcp-server
cd linkedin-mcp-server

# 2. Install dependencies
uv sync

# 3. Log in (first time only)
uv run -m linkedin_mcp_server --login

This opens a browser for you to log in manually (5 minute timeout for 2FA, captcha, etc.). The browser profile is saved to ~/.linkedin-mcp/profile/ and persists across sessions.

Installing into another project

To use this server from another Python project or Claude Code workspace, install it in editable mode pointing at your local clone:

# With uv (recommended)
uv pip install -e /path/to/linkedin-mcp-server

# Or with pip
pip install -e /path/to/linkedin-mcp-server

Editable mode means changes to the repo are immediately reflected — no reinstall needed.

Then point your MCP config at the installed binary:

# Find the installed binary path
which linkedin-scraper-mcp

Claude Code / MCP Configuration

After installing (either in a project venv or globally), add to your .mcp.json:

{
  "mcpServers": {
    "linkedin": {
      "command": "/path/to/venv/bin/linkedin-scraper-mcp",
      "args": ["--project-auth", "--delay-between-linkedin-calls", "1.5", "--delay-jitter", "0.5"],
      "env": {
        "LOG_LEVEL": "INFO"
      }
    }
  }
}

Or run directly from the repo with uv (no install needed):

{
  "mcpServers": {
    "linkedin": {
      "command": "uv",
      "args": ["--directory", "/path/to/linkedin-mcp-server", "run", "-m", "linkedin_mcp_server", "--project-auth", "--delay-between-linkedin-calls", "1.5", "--delay-jitter", "0.5"],
      "env": {
        "LOG_LEVEL": "INFO"
      }
    }
  }
}

The uv approach is the simplest — just update the --directory path and you're done.

Use --slow-mo only when debugging or when you want to watch browser actions happen more slowly. For LinkedIn-facing pacing between tool calls, prefer --delay-between-linkedin-calls / --delay-jitter. The smaller --human-delay-min-ms / --human-delay-max-ms settings still control waits inside individual browser workflows.

If you want a separate LinkedIn account per repo on the same machine, start the server with --project-auth. That stores the auth state under the current project in .linkedin-mcp-server/ instead of the shared ~/.linkedin-mcp/ location.

CLI Options

  • --login - Open browser to log in and save persistent profile
  • --logout - Clear stored LinkedIn browser profile
  • --status - Check if current session is valid and exit
  • --no-headless - Show browser window (useful for debugging)
  • --project-auth - Store auth under the current project at .linkedin-mcp-server/
  • --slow-mo MS - Debugging aid: slows all browser actions uniformly in ms (default: 0)
  • --human-delay-min-ms MS - Minimum randomized delay between LinkedIn actions (default: 500)
  • --human-delay-max-ms MS - Maximum randomized delay between LinkedIn actions (default: 2000)
  • --delay-between-linkedin-calls SECONDS - Baseline delay before each LinkedIn MCP tool call (default: 1.5)
  • --delay-jitter SECONDS - Random jitter added/subtracted from tool-call delay (default: 0.5)
  • --delay-distribution {uniform,beta,lognormal} - Shape of randomized delays (default: beta)
  • --linkedin-cache-dir PATH - Override cache root (default: beside auth profile)
  • --disable-linkedin-cache - Disable LinkedIn tool result caching
  • --profile-cache-ttl-hours HOURS - TTL for cached person profiles (default: 720 / 30 days)
  • --no-reading-time - Disable reading-time pauses after page extraction
  • --reading-time-wpm WPM - Simulated reading speed in words-per-minute (default: 800)
  • --reading-time-jitter PERCENT - Jitter percentage for reading-time pauses (default: 30)
  • --reading-time-max SECONDS - Maximum reading-time pause in seconds (default: 45)
  • --log-level {DEBUG,INFO,WARNING,ERROR} - Logging level (default: WARNING)
  • --timeout MS - Browser timeout for page operations in ms (default: 5000)
  • --transport {stdio,streamable-http} - Transport mode (default: stdio)

Troubleshooting

Patchright browser cache: The server owns Patchright setup by default. On startup it sets PLAYWRIGHT_BROWSERS_PATH=~/.linkedin-mcp/patchright-browsers, sets PLAYWRIGHT_SKIP_BROWSER_GC=1, and idempotently runs python -m patchright install chromium. This makes separate terminals, cron jobs, uvx runs, and long-running automations independent of shell state and prevents unrelated Playwright/Patchright installs from garbage-collecting the browser revision. Override PLAYWRIGHT_BROWSERS_PATH if you need a custom cache, or set LINKEDIN_PATCHRIGHT_AUTO_INSTALL=0 to disable startup install.

Derived runtime profiles: Foreign runtimes reuse committed derived profiles by default, so server restarts do not re-bridge/rebuild the browser profile every time. Set LINKEDIN_PERSIST_DERIVED_SESSION=0 for legacy fresh-bridge behavior, or LINKEDIN_DEBUG_BRIDGE_EVERY_STARTUP=1 when intentionally debugging bridging.

Session expired: Run uv run -m linkedin_mcp_server --login again.

Login issues: LinkedIn may require confirmation in the mobile app, or show a captcha — the --login browser window lets you handle these manually.

Scraping issues: Use --no-headless to watch the browser and --log-level DEBUG for detailed logs.

Timeout issues: Increase with --timeout 10000 or env var TIMEOUT=10000.

Tool-call pacing: Tune with --delay-between-linkedin-calls / --delay-jitter or env vars LINKEDIN_CALL_DELAY_MS / LINKEDIN_CALL_DELAY_JITTER_MS. Defaults are 1.5 ± 0.5 seconds, so each LinkedIn MCP tool call starts after roughly 1-2 seconds.

In-tool pacing: Tune with --human-delay-min-ms / --human-delay-max-ms or env vars HUMAN_DELAY_MIN_MS / HUMAN_DELAY_MAX_MS. These are smaller pauses inside a tool workflow, e.g. after navigation or clicks.

Delay distribution: By default, randomized delays use a beta distribution (right-skewed) instead of a flat uniform distribution. This means most delays cluster toward the shorter end of the configured range, with occasional longer pauses — a pattern that's mathematically much harder for bot detectors to fingerprint. Use --delay-distribution uniform or DELAY_DISTRIBUTION=uniform to restore the old flat distribution. lognormal is also available for a classic human reaction-time model.

Reading-time simulation: After extracting page content, the scraper pauses proportionally to text length to simulate skimming. This makes the scraper's time-on-page correlate with content length, which looks more human to LinkedIn's behavioral analysis. Defaults are 800 WPM with ±30% jitter and a 45-second hard cap. Tune with --reading-time-wpm, --reading-time-jitter, --reading-time-max, or env vars READING_TIME_WPM, READING_TIME_JITTER_PCT, READING_TIME_MAX_SECONDS. Use --no-reading-time or READING_TIME_ENABLED=false to disable.

Scrolling behavior: The scraper simulates human-like scrolling via mouse wheel events rather than instant window.scrollTo jumps. Scroll distances are variable, occasionally include small upward corrections (overshoot/re-reading), and use non-uniform pauses between scroll steps. This triggers LinkedIn's lazy loaders and intersection observers more naturally than mechanical scrolling.

Mouse movement: The scraper replaces Playwright's default locator.click() (which teleports the mouse to the exact element center with no mousemove events) with curved quadratic Bezier paths that generate 5–10 mousemove events before every click. This includes variable speed, occasional overshoot-and-correction, and random target offsets within the element. Idle mouse micro-movements are also added during page reading time to maintain nonzero mouse event density.

Profile caching: get_person_profile caches raw profile scrape results for 720 hours by default. The cache is shared across sessions beside the auth profile: ~/.linkedin-mcp/cache/profiles/ by default, or .linkedin-mcp-server/cache/profiles/ when using --project-auth. Use force_refresh=true on the tool call to bypass cache for one profile. Requests that include the posts section are not cached, so competitor post discovery stays fresh. Use --profile-cache-ttl-hours, --linkedin-cache-dir, or --disable-linkedin-cache to tune globally. Env vars: LINKEDIN_PROFILE_CACHE_TTL_HOURS, LINKEDIN_CACHE_DIR, LINKEDIN_CACHE_DISABLED.

slow_mo vs pacing delays: --slow-mo / SLOW_MO is mainly for debugging because it slows nearly every browser action. Prefer the tool-call and in-tool pacing settings for normal runs.

Per-project LinkedIn accounts: Use --project-auth or env var PROJECT_AUTH=true. The login flow will then create:

  • .linkedin-mcp-server/profile/ for the persistent browser profile
  • .linkedin-mcp-server/cookies.json
  • .linkedin-mcp-server/source-state.json
  • .linkedin-mcp-server/runtime-profiles/ for derived runtime sessions

send_message / get_conversation not finding the Message button: The target user must be a 1st-degree connection. The tool returns an error with diagnostic info if the button isn't found.

send_connection_request not finding the Connect button: The user may already be a connection, have a pending request, or not allow connection requests. If Connect is hidden behind a More dropdown, the tool handles this automatically.

Notes

  • Browser profile stored at ~/.linkedin-mcp/profile/
  • Tool calls are serialized — concurrent requests queue rather than run in parallel
  • Use in accordance with LinkedIn's Terms of Service

License

Apache 2.0 — see LICENSE

Related MCP servers

Browse all →