LinkedIn MCP Server

LinkedIn MCP Server

Lets an AI assistant operate LinkedIn through an authenticated browser session, enabling profile management, posting, networking, messaging, job search, and automated applications.

Category
访问服务器

README

LinkedIn MCP Server

A production-grade Model Context Protocol server that lets an AI assistant operate LinkedIn through an authenticated browser session — profile management, posting, networking, messaging, job search and automated applications.

101 tools across 15 categories. No password login: authentication reuses cookies from a browser where you are already signed in.


Why this design

A few decisions shape everything else, and they are worth stating up front.

Two transports, chosen per operation. Reads prefer LinkedIn's internal Voyager API — one HTTP request returning structured JSON, no browser. Writes go through real DOM automation, because LinkedIn's write endpoints are the least stable part of that API and a malformed request can silently blank a profile section. Each transport falls back to the other.

Selector fallback chains. LinkedIn ships UI changes constantly and its class names are obfuscated. Every element is described by a ranked list of selectors — stable test hooks first, then ARIA roles, then visible text, then structural guesses. A markup change usually breaks one candidate, not all four, turning a hard breakage into a silent fallback.

Errors are data, not exceptions. Every tool returns the same envelope whether it succeeds or fails, and failures carry a machine-readable code plus a remediation string. An agent can read why something failed and adapt, instead of blindly retrying.

Conservative by default. Rate limits, daily action quotas, a confirmation gate on destructive tools, and a global dry-run switch. LinkedIn restricts accounts that behave inhumanly, and a restriction costs far more than a slow run.

It says what it does not know. Sections LinkedIn withholds are reported as unavailableSections. Generated resumes list their gaps. Inferred values — like whether someone is a recruiter — carry a score and the signals behind it. Nothing is silently invented.


Install

Requires Node.js 20+.

From npm

npm install -g linkedin-mcp-bridge   # also installs the Chromium browser
linkedin-mcp                          # stdio server
linkedin-mcp-http                     # streamable HTTP server

You still need a LinkedIn session before the tools do anything — see Authenticate below. linkedin-mcp-setup does it in one step.

From source

git clone https://github.com/Sabari2005/linkedin-mcp-server.git
cd linkedin-mcp-server
npm install            # installs deps and the Chromium browser
cp .env.example .env
npm run setup:profile  # sign in by hand; see Authenticate below
npm run build

Verify:

npm run cookies:check   # is the session valid?
npm run tools:list      # what can it do?
npm run smoke           # exercise all 101 tools against your account (dry run)

Authenticate

This server never asks for your password. You sign in to LinkedIn yourself, in a real browser, and the server reuses that session — so 2FA, CAPTCHAs and device confirmations all work normally.

Dedicated Chrome profile (recommended)

npm run setup:profile        # from source
linkedin-mcp-setup           # installed globally

A Chrome window opens; sign in; the command prints one line for your .env:

LINKEDIN_BROWSER_PROFILE=/path/it/printed

The session lives inside that profile, so LinkedIn refreshes it in place and it survives restarts — there is no cookie file to expire behind your back.

The profile is dedicated on purpose: Chrome 136+ refuses to be automated against your primary user-data directory, and it fails by hanging for three minutes rather than saying so. A separate profile also means you never have to close your everyday Chrome to let the server run.

Cookie capture (alternative)

npm run login

Best when there is no Chrome to keep around — a headless server, a container. It opens a browser once, captures the session to a file, and reuses it. That file is a credential, and it expires when LinkedIn rotates or you change your password.

Manual export via the Cookie-Editor extension also works — see docs/authentication.md.

Set exactly one strategy. LINKEDIN_LI_AT outranks LINKEDIN_COOKIE_FILE, which outranks LINKEDIN_BROWSER_PROFILE. A leftover cookie-file line silently ignoring your profile is the single most common setup failure. The server warns at startup when it sees more than one.

Check what it resolved to at any time:

npm run cookies:check

Connect a client

Two transports, same 101 tools:

Transport Command Use it for
stdio node dist/index.js Editor and CLI clients that spawn the server themselves
streamable HTTP node dist/http.js Hosted clients that can only reach a URL

Everything below assumes you have run npm install && npm run build and authenticated. Use the absolute path to dist/index.js — most clients do not launch from your project directory.

Claude Code

claude mcp add linkedin --scope user -- node /absolute/path/to/dist/index.js
claude mcp list   # → linkedin: ✔ Connected

Cursor

.cursor/mcp.json in the project, or ~/.cursor/mcp.json globally:

{
  "mcpServers": {
    "linkedin": {
      "command": "node",
      "args": ["/absolute/path/to/dist/index.js"]
    }
  }
}

Windsurf

~/.codeium/windsurf/mcp_config.json — same shape as Cursor:

{
  "mcpServers": {
    "linkedin": {
      "command": "node",
      "args": ["/absolute/path/to/dist/index.js"]
    }
  }
}

GitHub Copilot (VS Code)

.vscode/mcp.json in the workspace. Note Copilot's key is servers, not mcpServers:

{
  "servers": {
    "linkedin": {
      "type": "stdio",
      "command": "node",
      "args": ["/absolute/path/to/dist/index.js"]
    }
  }
}

Then open the Copilot Chat Agent mode tool picker to enable the LinkedIn tools.

OpenAI Codex CLI

~/.codex/config.toml:

[mcp_servers.linkedin]
command = "node"
args = ["/absolute/path/to/dist/index.js"]

Claude Desktop

claude_desktop_config.json:

{
  "mcpServers": {
    "linkedin": {
      "command": "node",
      "args": ["C:\\absolute\\path\\to\\mcp\\dist\\index.js"]
    }
  }
}
  • Windows %APPDATA%\Claude\claude_desktop_config.json
  • macOS ~/Library/Application Support/Claude/claude_desktop_config.json

Hosted clients — ChatGPT, Claude web, Gemini, Antigravity

These cannot spawn a local process; they connect to a URL. Run the HTTP transport:

export MCP_HTTP_TOKEN="$(openssl rand -hex 32)"   # required for any non-loopback bind
npm run start:http                                 # → http://127.0.0.1:3000/mcp

Then add it as a custom connector with the URL and an Authorization: Bearer <token> header.

A hosted client cannot reach your laptop directly, so expose the port with a tunnel (cloudflared tunnel --url http://127.0.0.1:3000, ngrok http 3000, or a reverse proxy) and give the client the public URL.

This endpoint acts as you on LinkedIn. Anyone who can reach it can post, message and connect as you. The server refuses to bind to a non-loopback address unless MCP_HTTP_TOKEN is set — do not remove that check, and always terminate TLS at your tunnel or proxy.

Variable Default Meaning
MCP_HTTP_PORT 3000 Port to listen on
MCP_HTTP_HOST 127.0.0.1 Bind address; non-loopback requires a token
MCP_HTTP_TOKEN (unset) Bearer token required on every request
MCP_HTTP_PATH /mcp Endpoint path (/health is always available)

Any other MCP client

Any stdio MCP client works: run node dist/index.js and speak JSON-RPC over stdin/stdout. Note that stdout carries the protocol — all logging goes to stderr and data/logs/.


First run

Start with dry-run enabled so nothing can touch your real account while you explore:

DRY_RUN=true

Every mutating tool then validates its arguments and reports what it would do without doing it. Turn it off when you are ready.

Try these:

Check my LinkedIn session status Read my LinkedIn profile and suggest improvements Search for remote machine learning jobs posted in the last week


What it can do

Category Capabilities
Profile Read complete profile · update headline, about, location · add/edit/delete experience, education, certifications, projects, publications, awards, volunteering, languages · manage skills · upload photo and banner · Open To Work · analytics · completeness analysis
Posts Text, image, video, document/carousel and poll posts · edit · delete · react · comment · reply · repost · save · local drafts · read feed and activity
Network Search people · connection requests with notes · accept/ignore/withdraw invitations · follow/unfollow · remove connections · list connections, followers, following
Messaging Read conversations and full history · search · send and reply · archive, delete, mark read/unread
Jobs Search with every LinkedIn filter · full job details · extracted skills · hiring team · save/unsave · saved and applied lists
Applications Easy Apply automation with form introspection · answer memory · bulk apply · application history · withdraw
Companies Company profiles · search · follow/unfollow · employees · hiring teams · open roles
Recruiters Find recruiters with confidence scoring · personalised outreach drafting · bulk messaging
Documents Job-fit analysis · tailored resumes · cover letters — all grounded in real profile data
Search Every vertical: people, jobs, companies, posts, events, groups, schools
Export Profiles, jobs, companies, connections, conversations, posts, search results → JSON, CSV, JSONL, Markdown
Notifications Read, mark read, delete
Settings Read all categories · change the few toggles that are safe to automate · trigger LinkedIn's official data export

Full catalogue: npm run tools:list, or ask the assistant to call linkedin_list_tools.


Example workflows

Tailor an application end to end

Find remote LLM engineer jobs in Germany posted this week. For the three best matches, analyse how well my profile fits, generate a tailored resume for each, and draft a cover letter addressed to someone on the hiring team.

The assistant chains linkedin_search_jobslinkedin_analyse_job_fitlinkedin_generate_resumelinkedin_get_hiring_teamlinkedin_generate_cover_letter.

Bulk apply, safely

Apply to all Easy Apply data engineering jobs in Berlin using resume.pdf. My phone is +49 30 12345678 and I have 5 years of Python experience.

linkedin_apply_to_jobs_bulk introspects each employer's form, answers from what you supplied plus remembered answers, and stops rather than guessing at any required question it cannot answer confidently — reporting exactly which ones, so you can supply them and retry.

Improve a profile

Read my profile, score it, then rewrite my About section to target AI engineering roles and add the three skills I am missing.

Recruiter outreach

Find recruiters hiring AI engineers in Berlin, draft a personalised message for each based on my background, and show me the drafts before sending anything.

More in examples/.


Safety

The defaults assume you would rather be slow than restricted.

Control Default Purpose
DRY_RUN false When true, mutating tools validate and report but never execute
REQUIRE_CONFIRMATION true Destructive tools need explicit confirm: true
RATE_LIMIT_REQUESTS_PER_MINUTE 30 Global request pacing
RATE_LIMIT_CONNECTIONS_PER_DAY 80 Below LinkedIn's ~100/week invitation ceiling
RATE_LIMIT_MESSAGES_PER_DAY 100 Messaging volume cap
RATE_LIMIT_APPLICATIONS_PER_DAY 50 Application volume cap
HUMANIZE true Randomised delays and human-like typing

Daily quotas persist to disk, so restarting the server cannot be used to sidestep them.

Raising these limits materially increases the risk of a temporary account restriction.


Configuration

Every setting lives in .env — see .env.example for the annotated list. The most useful:

TRANSPORT_STRATEGY=auto      # auto | api | browser
BROWSER_HEADLESS=true        # false to watch automation live (great for debugging)
LOG_LEVEL=info               # trace | debug | info | warn | error | silent
DEBUG_ARTIFACTS=true         # screenshot + HTML dump on browser failures
CACHE_TTL_MS=300000          # read cache lifetime

Set exactly one authentication source — see Authenticate.


Troubleshooting

AUTH_EXPIRED / AUTH_MISSING Run npm run cookies:check — it reports which strategy resolved and flags conflicting ones. If the session is dead, re-run npm run setup:profile (or npm run login). See docs/authentication.md — in particular the section on cookies exported from the login page, which look valid but are not.

My browser profile is being ignored LINKEDIN_LI_AT and LINKEDIN_COOKIE_FILE both outrank LINKEDIN_BROWSER_PROFILE. Comment them out. npm run cookies:check names the winner.

SELECTOR_NOT_FOUND / UI_CHANGED LinkedIn changed its markup. Check the screenshot in data/artifacts/, then add a candidate to the relevant list in src/browser/selectors.ts. No other code needs to change.

RATE_LIMITED Back off. Lower RATE_LIMIT_REQUESTS_PER_MINUTE. Sustained throttling can escalate.

QUOTA_EXCEEDED A self-imposed safety limit, not LinkedIn. Wait for the window to roll over, or raise the corresponding RATE_LIMIT_* value if you accept the risk.

Browser will not launch npx playwright install chromium. If using LINKEDIN_BROWSER_PROFILE, close Chrome completely — Chromium cannot share a locked profile.

Browser hangs for ~180s, then times out Chrome 136+ refusing to automate a primary Chrome user-data directory. The silent hang is the symptom — there is no error until the timeout. Run npm run setup:profile to create a dedicated profile and point LINKEDIN_BROWSER_PROFILE at that instead.

Watch it work Set BROWSER_HEADLESS=false and LOG_LEVEL=debug.


Architecture

src/
  core/        config, logger, errors, tool registry, response envelope,
               retry/backoff, rate limiting, TTL cache, shared types
  auth/        cookie parsing and normalisation, session lifecycle
  browser/     Playwright manager, LinkedInPage wrapper, selector registry
  voyager/     internal API client and normalized-JSON graph decoder
  domains/     profile, posts, network, messaging, jobs, applications,
               companies, recruiters, search, notifications, settings, documents
  tools/       MCP tool definitions (schemas + descriptions)
  storage/     local store for applications, drafts, remembered answers
  export/      JSON / CSV / JSONL / Markdown writers
  cli/         login, session check, cookie import, tool listing

The registry (src/core/registry.ts) is the keystone: it wraps every tool with argument validation, rate limiting, quota enforcement, dry-run interception, confirmation gating, timing and error normalisation. That is why individual tool handlers stay short — the repetitive parts happen exactly once.


Development

npm run dev        # watch mode
npm run typecheck  # strict type check
npm run build      # compile to dist/

Adding a tool: write the domain logic in src/domains/, define the tool with defineTool() in src/tools/, and add it to the array in src/tools/index.ts. The registry handles everything else.

Smoke test

npm run smoke exercises every registered tool against your live session. It forces DRY_RUN=true, so reads hit LinkedIn for real while writes are intercepted and validated without being sent. Fixtures (a post, a job, a conversation) are discovered from your own account at runtime — nothing is hardcoded.

npm run smoke                      # all 101 tools
npm run smoke -- --only jobs -v    # one category, with failure detail
npm run smoke -- --skip-writes     # reads only
npm run smoke -- --json            # machine-readable report

A handful of tools are skipped by design: those that print live credentials (auth_export_cookies), tear down the session the suite depends on (system_reset, auth_reload), or have irreversible LinkedIn-side effects even in dry run (request_data_export, mark_notifications_read). Tools whose fixture is missing from your account — messaging tools on an empty inbox, for example — are reported as skipped rather than passed.

Releasing

Releases are tag-driven and fully automated by .github/workflows/publish-mcp.yml. Pushing a v* tag publishes the npm package and then registers it with the MCP Registry.

npm version patch          # bumps package.json
# bump "version" and packages[0].version in server.json to match
npm run validate:registry  # fails loudly if anything disagrees
git commit -am "Release v1.0.4" && git tag v1.0.4
git push origin main --tags

server.json is the registry manifest. npm run validate:registry checks it against the $schema it declares using ajv, then verifies the invariants the schema cannot express:

Invariant Why it matters
package.json mcpNameserver.json name How the registry proves you own the npm package. Compared byte for byteio.github.Sabari2005 must keep its capitals.
packages[0].identifierpackage.json name The registry resolves the npm package from this field.
tag ≡ package.jsonserver.json version Three places, one number. The workflow refuses to publish a release whose tag disagrees.
repository.url owner ≡ namespace owner GitHub OIDC only grants io.github.<owner>/*.

The workflow authenticates to the registry with GitHub OIDC, so the only secret the repository needs is NPM_TOKEN (an npm automation token). npm publish runs first — registry validation fetches the published package — and is skipped if that version is already on npm, so a re-run after a partly failed release still completes.


Limitations

Stated plainly, because silent failure is worse than a clear "no":

  • Premium features (InMail to strangers, full analytics, Recruiter) need a paid account. Tools report this rather than failing obscurely.
  • Scheduled posting is not supported — LinkedIn's scheduler has no automatable surface. Use local drafts plus your own scheduler.
  • Most account settings are read-only here. LinkedIn's settings UI has no stable hooks and a mis-click has real privacy consequences, so only unambiguous toggles are writable; the rest come with direct links.
  • Skill extraction from job descriptions is keyword matching against a curated vocabulary — deliberately conservative, since a false "requires Rust" is worse than an omission.
  • Recruiter identification is inferred from headline patterns; LinkedIn has no recruiter entity type. Every match carries a score and its reasoning.
  • Resume generation assembles and organises real profile data. It never invents experience, and reports what it could not source.

Responsible use

This automates your own LinkedIn account. You remain responsible for what it does.

LinkedIn's User Agreement restricts automated access. Accounts that behave inhumanly get restricted. The conservative defaults exist for that reason — keep them, and prefer targeted actions over volume.

Do not use this to spam, scrape people who have not consented, or misrepresent qualifications. Generated resumes and letters are drafts built from your real profile: review them before they reach another person.


License

MIT

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选