Skip to main content
Glama
LinkedIn MCP Server

LinkedIn MCP Server

by Sabari2005

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.

102 tools across 15 categories. No password login: authentication reuses cookies from a browser where you are already signed in. Selectors repair themselves when LinkedIn changes its markup — using your own model, with page content never leaving your machine.

Unofficial project. Not affiliated with or endorsed by LinkedIn Corporation. Automating LinkedIn may violate its User Agreement and can get your account restricted. See Responsible use.


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.

Selectors that repair themselves. When every candidate misses, the server shows your model a redacted structural outline of the page, takes its suggestions, verifies them against the live DOM, and remembers what works — so a markup change costs one slow call instead of a broken tool and a wait for the next release. It is deliberately narrow: it never relearns a destructive control, never adopts a selector it has not just seen resolve, and never sends page content anywhere. See Self-healing below.

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.


Related MCP server: kaushik-linkedin-mcp

Install

Requires Node.js 20+.

From npm

The shortest path from nothing to working tools, on any platform:

npm install -g linkedin-mcp-bridge   # 1. install
linkedin-mcp-setup                    # 2. sign in, and register with Claude Code

Step 2 opens a real Chrome window at LinkedIn. Sign in by hand — passwords, 2FA and CAPTCHAs stay between you and LinkedIn, and this server never sees them. When you are in, it offers to register itself with Claude Code and to set the profile path in the server's env block. Restart your client and the linkedin_* tools are there.

Other binaries in the package:

linkedin-mcp-bridge                   # stdio server (what your client spawns)
linkedin-mcp-http                     # streamable HTTP server

Alternatively, skip the install and point your client at npx -y linkedin-mcp-bridge, which fetches the package on first run. You still need to sign in once — run npx -y -p linkedin-mcp-bridge linkedin-mcp-setup. See Authenticate.

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 every tool 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.

Nothing to configure (default)

Install, add the server to your client, and ask it something:

> Show my LinkedIn profile

A Chrome window opens at the LinkedIn login page.
Sign in there. The request completes on its own.

The first call that needs authentication creates a browser profile, opens it at LinkedIn, and waits. Sign in quickly and the same call returns your data; take longer and it says "sign in, then ask again" while the window stays open — the next request goes through. You only ever sign in once: the session lives in the profile and survives restarts.

The profile lives outside the repo, one per machine:

OS

Path

Windows

%LOCALAPPDATA%\linkedin-mcp\chrome-profile

macOS

~/Library/Application Support/linkedin-mcp/chrome-profile

Linux

$XDG_DATA_HOME/linkedin-mcp/chrome-profile

Set LINKEDIN_AUTO_LOGIN=false to turn this off and get a plain error instead.

Sign in up front (optional)

To choose where the profile lives, or to authenticate before wiring up a client:

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

It prints one line for your .env:

LINKEDIN_BROWSER_PROFILE=/path/it/printed

Either way 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.

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 102 tools:

Transport

Command

Use it for

stdio

npx -y linkedin-mcp-bridge

Editor and CLI clients that spawn the server themselves

streamable HTTP

npx -y -p linkedin-mcp-bridge linkedin-mcp-http

Hosted clients that can only reach a URL

Every config below uses npx, which downloads the package on first run and needs no clone, no build and no absolute paths. The -y skips the install prompt, which a client launching the server has no way to answer.

On the very first authenticated call the server opens a Chrome window for you to sign in (see Authenticate). Nothing else is required up front.

Replace "command": "npx", "args": ["-y", "linkedin-mcp-bridge"] with "command": "node", "args": ["/absolute/path/to/dist/index.js"] after npm install && npm run build. Use an absolute path — clients do not launch from your project directory.

Claude Code

linkedin-mcp-setup offers to do this for you. To do it by hand:

claude mcp add linkedin --scope user -- npx -y linkedin-mcp-bridge
claude mcp list   # → linkedin: ✔ Connected

Use --scope user, not the default local. A local-scoped server is recorded under projects["<cwd>"] in ~/.claude.json and only loads when Claude Code starts from that exact directory — from anywhere else the tools simply are not there, which is easy to mistake for a broken server.

Cursor

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

{
  "mcpServers": {
    "linkedin": {
      "command": "npx",
      "args": ["-y", "linkedin-mcp-bridge"]
    }
  }
}

Windsurf

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

{
  "mcpServers": {
    "linkedin": {
      "command": "npx",
      "args": ["-y", "linkedin-mcp-bridge"]
    }
  }
}

GitHub Copilot (VS Code)

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

{
  "servers": {
    "linkedin": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "linkedin-mcp-bridge"]
    }
  }
}

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

OpenAI Codex CLI

~/.codex/config.toml:

[mcp_servers.linkedin]
command = "npx"
args = ["-y", "linkedin-mcp-bridge"]

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
npx -y -p linkedin-mcp-bridge linkedin-mcp-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 npx -y linkedin-mcp-bridge 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_jobs → linkedin_analyse_job_fit → linkedin_generate_resume → linkedin_get_hiring_team → linkedin_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

BROWSER_STEALTH

false

Anti-detection, off by default — automating your own account should not require concealing it

SELECTOR_REPAIR

true

Relearn moved selectors via your model; never for destructive controls

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.


Self-healing

LinkedIn rewrites its markup continuously and hashes class names per build. The usual result is a control that quietly stops resolving, a fix that is one line long, and a wait for someone to ship it.

Instead, when every shipped selector misses, the server asks your model to work out where the element went:

  1. It captures a structural outline of the page — tags, roles, ARIA labels, stable data-* hooks, href shapes. Never text.

  2. It asks for candidate selectors via MCP sampling, so the request goes to the model your client is already running. No API key, no separate billing, no third-party service.

  3. It verifies each candidate against the live DOM and keeps only what resolves.

  4. It remembers what worked and tries it first next time.

Requires a client that supports sampling. Without one the feature stays off and selector failures behave exactly as before.

What it will not do

Guard

Behaviour

Destructive controls

delete, remove, withdraw, block, unfollow and similar are never relearned. A wrong guess there cannot be undone, so they fail loudly instead.

Unverified suggestions

A selector is adopted only after it resolves to a visible element on the page that just failed. The model proposes; the DOM decides.

Over-broad matches

A candidate matching more than SELECTOR_REPAIR_MAX_MATCHES elements is rejected, and the model is asked once for something narrower. On a profile page a bare [aria-label*="to connect"] matches ~12 elements — the sidebar rail included — which is exactly how automation ends up acting on the wrong person.

Page content

Text nodes are dropped and identities redacted inside the page, before anything is serialised. Invite Kamal M to connect → Invite ▮ to connect. Stored repairs record /in/*, never a real profile URL.

Reviewing repairs

"show me any selectors the server has relearned"     → linkedin_selector_repairs
"forget the relearned connect button"                → linkedin_selector_repairs { forget: true, label: "connect button" }

Repairs are local to your machine. If one works, please open a PR with it — that is how the fix reaches everyone else instead of each user relearning it independently.

Turn the whole thing off with SELECTOR_REPAIR=false.


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

PROFILE_LOCKED Something else is using the Chrome profile — Chrome allows exactly one process per user-data directory. Your session is fine; do not re-export cookies. Usually a second copy of this server, a linkedin-mcp-setup window still open, or a Chrome you launched from that profile. Close the extra one, or restart your MCP client if it has accumulated duplicate servers, then retry.

Older versions reported this as AUTH_EXPIRED, which sent people to redo a sign-in that was never broken. If you see that on an old build, upgrade.

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                      # every tool
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 mcpName ≡ server.json name

How the registry proves you own the npm package. Compared byte for byte — io.github.Sabari2005 must keep its capitals.

packages[0].identifier ≡ package.json name

The registry resolves the npm package from this field.

tag ≡ package.json ≡ server.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, from your own machine, using a session you signed into yourself. There is no server in the middle and no credential ever leaves your computer. You remain responsible for what it does.

Read this before you use it. LinkedIn's User Agreement restricts automated access — §8.2 prohibits using "software, devices, scripts, robots… to scrape the Services" and using "bots or other automated methods to access the Services." Using this tool may violate that agreement, and LinkedIn can restrict or terminate accounts for it. That risk is yours, it is real, and no amount of care in this codebase removes it.

The conservative defaults exist for that reason. Keep them, prefer targeted actions over volume, and treat a daily quota as a ceiling rather than a target.

What this deliberately does not do:

  • No credential handling. It never accepts, stores, or types a password. Authentication is a browser session you established yourself.

  • No evasion by default. BROWSER_STEALTH is off, and LinkedIn's own bot-detection endpoint is not blocked. Automating your own account does not require concealing that it is automated, and the project takes the view that it should not try to.

  • No bulk harvesting. There is no multi-account support, no unattended crawling, and no mass outreach. Volume is what gets accounts restricted and what makes a tool a scraper.

  • No data collection by us. No telemetry, no analytics, no phone-home. Everything stays in your data/ directory.

If you export other people's data, that data is theirs and may be regulated where you or they live (GDPR, India's DPDP Act, and similar). Exporting a profile is a decision with obligations attached; the tool will not make it for you.

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


Trademark

This is an independent, unofficial project. It is not affiliated with, endorsed, sponsored by, or connected to LinkedIn Corporation in any way. "LinkedIn" is a trademark of LinkedIn Corporation, used here only to describe what this software interoperates with.


License

MIT — provided "as is", without warranty of any kind. See LICENSE.

Available Tools

101 tools
linkedin_add_awardB

Add an honour or award to the profile.

Inputs: title (required), issuer, startYear, startMonth, +1 more.

linkedin_add_certificationB

Add a licence or certification to the profile.

Inputs: name (required), endYear, endMonth, startYear, +4 more.

linkedin_add_educationA

Add an education entry to the profile. School names are matched against LinkedIn's directory.

Inputs: school (required), grade, degree, endYear, +5 more.

linkedin_add_experienceA

Add a work experience entry to the profile. Company names are matched against LinkedIn's company directory. Set current=true for an ongoing role and omit the end date.

Inputs: title (required), company (required), current, endYear, +6 more.

linkedin_add_languageB

Add a language to the profile with an optional proficiency level.

Inputs: language (required), proficiency.

linkedin_add_projectA

Add a project to the profile — useful for portfolio work, open source and side projects.

Inputs: name (required), url, endYear, endMonth, +3 more.

linkedin_add_publicationB

Add a publication (paper, article, book) to the profile.

Inputs: title (required), url, publisher, startYear, +2 more.

linkedin_add_skillA

Add a skill to the profile. LinkedIn matches skills against its own taxonomy and uses them for recruiter search, so prefer standard names ("Machine Learning" over "ML wizardry"). Max 50 skills.

Inputs: skill (required).

linkedin_add_volunteer_experienceB

Add volunteer experience to the profile.

Inputs: role (required), organization (required), cause, current, +5 more.

linkedin_analyse_job_fitA
Read-onlyIdempotent

Compare the user's profile against a job posting: match score, which required skills they have and lack, keyword coverage, and specific recommendations. Use this to decide whether a role is worth applying to, and to know what to emphasise if it is.

Inputs: job (required), identifier.

linkedin_analyse_profileA
Read-onlyIdempotent

Score profile completeness and return specific, actionable improvement suggestions. The score is computed by this server from which sections are present and substantive — it is not LinkedIn's own metric. Use this to answer "how can I improve my profile?".

Inputs: identifier.

linkedin_apply_to_jobA

Apply to a job through LinkedIn Easy Apply. Reads each step of the form, answers questions from the answers you supply, from previously remembered answers, or from safe defaults — and by default STOPS rather than guessing at a required question it cannot answer confidently. Check the unanswered field in the result and re-invoke with those answers supplied. This submits a real application to a real employer, so confirm intent with the user first.

Inputs: job (required), phone, answers, resumePath, +4 more.

linkedin_apply_to_jobs_bulkA

Apply to several Easy Apply jobs in sequence, with human-like pacing between them. Skips jobs already applied to, and aborts the batch on a session or quota failure rather than hammering LinkedIn. Applications where a required question could not be answered are reported as skipped with the specific questions listed — supply those answers and retry those jobs. This submits real applications, so get explicit user approval for the batch first.

Inputs: jobs (required), phone, answers, resumePath, +2 more.

linkedin_auth_export_cookiesA
Read-onlyIdempotent

Export the currently loaded cookies so they can be moved to another machine or backed up. WARNING: the output contains live session credentials — anyone holding them can access the user's LinkedIn account. Only surface this when the user explicitly asks for it.

Inputs: format.

linkedin_auth_import_cookiesA

Import LinkedIn cookies supplied directly as text. Accepts Cookie-Editor JSON, EditThisCookie JSON, a Playwright storageState object, Netscape cookies.txt, or a raw "li_at=…; JSESSIONID=…" header string. Use this when the user pastes cookies into the conversation rather than saving a file.

Inputs: cookies (required), persist.

linkedin_auth_reloadA

Reload cookies from the configured source (file or environment) and re-validate the session. Use this after the user has exported a fresh cookie file to recover from an expired session without restarting the server.

Inputs: cookieFile, restartBrowser.

linkedin_auth_statusA
Read-onlyIdempotent

Check whether the LinkedIn session is currently valid, and report which cookies are loaded, from where, and who the session belongs to. Call this first if any other tool returns an AUTH_EXPIRED or AUTH_MISSING error, and before starting a long workflow.

Inputs: force. Returns: cookies, problem, identity, apiAvailable, +4 more

linkedin_comment_on_postA

Add a comment to a post. Max 1,250 characters. The comment is public and attributed to the signed-in user, so confirm the wording with them first.

Inputs: post (required), text (required).

linkedin_compose_outreachA
Read-onlyIdempotent

Draft a personalised outreach message grounded in the user's real profile — no invented credentials. Returns the draft plus notes about what was missing. Rewrite it for voice before sending; this gives you an accurate factual base, not finished prose.

Inputs: role, tone, highlights, identifier, +2 more.

linkedin_create_postA

Publish a post to the LinkedIn feed. Supports plain text, images (up to 20), a video, a document (PDF/PPT — this is how carousels are made), or a poll. Only one media type per post. Max 3,000 characters. This publishes immediately and is visible to the network, so confirm the text with the user first.

Inputs: text (required), poll, video, images, +3 more.

linkedin_delete_draftA
Destructive

Delete a local post draft. This only affects this server's store, not LinkedIn.

Inputs: draftId (required), confirm.

linkedin_delete_notificationA
Destructive

Delete a single notification, matched by a distinctive substring of its text.

Inputs: match (required), confirm.

linkedin_delete_postA
Destructive

Permanently delete one of your own posts, along with its reactions and comments. This cannot be undone.

Inputs: post (required), confirm.

linkedin_delete_profile_sectionA
Destructive

Permanently delete a profile entry. Addressed by zero-based index within the section — call linkedin_get_profile first to confirm you have the right one. This cannot be undone.

Inputs: index (required), section (required), confirm.

linkedin_edit_postA

Replace the text of an existing post. LinkedIn does not allow editing every post type — polls and some reposts are immutable, and the tool reports that clearly rather than failing silently.

Inputs: post (required), text (required).

linkedin_edit_profile_sectionA

Edit an existing profile entry in place. Entries are addressed by their zero-based index within the section, so call linkedin_get_profile first to see the current order. Only the fields you supply are changed.

Inputs: index (required), fields (required), section (required).

linkedin_export_companiesA
Read-onlyIdempotent

Search companies and export the results to JSON, CSV or Markdown.

Inputs: query (required), limit, format.

linkedin_export_connectionsA
Read-onlyIdempotent

Export connections to JSON, CSV or Markdown. For a genuinely complete archive (with emails and connection dates) prefer linkedin_request_data_export, which uses LinkedIn's official export.

Inputs: limit, format.

linkedin_export_conversationsA
Read-onlyIdempotent

Export conversations — optionally including full message history — to JSON, CSV or Markdown.

Inputs: limit, format, includeMessages.

linkedin_export_jobsA
Read-onlyIdempotent

Run a job search and export the results to JSON, CSV or Markdown — useful for building a pipeline spreadsheet or feeding another tool. Accepts the same filters as linkedin_search_jobs.

Inputs: limit, format, source, keywords, +5 more.

linkedin_export_postsA
Read-onlyIdempotent

Export posts (with engagement metrics) to a JSON, CSV or Markdown file — useful for analysing which content performed best.

Inputs: limit, format, identifier.

linkedin_export_profileA
Read-onlyIdempotent

Export a full profile to a structured file (JSON, CSV or Markdown) for downstream use — archiving, feeding another tool, or handing to a resume builder.

Inputs: format, identifier.

linkedin_export_search_resultsB
Read-onlyIdempotent

Run any search and export the results to JSON, CSV or Markdown.

Inputs: query (required), limit, format, location, +1 more.

linkedin_find_recruitersA
Read-onlyIdempotent

Find recruiters relevant to a role or company. Each match carries a recruiterScore (0-100) and the signals behind it, so you can judge confidence rather than trusting a silent filter. LinkedIn has no "recruiter" entity type — this infers it from headline patterns.

Inputs: limit, company, keywords, location, +1 more.

linkedin_followA

Follow or unfollow a person. Following shows their posts in your feed without requiring a connection — useful for people who do not accept invitations.

Inputs: follow (required), identifier (required).

linkedin_follow_companyA

Follow or unfollow a company page.

Inputs: follow (required), company (required).

linkedin_forget_answerA
Destructive

Delete a remembered screening-question answer, e.g. after it turns out to be wrong.

Inputs: question (required), confirm.

linkedin_generate_cover_letterA

Draft a cover letter grounded in the user's real profile and a specific job posting. Draws evidence from actual experience descriptions rather than inventing achievements. Reports gaps so you know what to strengthen. Rewrite the output for voice before it is sent.

Inputs: job (required), save, tone, filename, +3 more.

linkedin_generate_resumeA

Build a structured resume from the user's actual LinkedIn profile, optionally tailored to a specific job (which reorders skills to surface relevant ones first). Every line comes from real profile data — nothing is invented. The gaps field lists what could not be sourced, which is exactly what you should ask the user about or rewrite yourself before sending it anywhere.

Inputs: job, save, format, tailor, +5 more.

linkedin_get_application_historyA
Read-onlyIdempotent

List applications submitted through this server, including the answers given and any failures. For LinkedIn's own authoritative record, use linkedin_get_applied_jobs instead.

Inputs: since, status.

linkedin_get_applied_jobsA
Read-onlyIdempotent

Read LinkedIn's own record of jobs the user has applied to. This is authoritative, unlike linkedin_get_application_history which reflects only applications made through this server.

Inputs: limit.

linkedin_get_companyA
Read-onlyIdempotent

Read a company page: description, industry, size, headquarters, founding year, specialties, follower count and website. The returned companyId is what job and people searches use for company filtering.

Inputs: company (required), refresh.

linkedin_get_company_employeesA
Read-onlyIdempotent

List people who work at a company, optionally filtered by job title. Useful for finding warm introductions, hiring managers, or peers in a target team.

Inputs: company (required), limit, title.

linkedin_get_company_jobsB
Read-onlyIdempotent

List open jobs posted by a specific company.

Inputs: company (required), limit.

linkedin_get_connectionsA
Read-onlyIdempotent

List the signed-in user's 1st-degree connections with names, headlines and profile links. Large networks take a while to page through, so set a realistic limit.

Inputs: limit, refresh.

linkedin_get_contact_infoA
Read-onlyIdempotent

Read a profile's contact information: email, phone, websites, Twitter and birthday. LinkedIn only reveals most of these for 1st-degree connections, so expect sparse results otherwise.

Inputs: identifier.

linkedin_get_conversation_historyA
Read-onlyIdempotent

Read the full message history of one conversation, oldest to newest. Use this to get context before composing a reply.

Inputs: conversation (required), limit.

linkedin_get_conversationsA
Read-onlyIdempotent

List message conversations with participants, the last message and unread status. Use this to triage the inbox before reading or replying to specific threads.

Inputs: limit, unreadOnly.

linkedin_get_followersB
Read-onlyIdempotent

List people who follow the signed-in user without necessarily being connected.

Inputs: limit.

linkedin_get_followingA
Read-onlyIdempotent

List the people the signed-in user follows.

Inputs: limit.

linkedin_get_hiring_teamA
Read-onlyIdempotent

Find recruiters and hiring contacts at a company, identified by title patterns. Use the results to address a cover letter to a real person or to send targeted outreach.

Inputs: company (required), limit.

linkedin_get_invitationsA
Read-onlyIdempotent

List pending connection invitations — either received (awaiting your response) or sent (awaiting theirs). Returns sender names and any personalised message.

Inputs: type, limit.

linkedin_get_jobA
Read-onlyIdempotent

Read a job posting in full: complete description, required skills (extracted from the text), salary, applicant count, employment type, experience level and the hiring team where LinkedIn shows it. Use this before applying or tailoring a resume.

Inputs: job (required), refresh.

linkedin_get_network_statsA
Read-onlyIdempotent

Summarise network size: connection count, follower count and pending invitation counts.

Inputs: no named parameters.

linkedin_get_notificationsA
Read-onlyIdempotent

Read LinkedIn notifications — reactions, comments, invitations, job alerts and profile views. Use unreadOnly to triage only what is new.

Inputs: limit, unreadOnly.

linkedin_get_postA
Read-onlyIdempotent

Read a single post in detail by URN, activity id or URL.

Inputs: post (required).

linkedin_get_post_commentsA
Read-onlyIdempotent

Read the comments on a post, in order. The returned index of each comment is what linkedin_reply_to_comment expects.

Inputs: post (required), limit.

linkedin_get_postsA
Read-onlyIdempotent

Read recent posts from a profile's activity or from the home feed, including text, author, engagement metrics and post type. Use this to review what someone posts about before engaging, or to audit your own posting history.

Inputs: limit, source, identifier.

linkedin_get_profileA
Read-onlyIdempotent

Read a complete LinkedIn profile: headline, about, experience, education, skills, certifications, projects, publications, awards, volunteering and languages. Use this before writing any profile update (so edits are grounded in what is actually there) and before tailoring a resume.

Inputs: refresh, sections, transport, identifier.

linkedin_get_profile_analyticsA
Read-onlyIdempotent

Read profile analytics: profile views, post impressions, search appearances and follower count. LinkedIn restricts several of these for non-Premium accounts, and anything unavailable is reported explicitly rather than guessed.

Inputs: no named parameters.

linkedin_get_remembered_answersA
Read-onlyIdempotent

List screening-question answers this server has remembered from previous applications, so they can be reviewed or corrected before the next batch.

Inputs: no named parameters.

linkedin_get_saved_jobsA
Read-onlyIdempotent

List the jobs the user has saved on LinkedIn.

Inputs: limit.

linkedin_get_settingsA
Read-onlyIdempotent

Read LinkedIn account settings for a category (account, privacy, visibility, notifications, communications, data-privacy). Returns each setting with its current value and a direct link, since most settings must be changed manually.

Inputs: category.

linkedin_list_draftsA
Read-onlyIdempotent

List locally saved post drafts that have not yet been published.

Inputs: includePublished.

linkedin_list_filesA
Read-onlyIdempotent

List files this server has produced or can use: previous exports and available resume/cover-letter documents. Use this to find a resume path before applying to a job, or to locate an earlier export.

Inputs: no named parameters.

linkedin_list_resumesA
Read-onlyIdempotent

List resume and cover-letter files available in the configured resume directory, with paths ready to pass to linkedin_apply_to_job.

Inputs: no named parameters.

linkedin_list_toolsA
Read-onlyIdempotent

List every tool this server exposes, grouped by category, with flags showing which are read-only, which mutate LinkedIn, and which are destructive. Useful for orienting before planning a multi-step workflow.

Inputs: category.

linkedin_manage_conversationB

Archive, unarchive, mark read/unread, or delete a conversation. Deletion is irreversible and removes the thread from your inbox only (the other party keeps their copy).

Inputs: action (required), conversation (required).

linkedin_mark_notifications_readA

Mark notifications as read. LinkedIn has no bulk API for this — the server loads and scrolls the notifications page, which is how LinkedIn itself clears the unread badge.

Inputs: no named parameters.

linkedin_message_recruitersA

Find recruiters hiring for a role and message each one, personalising from the user's profile unless a fixed message is supplied. Paces sends and stops on quota or session failures. This contacts real people on the user's behalf — always confirm the recipient list and message wording with them before invoking it.

Inputs: role (required), company, message, minScore, +1 more.

linkedin_publish_draftA

Publish a previously saved local draft to LinkedIn. Attaches any media recorded with the draft. The draft is marked published on success.

Inputs: draftId (required), visibility.

linkedin_react_to_postA

React to a post. Defaults to a Like; celebrate/support/love/insightful/funny are also available. Calling this on an already-liked post is a no-op rather than an error.

Inputs: post (required), reaction.

linkedin_remove_connectionA
Destructive

Remove an existing 1st-degree connection. They are not notified, but re-connecting requires a fresh invitation. This cannot be undone directly.

Inputs: identifier (required), confirm.

linkedin_remove_skillA
Destructive

Remove a skill from the profile, along with any endorsements it has accumulated.

Inputs: skill (required), confirm.

linkedin_reply_to_commentA

Reply to a specific comment on a post. Comments are addressed by zero-based index — call linkedin_get_post_comments first to see the order.

Inputs: post (required), text (required), commentIndex (required).

linkedin_reply_to_conversationA

Send a message into an existing conversation thread by its id.

Inputs: text (required), conversation (required).

linkedin_repostA

Repost (reshare) someone else's post. Without thoughts it reposts instantly; with thoughts it creates a new post quoting the original.

Inputs: post (required), thoughts.

linkedin_request_data_exportA

Trigger LinkedIn's official data export. This is the only way to obtain a genuinely complete archive — connections with emails, full message history, activity logs — which scraping cannot match. LinkedIn emails a download link within minutes to 24 hours.

Inputs: no named parameters.

linkedin_resolve_locationA
Read-onlyIdempotent

Resolve a place name to LinkedIn's internal geo id/URN. Most callers do not need this — the search tools resolve locations automatically — but it is useful for building precise filters by hand.

Inputs: location (required).

linkedin_respond_to_invitationA

Accept or ignore a received connection invitation. Identify the sender by name or public identifier — the invitation list reorders between calls, so indexes are not used.

Inputs: action (required), person (required).

linkedin_save_draftA

Save post text as a local draft without publishing it. LinkedIn exposes no draft API, so drafts live in this server's local store — they are not visible in the LinkedIn UI. Use this to prepare content for review before publishing.

Inputs: text (required), note, mediaPaths.

linkedin_save_jobA

Save a job to the user's saved list, or remove it from the list.

Inputs: job (required), saved.

linkedin_save_postA

Bookmark a post to the user's saved items for later reading.

Inputs: post (required).

linkedin_search_companiesA
Read-onlyIdempotent

Search for companies by name or keyword. Use this to resolve a company name into the numeric companyId that linkedin_search_jobs and linkedin_search_people need for company filters.

Inputs: query (required), limit, companySize, industryIds.

linkedin_search_conversationsA
Read-onlyIdempotent

Search the message inbox by keyword or participant name.

Inputs: query (required), limit.

linkedin_search_jobsA
Read-onlyIdempotent

Search LinkedIn jobs using the full filter set: keywords, location, remote/hybrid/onsite, experience level, employment type, date posted, salary band, company, industry, Easy Apply only, and more. Handles pagination automatically. This is the entry point for every job workflow — the returned jobIds feed linkedin_get_job, linkedin_save_job and linkedin_apply_to_job.

Inputs: limit, sortBy, keywords, location, +12 more.

linkedin_search_peopleA
Read-onlyIdempotent

Search for people, with filters for connection degree, current/past company, school, industry, location and job title. Returns names, headlines, locations and profile identifiers. Use the identifiers with linkedin_get_profile, linkedin_send_connection_request or linkedin_send_message.

Inputs: query (required), limit, title, network, +7 more.

linkedin_send_connection_requestA

Send a connection request, optionally with a personalised note (max 300 characters). Notes dramatically improve acceptance rates but free accounts are limited to about 5 per month. LinkedIn also caps invitations at roughly 100/week — this server enforces a conservative daily limit to keep the account safe.

Inputs: identifier (required), note.

linkedin_send_messageA

Send a direct message to a person, starting a new conversation or continuing an existing one. Messaging people outside your network generally requires Premium InMail — the tool says so explicitly when that is the blocker. Max 8,000 characters.

Inputs: text (required), identifier (required), subject.

linkedin_set_open_to_workA

Turn the "Open To Work" signal on or off. When enabling, you can specify target job titles and locations, and whether the badge is visible to everyone or only to recruiters.

Inputs: enabled (required), titles, locations, visibility.

linkedin_system_resetA

Clear the in-memory cache and/or restart the browser. Use this when reads seem stale after an out-of-band change, or when browser automation starts failing repeatedly.

Inputs: clearCache, resetQuotas, restartBrowser.

linkedin_system_statusA
Read-onlyIdempotent

Report server health: configuration in effect, browser state, cache statistics, rate-limit quota consumption and local storage counts. Use this to diagnose slowness, unexpected refusals, or to confirm whether DRY_RUN is active before performing writes.

Inputs: no named parameters.

linkedin_update_aboutA

Replace the About (summary) section on the signed-in user's profile. Max 2,600 characters. Read the existing profile first so the rewrite keeps real facts intact.

Inputs: about (required).

linkedin_update_headlineA

Replace the headline on the signed-in user's profile. The headline is the single most visible field in search results, so prefer a specific role + specialism over a bare job title. Max 220 characters.

Inputs: headline (required), industry.

linkedin_update_locationA

Update the profile location. LinkedIn requires a location from its own list, so the value is matched against its typeahead — use a recognisable city or region name.

Inputs: location (required).

linkedin_update_settingA

Change one of the few settings that can be automated safely: profile-viewing-mode (how you appear when viewing others) and open-to-work-visibility. Everything else must be changed manually — LinkedIn's settings UI has no stable hooks and a mis-click has real privacy consequences.

Inputs: value (required), setting (required).

linkedin_upload_bannerA

Upload or replace the profile background banner. LinkedIn recommends 1584x396 pixels; JPG/PNG/GIF up to 8MB.

Inputs: filePath (required).

linkedin_upload_profile_photoA

Upload or replace the profile photo. Accepts JPG, PNG or GIF up to 8MB. LinkedIn recommends a square image of at least 400x400.

Inputs: filePath (required).

linkedin_withdraw_applicationA
Destructive

Withdraw a submitted job application. LinkedIn only allows this for some postings and within a limited window, so a clear "not possible" answer is a normal outcome.

Inputs: job (required), confirm.

linkedin_withdraw_invitationA

Withdraw a connection request you sent. Note that LinkedIn blocks re-inviting the same person for about three weeks after a withdrawal.

Inputs: person (required).

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A3.6/5.0

    Scored across 101 tools

    Disambiguation4/5

    Almost every tool has a clearly distinct purpose, and the descriptions are exemplary at resolving overlaps (e.g., linkedin_get_applied_jobs vs linkedin_get_application_history, linkedin_export_connections vs linkedin_request_data_export). A few nearby pairs like linkedin_send_message vs linkedin_reply_to_conversation retain slight overlap, but misselection is unlikely given the detailed guidance.

    Naming Consistency4/5

    All 101 tools follow a consistent linkedin_verb_noun snake_case pattern with a uniform prefix. Minor deviations keep it from a perfect score: profile sections use add_* while posts use create_post; edit_* and update_* are both used for modifications; and delete_*, remove_*, and withdraw_* all express removal.

    Tool Count1/5

    At 101 tools, this is far beyond the 50+ threshold the rubric flags as an extreme mismatch. While LinkedIn's surface area is genuinely large and each tool has a defined role, the count is overwhelming for an agent to navigate effectively.

    Completeness4/5

    The server covers LinkedIn's core workflows end to end: profile CRUD, job search/apply/withdraw, messaging, connection management, posts, notifications, search, settings, and data export. Minor gaps like endorsements, recommendations, and Featured-section management exist, but no essential flow is left as a dead end.

    Maintenance

    ActivityStale
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers