LinkedIn MCP Server
This server lets AI assistants operate a real LinkedIn session locally to read profiles, search, and take limited actions.
Profile lookup: get any person's profile (experience, education, skills, posts, contact info, etc.) or your own.
People discovery: search by keyword/location/network/current company, extract sidebar recommendations.
Networking: send or accept connection requests with optional note.
Companies: get company profiles/posts/employees and search companies.
Jobs: search jobs with filters, get job details, list saved jobs.
Messaging: list inbox, read conversations, search messages, send messages (with confirmation).
Feed/posts: read your home feed and search posts globally with recency filter.
Session control: close the browser session; also CLI login/import/logout support for authentication.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@LinkedIn MCP Serverwhat are my recommended jobs I can apply to?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
MCP Server for LinkedIn
An MCP server that connects AI assistants like Claude to LinkedIn through your own logged-in browser session. Look up profiles and companies, send messages, manage your inbox, or search for jobs. All browser actions run locally on your machine.
This is an independent open-source project, not affiliated with, authorized by, endorsed by, or sponsored by LinkedIn or Microsoft. LinkedIn is a trademark of LinkedIn Corporation and is used here only to identify the service this software interacts with.
This MCP server is supported by Unipile. Unipile is the fully managed cloud option for developers: a hosted LinkedIn API for Classic, Sales Navigator, and Recruiter that handles auth, sessions, and infrastructure for you.
Installation Methods - LinkedIn MCP Server
Tool | Description |
| Get profile info with explicit section selection (experience, education, interests, honors, languages, certifications, skills, projects, contact_info, posts) |
| Get the authenticated user's own LinkedIn profile (same sections as get_person_profile) |
| Send a connection request or accept an incoming one, with optional note |
| Extract profile URLs from sidebar recommendation sections ("More profiles for you", "Explore premium profiles", "People you may know") on a profile page |
| List recent conversations from the LinkedIn messaging inbox |
| Read a specific messaging conversation by username or thread ID |
| Search messages by keyword |
| Compose/send a new message to a LinkedIn user (requires confirmation; profile-based targeting may open a separate DM instead of replying in an existing thread — see #483) |
| Extract company information with explicit section selection (posts, jobs); about-section references may include a |
| Get recent posts from a company's LinkedIn feed |
| Search for companies on LinkedIn by keywords |
| List employees at a company from the /people/ page, with optional keyword filter |
| Search for jobs with keywords and location filters |
| List job postings saved by the authenticated user |
| Search for people by keywords, location, connection degree (1st/2nd/3rd), and current company |
| Get detailed information about a specific job posting |
| Get recent posts from the authenticated user's home feed |
| Search posts/content globally by keyword (the "Posts" tab) with an optional recency filter (past-24h/past-week/past-month); returns unordered candidate permalinks in references |
| Close browser session and clean up resources |
Related MCP server: LinkedIn Job Scraper MCP Server
🚀 uvx Setup (Recommended)
Prerequisites: Install uv.
Installation
Client Configuration
{
"mcpServers": {
"mcp-server-linkedin": {
"command": "uvx",
"args": ["mcp-server-linkedin@latest"],
"env": { "UV_HTTP_TIMEOUT": "300" }
}
}
}The @latest tag turns on automatic updates, which keep the server working with LinkedIn's current page structure. The server starts quickly, prepares the shared Patchright Chromium browser cache in the background under ~/.linkedin-mcp/patchright-browsers, and opens a LinkedIn login browser window on the first tool call that needs authentication.
AI agent? Get a quick confirmation from the user before enabling automatic updates.
Early tool calls may return a setup/authentication-in-progress error until browser setup or login finishes. If you prefer to create a session explicitly, runuvx mcp-server-linkedin@latest --login.
uvx Setup Help
Default (stdio): Standard communication for local MCP servers
Streamable HTTP: For a web-based MCP server
If no transport is specified, the server defaults to
stdioAn interactive terminal without explicit transport shows a chooser prompt
Session:
--login- Open a browser to sign in and save the session--import-from-browser [BROWSER]- Reuse a session from a locally signed-in Chromium browser (chrome,chromium,brave,edge,arc,vivaldi,helium,yandex,whale,auto). Bare flag picksauto, the most recently used browser with a live LinkedIn session.--auto-import/--no-auto-import- Import a session from a signed-in local browser on the first tool call that needs one, before falling back to manual login (default: on). Skipped in Docker, behind a proxy, and on a non-loopback HTTP bind. On macOS the keychain may prompt once.--logout- Clear the stored session--login-viewer- Docker only: show the--loginbrowser at a token-protected URL on port 6080 (see Authentication)--user-data-dir PATH- Browser profile directory (default: ~/.linkedin-mcp/profile). Rotating or clearing a session deletes this directory and its parent, which holds the stored cookies and derived profiles.--claim-profile-root- Take over a profile directory the server will not claim on its own, such as one whose parent already holds other files. Needed once per directory.
Transport:
--transport {stdio,streamable-http}- Force the transport mode (default: stdio)--host HOST/--port PORT/--path PATH- HTTP server address (defaults: 127.0.0.1, 8000, /mcp)
Timeouts:
--timeout MS- Timeout for a single page operation (default: 5000)--tool-timeout SECONDS- Timeout for a whole tool call (default: 180). Raise it for heavy scrapes, slow networks, or a cold-start browser.--login-timeout SECONDS- How long the login browser waits for you to finish signing in (default: 1800; 0 = no limit).--login-viewerends the session after 30 minutes either way.--login-inline-wait SECONDS- How long a tool call waits for a login to finish before telling the model to retry (default: 25, max 45; 0 = return at once)
Shared browser:
--browser-wait SECONDS- How long to wait for another server process to hand over the shared browser (default: 25, max 45; 0 = report busy at once). Only matters with several MCP clients running at once.--browser-min-hold SECONDS- Shortest time this process keeps the shared browser before handing it over (default: 20). Clamped to 3 seconds below--browser-wait, so raise that one along with it. Higher means fewer browser restarts but longer waits for other clients.--browser-idle-timeout SECONDS- Close an idle browser and release the profile after this long without a tool call (default: 600; 0 = keep it open)
Browser:
--no-headless- Show the browser window (useful for debugging)--chrome-path PATH- Path to a Chrome/Chromium executable--proxy-server URL- Route browser traffic through a proxy, asscheme://host:port. Set it up before--login; see Using a proxy.
Other:
--log-level {DEBUG,INFO,WARNING,ERROR}- Logging level (default: WARNING)
If you are already signed into LinkedIn in Chrome, Chromium, Brave, Edge, Arc, Vivaldi, Helium, Yandex, or Naver Whale, you can skip the manual --login step and reuse that session:
# Auto-pick the most recently used browser with a live LinkedIn session
uvx mcp-server-linkedin@latest --import-from-browser
# Or target a specific browser
uvx mcp-server-linkedin@latest --import-from-browser braveThis reads the browser's LinkedIn cookies, validates them against your feed, and saves them to ~/.linkedin-mcp/profile/, the same place --login writes to. Notes:
With several signed-in browsers, the most recently used live LinkedIn session is tried first. If LinkedIn rejects it (revoked or remote-logged-out), the next most recent is tried automatically; the first the server accepts is imported. There is no prompt to pick. Pass a browser name to target one specifically.
On macOS the OS keychain may prompt to allow access to the browser's Safe Storage. Close the source browser first for the most reliable read.
Cookies protected by Chrome 127+ app-bound encryption (
v20) cannot be decrypted without OS elevation; in that case use--logininstead.Imported cookies match a real login's on-disk set. The local server reads them back in full from the saved profile; the Docker bridge narrows to the same minimal auth subset it uses for a normal session.
Basic Usage Examples:
# Run with debug logging
uvx mcp-server-linkedin@latest --log-level DEBUGHTTP Mode Example (for web-based MCP clients):
uvx mcp-server-linkedin@latest --transport streamable-http --host 127.0.0.1 --port 8080 --path /mcpRuntime server logs are emitted by FastMCP/Uvicorn.
Tool calls are serialized to protect the shared LinkedIn browser session, both
within one server process and across separate ones. If you run several MCP
clients at once, each starts its own server process, and only one of them uses
the browser at a time; the others wait briefly and take over as soon as it
finishes a call. A client that waits too long gets a "browser is busy" message
and can simply retry. Use --log-level DEBUG to see the wait/acquire/release
logs.
This covers processes on the same machine and in the same runtime. It does not
extend between the host and a Docker container sharing the same
~/.linkedin-mcp directory, so do not run --login or --logout on the host
while a container is running.
Test with mcp inspector:
Install and run mcp inspector
bunx @modelcontextprotocol/inspectorClick pre-filled token url to open the inspector in your browser
Select
Streamable HTTPasTransport TypeSet
URLtohttp://localhost:8080/mcpConnect
Test tools
Ensure you have uv installed:
curl -LsSf https://astral.sh/uv/install.sh | shCheck uv version:
uv --version(should be 0.4.0 or higher)On first run,
uvxdownloads all Python dependencies. On slow connections, uv's default 30s HTTP timeout may be too short. The recommended config above already setsUV_HTTP_TIMEOUT=300(seconds) to avoid this.Windows,
DLL load failed while importing _greenlet: move to greenlet 3.5.5 or newer, whose published Windows wheels carry the C++ runtime inside the extension again. A freshuvxrun resolves that on its own; an environment that pins its dependencies needsuv lock --upgrade-package greenlet. Only greenlet 3.3.1 through 3.5.4 needMSVCP140.dll, which neither the python.org installer nor theuv-managed builds carry, and a greenlet built from source can need it at any version. Where the version cannot be moved, the Microsoft Visual C++ Redistributable supplies that DLL. Reported as greenlet#525, fixed in greenlet#526.
Browser profile is stored at
~/.linkedin-mcp/profile/Managed browser downloads are cached at
~/.linkedin-mcp/patchright-browsers/The browser cache keeps growing: a server upgrade can bring a new Chromium revision, and Patchright keeps the old one for as long as any installed version still references it.
uvxkeeps one archive per version you have ever run, so every one of them holds such a reference and the old revisions stay. The server logs a warning naming the revisions it is holding and how much space they take. To reclaim it, stop every LinkedIn MCP Server instance, delete~/.linkedin-mcp/patchright-browsers/, and let the next launch download the current browser.
Make sure you have only one active LinkedIn session at a time
LinkedIn may require a login confirmation in the LinkedIn mobile app for
--loginLinkedIn may show a captcha challenge during login. Run
uvx mcp-server-linkedin@latest --loginwhich opens a browser where you can solve it manually.
Page operations failing (elements not found, navigation hangs): increase the browser page-op timeout:
--timeout 10000orTIMEOUT=10000(milliseconds, default 5000).Entire tool calls timing out (e.g. multi-section profiles, cold-start Chromium, slow containers): increase the per-tool execution timeout:
--tool-timeout 300orTOOL_TIMEOUT=300(seconds, default 180).First tool call with no session: if a locally logged-in browser has a live LinkedIn session, the server auto-imports it (see
AUTO_IMPORT_FROM_BROWSER/--auto-import) instead of forcing a manual login. On macOS the keychain may prompt once for Safe Storage access. If no importable browser session exists, it falls back to opening a login window and waits up toLOGIN_INLINE_WAITseconds (default 25, max 45;--login-inline-wait) so a quick sign-in resolves in one call. If the wait elapses, the tool returns a pending signal and the model retries in about 30 seconds. Neither the auto-import nor the inline wait applies under Docker or when the server is bound to a non-loopback HTTP host. Create the session on the host with--login, or use the explicit Docker--login --login-viewercommand.Users on slow connections may need higher values for either.
If tool calls answer "No valid LinkedIn session is available in Docker" on a machine that is not a container, the runtime was misdetected. This happened on Linux hosts running a Docker daemon for unrelated services. Set
LINKEDIN_MCP_CONTAINER=falseto override the detection;trueforces the opposite.
If Chrome is installed in a non-standard location, use
--chrome-path /path/to/chromeCan also set via environment variable:
CHROME_PATH=/path/to/chromeOn macOS and Linux the browser must be at least as new as the one that last opened your profile, and the server refuses the launch otherwise. (Not on Windows: a browser there cannot be asked its version without starting one, so the check is off.) An older browser can silently drop stores a newer one wrote, the saved session among them, and the failure then looks exactly like an expired login. The message names both versions. Going back to the bundled Chromium after running a newer Chrome once is the usual way to meet this; either run the newer browser again, whichever one that was, or run
--login, which moves the stored session aside and signs in fresh with the browser you have.--logoutalso clears it but discards the old session instead of keeping it recoverable, and it asks for confirmation on the terminal, so it is not usable from a server an MCP client started.Only Chrome, Chromium and Chrome for Testing are compared this way. Forks number themselves differently (Vivaldi is on 7.x, Edge's build number sits far below Chrome's under the same major), so pointing
CHROME_PATHat one turns the check off rather than producing a refusal nothing could satisfy.
📦 Claude Desktop MCP Bundle (formerly DXT)
Prerequisites: Claude Desktop.
One-click installation for Claude Desktop users:
Download the latest
.mcpbartifact from releasesClick the downloaded
.mcpbfile to install it into Claude DesktopCall any LinkedIn tool
On startup, the MCP Bundle starts preparing the shared Patchright Chromium browser cache in the background. If you call a tool too early, Claude will surface a setup-in-progress error. On the first tool call that needs authentication, the server opens a LinkedIn login browser window and asks you to retry after sign-in.
MCP Bundle Setup Help
Claude Desktop starts the bundle immediately; browser setup continues in the background
If the Patchright Chromium browser is still downloading, retry the tool after a short wait
Managed browser downloads are shared under
~/.linkedin-mcp/patchright-browsers/The browser cache keeps growing: Patchright keeps an old Chromium revision for as long as any installed version still references it, so an upgrade can leave both on disk. The server logs a warning naming what it holds. To reclaim the space, stop every LinkedIn MCP Server instance, delete
~/.linkedin-mcp/patchright-browsers/, and let the next launch download the current browser.Windows, the bundle exits with
DLL load failed while importing _greenlet: install the Microsoft Visual C++ Redistributable, or reinstall a bundle pinning greenlet 3.5.5 or newer, whose published Windows wheels carry the C++ runtime inside the extension again. A bundle pinning greenlet 3.3.1 through 3.5.4 needsMSVCP140.dllfrom that redistributable, which neither the python.org installer nor theuv-managed builds carry, and a greenlet built from source can need it at any version. The server names this itself on startup, and only after checking that the loader cannot produce that DLL. Reported as greenlet#525, fixed in greenlet#526.
Make sure you have only one active LinkedIn session at a time
LinkedIn may require a login confirmation in the LinkedIn mobile app for
--loginLinkedIn may show a captcha challenge during login. Run
uvx mcp-server-linkedin@latest --loginwhich opens a browser where you can solve captchas manually. See the uvx setup for prerequisites.
Page operations failing (elements not found, navigation hangs): increase the browser page-op timeout:
--timeout 10000orTIMEOUT=10000(milliseconds, default 5000).Entire tool calls timing out (e.g. multi-section profiles, cold-start Chromium, slow containers): increase the per-tool execution timeout:
--tool-timeout 300orTOOL_TIMEOUT=300(seconds, default 180).First tool call with no session: if a locally logged-in browser has a live LinkedIn session, the server auto-imports it (see
AUTO_IMPORT_FROM_BROWSER/--auto-import) instead of forcing a manual login. On macOS the keychain may prompt once for Safe Storage access. If no importable browser session exists, it falls back to opening a login window and waits up toLOGIN_INLINE_WAITseconds (default 25, max 45;--login-inline-wait) so a quick sign-in resolves in one call. If the wait elapses, the tool returns a pending signal and the model retries in about 30 seconds. Neither the auto-import nor the inline wait applies under Docker or when the server is bound to a non-loopback HTTP host. Create the session on the host with--login, or use the explicit Docker--login --login-viewercommand.Users on slow connections may need higher values for either.
If tool calls answer "No valid LinkedIn session is available in Docker" on a machine that is not a container, the runtime was misdetected. This happened on Linux hosts running a Docker daemon for unrelated services. Set
LINKEDIN_MCP_CONTAINER=falseto override the detection;trueforces the opposite.
🧩 Codex plugin
This repository includes an opt-in Codex plugin that bundles the MCP server. Add the repository marketplace and install the plugin:
codex plugin marketplace add stickerdaniel/linkedin-mcp-server
codex plugin add linkedin-mcp-server@linkedin-mcp-server🐳 Docker Setup
Authentication
Log in once. The container opens a LinkedIn login browser that you drive from your own browser tab.
macOS / Linux:
# Create the directory first so the container can save your session into it
mkdir -p ~/.linkedin-mcp
docker run -it --rm \
-v ~/.linkedin-mcp:/home/pwuser/.linkedin-mcp \
-p 127.0.0.1:6080:6080 \
stickerdaniel/linkedin-mcp-server:latest \
--login --login-viewerPowerShell (Windows):
$sessionDir = Join-Path $env:USERPROFILE ".linkedin-mcp"
New-Item -ItemType Directory -Force -Path $sessionDir | Out-Null
docker run -it --rm `
-v "${sessionDir}:/home/pwuser/.linkedin-mcp" `
-p 127.0.0.1:6080:6080 `
stickerdaniel/linkedin-mcp-server:latest `
--login --login-viewerOpen the full URL the command prints (it carries the access token) and sign in. The viewer closes itself afterwards; let the command exit on its own so the session is stored completely. It gives up after 30 minutes.
Keep the same host directory mounted at /home/pwuser/.linkedin-mcp on every later docker run, otherwise the server cannot find the session.
Configure Claude Desktop with Docker
macOS / Linux (absolute path in JSON):
{
"mcpServers": {
"mcp-server-linkedin": {
"command": "docker",
"args": [
"run", "--rm", "-i",
"-v", "/absolute/path/to/.linkedin-mcp:/home/pwuser/.linkedin-mcp",
"stickerdaniel/linkedin-mcp-server:latest"
]
}
}
}Spell that first path out in full. A client runs docker directly rather than through a shell, so a leading ~ reaches Docker unexpanded and it refuses the mount.
PowerShell (Windows): use a forward-slash JSON path. A backslash path like
C:\Users\Alice\.linkedin-mcp fails JSON parsing because \U is an invalid
escape. Use C:/Users/Alice/.linkedin-mcp instead, replacing Alice with your
username.
{
"mcpServers": {
"mcp-server-linkedin": {
"command": "docker",
"args": [
"run", "--rm", "-i",
"-v", "C:/Users/Alice/.linkedin-mcp:/home/pwuser/.linkedin-mcp",
"stickerdaniel/linkedin-mcp-server:latest"
]
}
}
}In PowerShell,~ is not expanded inside a composite Docker -v argument.
Use C:/Users/<you>/.linkedin-mcp or build the path with
$env:USERPROFILE\.linkedin-mcp before passing it to Docker.
Sessions expire over time. When tool calls start asking for authentication, repeat the login command above, or runuvx mcp-server-linkedin@latest --login on the host.
Docker Setup Help
Default (stdio): Standard communication for local MCP servers
Streamable HTTP: For a web-based MCP server
If no transport is specified, the server defaults to
stdioAn interactive terminal without explicit transport shows a chooser prompt
Session:
--auto-import/--no-auto-import- Import a session from a signed-in local browser on the first tool call that needs one, before falling back to manual login (ignored in Docker). On macOS the keychain may prompt once.--logout- Clear the stored session and every profile derived from it--login-viewer- With--login, show the login browser at a token-protected URL on port 6080. Needs the profile mount from Authentication.--user-data-dir PATH- Browser profile directory (default: ~/.linkedin-mcp/profile). Rotating or clearing a session deletes this directory and its parent, which holds the stored cookies and derived profiles.--claim-profile-root- Take over a profile directory the server will not claim on its own, such as one whose parent already holds other files. Needed once per directory.
Transport:
--transport {stdio,streamable-http}- Force the transport mode (default: stdio)--host HOST/--port PORT/--path PATH- HTTP server address (defaults: 127.0.0.1, 8000, /mcp)
Timeouts:
--timeout MS- Timeout for a single page operation (default: 5000)--tool-timeout SECONDS- Timeout for a whole tool call (default: 180). Raise it for heavy scrapes, slow networks, or a cold-start browser.--login-timeout SECONDS- How long the login browser waits for you to finish signing in (default: 1800; 0 = no limit).--login-viewerends the session after 30 minutes either way.--login-inline-wait SECONDS- How long a tool call waits for a login to finish before telling the model to retry (default: 25, max 45; 0 = return at once)
Shared browser:
--browser-wait SECONDS- How long to wait for another server process to hand over the shared browser (default: 25, max 45; 0 = report busy at once). Only matters with several MCP clients running at once.--browser-min-hold SECONDS- Shortest time this process keeps the shared browser before handing it over (default: 20). Clamped to 3 seconds below--browser-wait, so raise that one along with it. Higher means fewer browser restarts but longer waits for other clients.--browser-idle-timeout SECONDS- Close an idle browser and release the profile after this long without a tool call (default: 600; 0 = keep it open)
Browser:
--chrome-path PATH- Path to a Chrome/Chromium executable (rarely needed in Docker)--proxy-server URL- Route browser traffic through a proxy, asscheme://host:port. Set it up before--login; see Using a proxy.
Other:
--log-level {DEBUG,INFO,WARNING,ERROR}- Logging level (default: WARNING)
Plain--login still has no visible window in Docker. Add --login-viewer and publish 127.0.0.1:6080:6080 only for the one-shot login command. Docker is already headed by default, so --no-headless changes nothing. The experimental --daemon is ignored in Docker because its owner can outlive the virtual display.
HTTP Mode Example (for web-based MCP clients):
Bash / macOS / Linux:
docker run -it --rm \
-v ~/.linkedin-mcp:/home/pwuser/.linkedin-mcp \
-p 127.0.0.1:8080:8080 \
stickerdaniel/linkedin-mcp-server:latest \
--transport streamable-http --host 0.0.0.0 --port 8080 --path /mcpPowerShell (Windows):
$sessionDir = Join-Path $env:USERPROFILE ".linkedin-mcp"
docker run -it --rm `
-v "${sessionDir}:/home/pwuser/.linkedin-mcp" `
-p 127.0.0.1:8080:8080 `
stickerdaniel/linkedin-mcp-server:latest `
--transport streamable-http --host 0.0.0.0 --port 8080 --path /mcpBoth halves of that are needed, and they do different jobs. --host 0.0.0.0
makes the server reachable inside the container: a process bound to
127.0.0.1 in there cannot be reached through a published port at all. The
127.0.0.1: in front of -p is what limits it outside, to this machine.
Drop that prefix and Docker publishes on every interface, which puts an
endpoint with no authentication on your network. The server cannot tell the two
apart, so it warns either way.
Loopback publishing limits this to the machine, not to the container. Other
containers on the same host can still reach it through host.docker.internal
wherever that name resolves, which is the default on Docker Desktop and
OrbStack but not on native Linux Docker.
Runtime server logs are emitted by FastMCP/Uvicorn.
The HTTP server answers requests addressed to localhost or to the address it
is bound to, and refuses others with 421. That is what stops a website you
merely visit from pointing a domain at this server and using your LinkedIn
session through your own browser.
Reaching the server by any other name is refused, including a machine name on
your network and the public name in front of a reverse proxy. Either have the
proxy rewrite the upstream Host to the backend address, or name the host you
serve it under:
FASTMCP_HTTP_ALLOWED_HOSTS='["mcp.example"]'That permits exactly that name and keeps refusing everything else. The endpoint still has no authentication, so anything reachable beyond your own machine belongs behind something that provides it.
Test with mcp inspector:
Install and run mcp inspector
bunx @modelcontextprotocol/inspectorClick pre-filled token url to open the inspector in your browser
Select
Streamable HTTPasTransport TypeSet
URLtohttp://localhost:8080/mcpConnect
Test tools
Make sure Docker is installed
Check if Docker is running:
docker psPermission errors on
~/.linkedin-mcp: an older rootful Docker run may have created the directory as root. Fix it withsudo chown -R "$(id -u):$(id -g)" ~/.linkedin-mcp.
Make sure you have only one active LinkedIn session at a time
LinkedIn may require a login confirmation in the LinkedIn mobile app for
--loginLinkedIn may show a captcha challenge during login. Run
uvx mcp-server-linkedin@latest --loginwhich opens a browser where you can solve captchas manually. See the uvx setup for prerequisites.If Docker auth becomes stale after you re-login on the host, restart Docker once so it can fresh-bridge from the new source session generation.
Page operations failing (elements not found, navigation hangs): increase the browser page-op timeout:
--timeout 10000orTIMEOUT=10000(milliseconds, default 5000).Entire tool calls timing out (e.g. multi-section profiles, cold-start Chromium, slow containers): increase the per-tool execution timeout:
--tool-timeout 300orTOOL_TIMEOUT=300(seconds, default 180).First tool call with no session: if a locally logged-in browser has a live LinkedIn session, the server auto-imports it (see
AUTO_IMPORT_FROM_BROWSER/--auto-import) instead of forcing a manual login. On macOS the keychain may prompt once for Safe Storage access. If no importable browser session exists, it falls back to opening a login window and waits up toLOGIN_INLINE_WAITseconds (default 25, max 45;--login-inline-wait) so a quick sign-in resolves in one call. If the wait elapses, the tool returns a pending signal and the model retries in about 30 seconds. Neither the auto-import nor the inline wait applies under Docker or when the server is bound to a non-loopback HTTP host. Create the session on the host with--login, or use the explicit Docker--login --login-viewercommand.Users on slow connections may need higher values for either.
If tool calls answer "No valid LinkedIn session is available in Docker" on a machine that is not a container, the runtime was misdetected. This happened on Linux hosts running a Docker daemon for unrelated services. Set
LINKEDIN_MCP_CONTAINER=falseto override the detection;trueforces the opposite.
If Chrome is installed in a non-standard location, use
--chrome-path /path/to/chromeCan also set via environment variable:
CHROME_PATH=/path/to/chromeOn macOS and Linux the browser must be at least as new as the one that last opened your profile, and the server refuses the launch otherwise. (Not on Windows: a browser there cannot be asked its version without starting one, so the check is off.) An older browser can silently drop stores a newer one wrote, the saved session among them, and the failure then looks exactly like an expired login. The message names both versions. Going back to the bundled Chromium after running a newer Chrome once is the usual way to meet this; either run the newer browser again, whichever one that was, or run
--login, which moves the stored session aside and signs in fresh with the browser you have.--logoutalso clears it but discards the old session instead of keeping it recoverable, and it asks for confirmation on the terminal, so it is not usable from a server an MCP client started.Only Chrome, Chromium and Chrome for Testing are compared this way. Forks number themselves differently (Vivaldi is on 7.x, Edge's build number sits far below Chrome's under the same major), so pointing
CHROME_PATHat one turns the check off rather than producing a refusal nothing could satisfy.In the documented Docker setup this check does not apply. The container never opens the profile you created with
--login; it derives its own from your cookies, and by default rebuilds that from scratch on every start, so there is nothing for an older image to downgrade. WithEXPERIMENTAL_PERSIST_DERIVED_RUNTIMEthe derived profile is kept, and an image tag that moves backwards then throws it away and re-derives it, again with nothing for you to do. The check matters on the host, where the server opens that profile directly. Not during--loginitself, which moves the old profile aside before it starts a browser and so can never trip it.
🛡️ Using a proxy
Swiftproxy offers residential proxies with sticky sessions and worldwide geo-targeting. Its dedicated static ISP options include networks such as AT&T, Sky UK, and Rogers, with unlimited traffic and renewable addresses.
Use code PROXY90 for 10% off Try Swiftproxy →
RapidProxy offers 90M+ residential IPs worldwide for LinkedIn automation and browser workflows, with sticky sessions, geo-targeting, and high-concurrency support. Plans start at $0.55/GB with non-expiring traffic.
Use code RAPID10 for 10% off Try RapidProxy for free →
LinkedIn scores the address a session signs in from. Your account's usual IP address is the safe one. You should use a proxy in your country when the server cannot use it: a VPS, another country, or a second account that must not share the first one's address.
Take a dedicated static ISP address and keep it. From a residential pool, use a sticky session that holds one address (never per-request rotation). A WireGuard or Tailscale connection to your home network works too.
Setup:
Set the proxy up before
--login. Moving an existing session to a new address triggers a LinkedIn checkpoint. That includes a session from--import-from-browser, which was created on your real address.--proxy-server scheme://host:portorPROXY_SERVER, withhttp,https,socks4orsocks5. Only browser traffic is routed, not the MCP transport.Pass credentials through
PROXY_USERNAMEandPROXY_PASSWORD, or include them inPROXY_SERVERusing the combinedhttp://user:pass@host:portform. The combined form is not accepted by the--proxy-serverCLI option.PROXY_BYPASS=localhost,127.0.0.1,::1reaches local targets directly. With a proxy set, Chromium routeslocalhostthrough it too.
Chromium cannot authenticate to a SOCKS proxy, so credentials require an
http(s)endpoint. If your provider only offers authenticated SOCKS5, run a local relay that holds the credentials and point the server at that.A wrong proxy password shows up as a timeout or a failed sign-in, because Chromium retries the authentication challenge until the page times out. If sessions stop working right after you add a proxy, check the proxy credentials first.
Auto-import is skipped while a proxy is configured: the imported session would move from your real address to the proxy. Use
--login.Inside a container
127.0.0.1is the container itself, so a relay on the host ishost.docker.internal; native Linux Docker also needs--add-host=host.docker.internal:host-gateway.
🐍 Local Setup (Develop & Contribute)
Contributions are welcome! See CONTRIBUTING.md for architecture guidelines and checklists. Packet: search first, then add evidence to an existing issue or prepare a new report. Agents follow the packet skill. Humans use the issue forms.
Prerequisites: Git and uv installed
Installation
# 1. Clone repository
git clone https://github.com/stickerdaniel/linkedin-mcp-server
cd linkedin-mcp-server
# 2. Install UV package manager (if not already installed)
curl -LsSf https://astral.sh/uv/install.sh | sh
# 3. Install dependencies
uv sync
uv sync --group dev
# 4. Install pre-commit hooks
uv run pre-commit install
# 5. Start the server
uv run -m linkedin_mcp_serverLocal Setup Help
Session:
--login- Open a browser to sign in and save the session--import-from-browser [BROWSER]- Reuse a session from a locally signed-in Chromium browser (chrome,chromium,brave,edge,arc,vivaldi,helium,yandex,whale,auto). Bare flag picksauto, the most recently used browser with a live LinkedIn session.--auto-import/--no-auto-import- Import a session from a signed-in local browser on the first tool call that needs one, before falling back to manual login (default: on). Skipped in Docker, behind a proxy, and on a non-loopback HTTP bind. On macOS the keychain may prompt once.--status- Check whether the stored session is valid, then exit--logout- Clear the stored session--user-data-dir PATH- Browser profile directory (default: ~/.linkedin-mcp/profile). Rotating or clearing a session deletes this directory and its parent, which holds the stored cookies and derived profiles.--claim-profile-root- Take over a profile directory the server will not claim on its own, such as one whose parent already holds other files. Needed once per directory.
Transport:
--transport {stdio,streamable-http}- Force the transport mode (default: stdio)--host HOST/--port PORT/--path PATH- HTTP server address (defaults: 127.0.0.1, 8000, /mcp)
Timeouts:
--timeout MS- Timeout for a single page operation (default: 5000)--tool-timeout SECONDS- Timeout for a whole tool call (default: 180). Raise it for heavy scrapes, slow networks, or a cold-start browser.--login-timeout SECONDS- How long the login browser waits for you to finish signing in (default: 1800; 0 = no limit).--login-viewerends the session after 30 minutes either way.--login-inline-wait SECONDS- How long a tool call waits for a login to finish before telling the model to retry (default: 25, max 45; 0 = return at once)
Shared browser:
--browser-wait SECONDS- How long to wait for another server process to hand over the shared browser (default: 25, max 45; 0 = report busy at once). Only matters with several MCP clients running at once.--browser-min-hold SECONDS- Shortest time this process keeps the shared browser before handing it over (default: 20). Clamped to 3 seconds below--browser-wait, so raise that one along with it. Higher means fewer browser restarts but longer waits for other clients.--browser-idle-timeout SECONDS- Close an idle browser and release the profile after this long without a tool call (default: 600; 0 = keep it open)
Browser:
--no-headless- Show the browser window (useful for debugging)--slow-mo MS- Delay between browser actions (default: 0, useful for debugging)--viewport WxH- Viewport size (default: 1280x720). Applies to windowless mode only; a headed launch uses the real window size.--chrome-path PATH- Path to a Chrome/Chromium executable--installer-temp-dir PATH- Parent directory for temporary files created during browser installation bootstrap (default: system temporary directory). Useful when system %TEMP% ancestry has non-standard ACLs or permissions.--proxy-server URL- Route browser traffic through a proxy, asscheme://host:port. Set it up before--login; see Using a proxy.
Other:
--log-level {DEBUG,INFO,WARNING,ERROR}- Logging level (default: WARNING)--help- Show help
Note: Most CLI options have environment variable equivalents. See
.env.examplefor details.
HTTP Mode Example (for web-based MCP clients):
uv run -m linkedin_mcp_server --transport streamable-http --host 127.0.0.1 --port 8000 --path /mcpClaude Desktop:
{
"mcpServers": {
"mcp-server-linkedin": {
"command": "uv",
"args": ["--directory", "/path/to/linkedin-mcp-server", "run", "-m", "linkedin_mcp_server"]
}
}
}stdio is used by default for this config.
Make sure you have only one active LinkedIn session at a time
LinkedIn may require a login confirmation in the LinkedIn mobile app for
--loginLinkedIn may show a captcha challenge during login. The
--logincommand opens a browser where you can solve it manually.
Use
--no-headlessto see browser actions and debug scraping problemsAdd
--log-level DEBUGto see more detailed logging
Browser profile is stored at
~/.linkedin-mcp/profile/Managed browser downloads are cached at
~/.linkedin-mcp/patchright-browsers/, shared with theuvxand MCP Bundle installationsThe browser cache keeps growing: Patchright keeps an old Chromium revision for as long as any installed version still references it, and a
uvarchive or a second worktree is such a reference. The server logs a warning naming what it holds. To reclaim the space, stop every LinkedIn MCP Server instance, delete~/.linkedin-mcp/patchright-browsers/, and let the next launch download the current browser.Use
--logoutto clear the profile and start fresh
Check Python version:
python --version(should be 3.12.4+)Reinstall Patchright:
uv run patchright install chromiumReinstall dependencies:
uv sync --reinstall
Page operations failing (elements not found, navigation hangs): increase the browser page-op timeout:
--timeout 10000orTIMEOUT=10000(milliseconds, default 5000).Entire tool calls timing out (e.g. multi-section profiles, cold-start Chromium, slow containers): increase the per-tool execution timeout:
--tool-timeout 300orTOOL_TIMEOUT=300(seconds, default 180).First tool call with no session: if a locally logged-in browser has a live LinkedIn session, the server auto-imports it (see
AUTO_IMPORT_FROM_BROWSER/--auto-import) instead of forcing a manual login. On macOS the keychain may prompt once for Safe Storage access. If no importable browser session exists, it falls back to opening a login window and waits up toLOGIN_INLINE_WAITseconds (default 25, max 45;--login-inline-wait) so a quick sign-in resolves in one call. If the wait elapses, the tool returns a pending signal and the model retries in about 30 seconds. Neither the auto-import nor the inline wait applies under Docker or when the server is bound to a non-loopback HTTP host. Create the session on the host with--login, or use the explicit Docker--login --login-viewercommand.Users on slow connections may need higher values for either.
If tool calls answer "No valid LinkedIn session is available in Docker" on a machine that is not a container, the runtime was misdetected. This happened on Linux hosts running a Docker daemon for unrelated services. Set
LINKEDIN_MCP_CONTAINER=falseto override the detection;trueforces the opposite.
If Chrome is installed in a non-standard location, use
--chrome-path /path/to/chromeCan also set via environment variable:
CHROME_PATH=/path/to/chromeOn macOS and Linux the browser must be at least as new as the one that last opened your profile, and the server refuses the launch otherwise. (Not on Windows: a browser there cannot be asked its version without starting one, so the check is off.) An older browser can silently drop stores a newer one wrote, the saved session among them, and the failure then looks exactly like an expired login. The message names both versions. Going back to the bundled Chromium after running a newer Chrome once is the usual way to meet this; either run the newer browser again, whichever one that was, or run
--login, which moves the stored session aside and signs in fresh with the browser you have.--logoutalso clears it but discards the old session instead of keeping it recoverable, and it asks for confirmation on the terminal, so it is not usable from a server an MCP client started.Only Chrome, Chromium and Chrome for Testing are compared this way. Forks number themselves differently (Vivaldi is on 7.x, Edge's build number sits far below Chrome's under the same major), so pointing
CHROME_PATHat one turns the check off rather than producing a refusal nothing could satisfy.
FAQ
Is this safe to use? Will I get banned? This 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.
What if my agents execute too many actions? Tool calls run sequentially through a queue. You are responsible for the volume of automation you run; use it sparingly and prompt your agents responsibly.
Acknowledgements
Built with FastMCP and Patchright.
Use in accordance with LinkedIn's User Agreement. Automated access may violate LinkedIn's terms and can lead to account restrictions. This tool is for personal use only and comes with no warranty of any kind.
License
This project is licensed under the Apache 2.0 license.
Building on this project is welcome! See the license for terms and the NOTICE for attribution.
Available Tools
17 toolsclose_sessionClose SessionADestructive
Close the current browser session and clean up resources.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The destructiveHint annotation already signals this is a destructive action, and the description confirms it by saying 'Close' and 'clean up resources'. It adds some scope detail ('current browser session') but does not describe side effects such as invalidating cookies, logging out the user, or making the session unusable. Since the annotations carry much of the safety signal, a mid-range score is appropriate.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The entire description is a single, front-loaded sentence with no redundant words. It states the action and consequence efficiently, earning a top score for conciseness.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a zero-parameter, destructive tool with an output schema and annotation coverage, the description is complete enough for an agent to know what will happen. The absence of an alternative, zero parameters, and existing destructiveHint mean no additional operational context is essential.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool has zero parameters and the schema is empty with 100% coverage, so there are no parameter semantics for the description to clarify. With no params, the baseline is 4, and the description does not need to add parameter-level explanation.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses a specific verb ('Close') and names the resource ('current browser session') plus a clear secondary purpose ('clean up resources'). It is well-distinguished from all sibling tools, which focus on profiles, searches, messaging, and feed actions, not session lifecycle.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives clear context that this tool ends the current browser session, implying it should be used when the session is no longer needed. There are no sibling tools providing an alternative session-close action, so explicit exclusions are unnecessary. A fully explicit 'use this when you are done' statement would improve clarity, but the context is already strong.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
connect_with_personConnect With PersonADestructive
Send a LinkedIn connection request or accept an incoming one.
The tool is annotated with destructiveHint so MCP clients will prompt for user confirmation before execution.
| Name | Required | Description | Default |
|---|---|---|---|
| linkedin_username | Yes | LinkedIn username (e.g., "stickerdaniel", "williamhgates") | |
| note | No | Optional note to include with the invitation |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare destructiveHint, and the description adds that 'MCP clients will prompt for user confirmation before execution,' which provides some behavioral context. However, it does not explain side effects beyond confirmation, such as potential duplicate requests or permission requirements, leaving room for more transparency.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is two sentences with no fluff. It leads with the primary purpose and then notes the annotation implication. Every word earns its place, making it appropriately concise and front-loaded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple tool with a clear action (connection request/accept), full schema coverage, and annotations covering safety, the description is sufficiently complete. It doesn't need to elaborate on return values since an output schema exists, and the action is straightforward. Minor gaps like error handling or idempotency could be added, but they aren't essential.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The input schema provides full descriptions for both parameters (linkedin_username and note), so schema description coverage is 100%. The description itself adds no extra parameter meaning beyond what is already in the schema, matching the baseline of 3.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool's function: 'Send a LinkedIn connection request or accept an incoming one.' This specifies the verb (send/accept) and resource (connection request), and distinguishes it from siblings like send_message and get_inbox, making its purpose unmistakable.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description implies when to use the tool by stating its purpose, but it does not explicitly provide context or alternatives. There is no mention of when not to use it or comparisons to similar tools, so the guidance is only implicit.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_company_employeesGet Company EmployeesARead-only
List employees at a company from the LinkedIn /people/ page, including the demographics aggregate that this view exposes: where employees live, where they studied, and a function breakdown (Engineering, Sales, Operations, etc.). The demographics are unique to this tool.
For filtered search by network degree (1st/2nd/3rd) or location, prefer search_people with current_company set to the company URN id. That path also returns more result pages than the /people/ tab.
The optional keywords filter narrows results by name, title, or skill.
company_name must be the exact LinkedIn URL slug (the path segment after /company/), not the display name. LinkedIn assigns unique slugs and the display name often does not match. For example, the AI lab Anthropic lives at /company/anthropicresearch/, not /company/anthropic/. If you are unsure of the slug, call search_companies first and pick the slug from the returned references.
| Name | Required | Description | Default |
|---|---|---|---|
| company_name | Yes | LinkedIn company URL slug (e.g., "docker", "anthropicresearch", "microsoft") | |
| keywords | No | Optional filter by name, job title, or skill (e.g., "engineer", "sales") |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint and openWorldHint, but the description adds behavioral context: it derives data from a specific LinkedIn page, exposes unique demographics, and warns about fewer result pages compared to search_people. It also clarifies the slug requirement.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is multi-paragraph and detailed, but every sentence adds value—use cases, alternatives, parameter constraints, and an example. While slightly longer than strictly necessary, it's efficiently structured around purpose, usage, and uniqueness.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given annotations (readOnlyHint, openWorldHint), the description covers all necessary behavioral context: output includes aggregates, page limits relative to search_people, and the surprising slug mismatch. With an output schema and both params documented, no critical gaps remain.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema describes both parameters with examplesags: company_name is a slug, keywords is optional filter. The description elaborates on the slug format and gives an example, but doesn't fully detail keyword matching semantics beyond 'name, title, or skill.' Schema coverage is 100% and description reinforces it, so a high score is warranted.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb and resource:
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly directs users to search_people for filtered searches by degree/location and notes the /people/ tab limitation, providing clear exclusions.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_company_postsGet Company PostsARead-only
Get recent posts from a company's LinkedIn feed.
| Name | Required | Description | Default |
|---|---|---|---|
| company_name | Yes | LinkedIn company name (e.g., "docker", "anthropic", "microsoft") |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint and openWorldHint. The description adds no extra context about authentication, rate limits, or side effects. It does not contradict the annotations, but it also does not provide additional behavioral details beyond what the annotations already imply.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single, concise sentence with no unnecessary words or repetition. It is well-structured and directly states the tool's function.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The tool is simple and the description sufficiently covers its purpose. Since an output schema exists (per context signals), the description does not need to explain return values, and no additional context is required for this straightforward operation.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema provides a description for the only parameter (company_name) with examples. The tool description does not add further semantic meaning beyond the schema, and since the schema coverage is high, the baseline of 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the action ('get') and the resource ('recent posts from a company's LinkedIn feed'). It effectively differentiates from sibling tools like 'get_company_profile' or 'get_company_employees' by specifying the data type (posts).
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description does not explicitly state when to use this tool versus alternatives, nor does it provide conditions or exclusions. However, the purpose is so straightforward that the usage context is implicitly clear for someone familiar with LinkedIn tools.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_company_profileGet Company ProfileARead-only
Get a specific company's LinkedIn profile.
| Name | Required | Description | Default |
|---|---|---|---|
| company_name | Yes | LinkedIn company name (e.g., "docker", "anthropic", "microsoft") | |
| sections | No | Comma-separated list of extra sections to scrape. The about page is always included. Available sections: posts, jobs Examples: "posts", "posts,jobs" Default (None) scrapes only the about page. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The annotations (readOnlyHint=true, openWorldHint=true) already indicate a read-only, side-effect-free operation. The description does not contradict these hints and adds no additional behavioral detail such as rate limits or authentication requirements, which would have improved transparency.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single, concise sentence that immediately conveys the tool's purpose. It is front-loaded and free of unnecessary wording, making it efficiently scannable.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the simplicity of the tool, the description is adequate. It conveys the core function, and the schema covers parameter details. However, it might slightly benefit from mentioning that it scrapes the profile (as implied in the schema), but this is not essential for basic usage.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The input schema provides high coverage (100%) with detailed descriptions for both parameters, including examples and defaults. The description itself adds no extra meaning beyond the schema, so it does not enhance parameter understanding.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool's function: 'Get a specific company's LinkedIn profile.' It uses a specific verb ('Get') and resource ('company's LinkedIn profile'), which distinguishes it from sibling tools like get_company_posts and get_company_employees.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides no guidance on when to use this tool versus the sibling tools. It does not mention any conditions or alternatives, leaving the user to infer usage from the name alone.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_conversationGet ConversationARead-only
Read a specific messaging conversation.
Provide either linkedin_username or thread_id to identify the conversation.
When looked up by linkedin_username, resolution searches the messaging inbox for the participant's display name and click-visits every matching row to capture its thread ID — LinkedIn's sidebar has no anchor hrefs or thread-id attributes, so this is the only available path. Each visit selects the row in the LinkedIn UI and may mark it as read. Pass thread_id directly to skip this enumeration.
| Name | Required | Description | Default |
|---|---|---|---|
| linkedin_username | No | LinkedIn username of the conversation participant | |
| thread_id | No | LinkedIn messaging thread ID | |
| index | No | 0-based selector for which thread to open when the participant has multiple threads (e.g. an organic 1-on-1 plus an InMail). Ignored when thread_id is provided. To enumerate thread IDs first, call search_conversations. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description discloses a side effect: 'Each visit selects the row in the LinkedIn UI and may mark it as read.' This directly contradicts the annotation readOnlyHint=true, which claims the tool performs no modifications. Per the scoring rules, a contradiction with annotations yields a score of 1, despite the description being otherwise transparent.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is well-structured and front-loaded with the main purpose. The additional details about enumeration and click-visits are necessary for proper usage, but the paragraph is a bit dense. Every sentence earns its place, though it could be slightly more streamlined.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a tool with three optional params and a complex fallback mechanism, the description is thorough. It covers the enumeration fallback, the side effect of marking as read, the index selector behavior, and points to search_conversations for thread ID enumeration. It provides sufficient context even without needing to describe the output schema.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Even though schema coverage is 100%, the description adds significant meaning beyond the schema. It explains the relationship between linkedin_username and thread_id, clarifies that index is ignored when thread_id is provided, and describes the enumeration behavior. This enriches all three parameters beyond their basic schema descriptions.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description starts with 'Read a specific messaging conversation', which uses a specific verb and resource. It clearly distinguishes from sibling tools like get_inbox (list all conversations) and search_conversations (enumerate thread IDs), and explains the two identification paths.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly states when to use the tool and how to choose between linkedin_username and thread_id. It also directs users to call search_conversations to enumerate thread IDs first, and explains when to pass thread_id directly, providing clear context and an explicit alternative.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_feedGet FeedARead-only
Get posts from the authenticated user's LinkedIn feed.
| Name | Required | Description | Default |
|---|---|---|---|
| num_posts | No | Number of posts to fetch (1-50, default 10). Posts are loaded in batches of ~5 as the page scrolls, so the actual count may slightly exceed the target. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description does not disclose behavioral traits beyond the annotations: it only says 'Get posts', which is consistent with readOnlyHint=true but adds no new behavioral context. Notable behavior like batched loading and count overshooting appears only in the schema property description, not in the tool description.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single short sentence with no filler, front-loading the action and resource. It is concise without sacrificing clarity.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple read-only feed fetch with one optional parameter and an output schema, the description plus schema provide all necessary information. The feed scope is clear, and nothing critical is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, with the num_posts parameter fully documented including default, range, and batch behavior. The tool description itself adds no parameter detail, but the schema already carries the full semantic burden, so the baseline of 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb 'Get' and a specific resource 'posts from the authenticated user's LinkedIn feed', which clearly distinguishes it from sibling tools like get_company_posts and search_posts. The phrase 'authenticated user' pins the scope unambiguously.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The intended use — fetching the current user's LinkedIn feed — is reasonably implied by the description, but there is no explicit guidance about when to choose this over alternative tools such as search_posts or get_company_posts. No exclusions or routing hints are provided.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_inboxGet InboxARead-only
List recent conversations from the LinkedIn messaging inbox.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Maximum number of conversations to load (1-50, default 20) |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already carry the safety profile (readOnlyHint=true) and the volatility expectation (openWorldHint=true), lowering the bar. The description adds modest behavioral value by specifying a recency ordering and inbox scope, but it does not disclose pagination behavior or whether ordering is guaranteed beyond the word 'recent.'
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
One sentence with zero filler: the verb is front-loaded, the resource and scope are named immediately, and no structured information from the schema or annotations is redundantly repeated.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple list tool with one fully documented optional parameter, an output schema, and safety annotations, almost nothing an agent needs to call it is missing. The only slight gap is pinning down the meaning of 'recent' and the ordering guarantee, which is minor at this complexity level.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%: the single parameter (limit) has its type, numeric range, default, and meaning fully documented in the schema. The description contributes no parameter-level detail, so the baseline 3 for fully covered schemas applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb ('List') and a well-defined resource ('recent conversations from the LinkedIn messaging inbox'). It distinguishes cleanly from the sibling tools get_conversation (a single thread's messages) and search_conversations (query-driven search), so an agent can tell them apart without opening schemas.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description establishes clear context: this is the default, recency-ordered browsing entry point for inbox conversations. However, it never names the alternatives or states when not to use it — especially relevant given the closely related search_conversations and get_conversation siblings — so the routing decision is left to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_job_detailsGet Job DetailsARead-only
Get job details for a specific job posting on LinkedIn.
| Name | Required | Description | Default |
|---|---|---|---|
| job_id | Yes | LinkedIn job ID (e.g., "4252026496", "3856789012") |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and openWorldHint=true, so the description does not need to restate basic safety. The description adds no meaningful behavioral context beyond the annotations, such as what kind of details are returned or whether the job must be publicly accessible. It is consistent with the annotations, so no contradiction.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is one short sentence with no filler. It front-loads the action and resource, and every word earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's simplicity—one parameter, full schema coverage, output schema present, and read-only annotations—the description is nearly complete. The only minor gap is lack of explicit guidance on when to choose this over search_jobs, but the 'specific job posting' wording covers the core usage context.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema has 100% coverage for the single parameter, including an example format for job_id. The description does not add additional parameter meaning beyond what the schema already provides, but with full schema coverage the baseline of 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the action ('Get'), the resource ('job details'), and the scope ('for a specific job posting on LinkedIn'). It distinguishes itself from sibling tools like search_jobs and get_saved_jobs by emphasizing a specific job ID rather than searching or listing.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The phrase 'for a specific job posting' implies this tool should be used when you already have a job_id and need details, as opposed to searching for jobs. However, it does not explicitly state when not to use it or name alternative tools.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_my_profileGet My ProfileARead-only
Get the authenticated user's own LinkedIn profile.
Navigates to /in/me/ and resolves the redirect to obtain the real username before scraping, so the url field in the result is the actual profile URL (e.g. linkedin.com/in/johndoe/) rather than /in/me/.
| Name | Required | Description | Default |
|---|---|---|---|
| sections | No | Comma-separated list of extra sections to scrape. The main profile page is always included. Available sections: experience, education, interests, honors, languages, certifications, skills, projects, contact_info, posts Examples: "experience,education", "contact_info", "skills,projects" Default (None) scrapes only the main profile page. | |
| max_scrolls | No | Maximum pagination attempts per section (same as get_person_profile). |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint and openWorldHint, so the safety profile is covered. The description adds valuable behavioral context by explaining that the tool navigates to /in/me/, follows the redirect, and returns the canonical profile URL instead of the /in/me/ placeholder.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is front-loaded with the core purpose in the first sentence, and the second sentence adds a single high-value behavioral detail about redirect resolution. There is no filler, repetition of schema content, or tangential information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the low complexity, full schema coverage, and presence of an output schema, the description provides everything needed to invoke the tool correctly. The redirect-resolution note closes the only likely source of confusion about the returned url field.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, and the input schema already documents sections and max_scrolls with defaults, examples, and constraints. The tool description does not add additional parameter meaning, so the baseline score of 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool gets the authenticated user's own LinkedIn profile, using a specific verb and resource. It also distinguishes itself from sibling tools like get_person_profile by emphasizing 'own' profile, and adds a concrete detail about resolving /in/me/ to the real URL.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description clearly implies this tool is for the authenticated user's own profile, which differentiates it from get_person_profile. It does not explicitly name alternatives or enumerate when-not-to-use, but the context is clear enough for an agent to select it appropriately.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_person_profileGet Person ProfileARead-only
Get a specific person's LinkedIn profile.
| Name | Required | Description | Default |
|---|---|---|---|
| linkedin_username | Yes | LinkedIn username (e.g., "stickerdaniel", "williamhgates") | |
| sections | No | Comma-separated list of extra sections to scrape. The main profile page is always included. Available sections: experience, education, interests, honors, languages, certifications, skills, projects, contact_info, posts Examples: "experience,education", "contact_info", "skills,projects", "honors,languages", "posts" Default (None) scrapes only the main profile page. | |
| max_scrolls | No | Maximum pagination attempts per section to load more content. On detail sections (experience, certifications, skills, etc.) this is the max number of "Show more" button clicks. On activity/posts it is the max scroll-to-bottom iterations. Applies to all sections in this call. Default (None) uses 5 for detail sections and 10 for posts. Increase when a profile has many items in a section (e.g., 30+ certifications, max_scrolls=20). To avoid slowing down other sections, request heavy sections in a separate call. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations declare readOnlyHint=true and openWorldHint=true, so the safety profile is clear. The description adds context about optional sections and pagination behavior (max_scrolls) which helps set expectations. It doesn't repeat annotation info, adding value on how the tool scrapes sections, though it could be more explicit about potential side effects (none) or rate limiting. Given annotations cover read-only nature, a 4 is appropriate.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single clear sentence, front-loaded with the action. It's concise, but it lacks detail on options that the schema covers, making it slightly under-specified as a standalone description. However, given the schema's richness, it's appropriately brief. It earns a 4 for efficiency.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With a rich schema (100% param coverage) and an output schema present, the description doesn't need to explain return values. The description covers the primary purpose and the schema handles details. It is complete enough for a straightforward read operation, though it doesn't mention error scenarios. Given the annotations, a 4 is fair.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema already provides 100% coverage of parameters with detailed descriptions (e.g., sections format, max_scrolls behavior). The description doesn't add additional parameter meaning beyond what's in the schema, so baseline 3 is justified. It does mention the main parameter implicitly in 'specific person', but that's minimal.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool retrieves a specific person's LinkedIn profile using a username, which distinguishes it from siblings like get_my_profile and get_company_profile. It is specific verb+resource with the required parameter noted.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description implies usage for retrieving a profile by username, and the schema adds context on optional sections. However, it doesn't explicitly contrast with sibling tools like search_people or get_sidebar_profiles, but the purpose is clear enough for an agent to select it when a known username is provided.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_sidebar_profilesGet Sidebar ProfilesARead-only
Get profile links from sidebar recommendation sections on a LinkedIn profile page.
Extracts profiles from "More profiles for you", "Explore premium profiles", and "People you may know" sidebar sections. Follows "Show all" links to return the full list from each section. Sections that redirect to linkedin.com/premium are skipped.
| Name | Required | Description | Default |
|---|---|---|---|
| linkedin_username | Yes | LinkedIn username of the profile page to scrape (e.g., "stickerdaniel", "williamhgates") |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The readOnlyHint annotation is respected—the description only retrieves data and does not imply any modification. It clearly states the actions (follows links, skips premium sections) and avoids contradicting the annotation. Since annotations already indicate read-only, the description adds value by detailing the exact procedural behavior.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is concise, using two sentences to convey the core function and key details. It avoids redundancy, though the first sentence is somewhat generic and could be merged with the specifics, but overall it is well-structured and to the point.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The tool is simple with one input, and the description adequately explains the output (profile links) and key behaviors (following 'Show all', skipping premium). It lacks explicit error handling or edge-case mentions, but given the simplicity and absence of an output schema, it is sufficiently complete for an agent to use correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The input schema fully describes the single parameter (linkedin_username) with a clear example. The description adds no additional semantic nuance beyond what is already in the schema, so the baseline of 3 applies given 100% schema coverage.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool extracts profile links from specific sidebar sections, using precise section names. It distinguishes itself from sibling tools like get_person_profile or search_people by focusing on sidebar recommendations, making the purpose unambiguous.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description implies usage for obtaining profile recommendations from a page's sidebar, but does not explicitly contrast with alternatives like get_person_profile or search_people. However, the detailed behavior (following 'Show all', skipping premium) provides implicit context for when this tool is appropriate.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_companiesSearch CompaniesBRead-only
Search for companies on LinkedIn.
| Name | Required | Description | Default |
|---|---|---|---|
| keywords | Yes | Search keywords (e.g., "fintech", "anthropic", "electric vehicles") |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description discloses no behavioral traits beyond what 'search' and 'LinkedIn' naturally imply. Annotations already declare readOnlyHint and openWorldHint, but the description adds no further context about result limits, matching behavior, or ordering. It is not contradictory, but it is also not informative beyond the basic action.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single compact sentence that wastes no words and immediately states the core action and target resource. It is appropriately sized for a simple tool.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a one-parameter tool with an output schema and safety annotations, the description is minimally adequate. However, it does not clarify edge behavior, result scope, or how it differs from nearby company-related tools, so an agent may still need to infer intent.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The input schema already fully documents the only parameter, keywords, with a useful example. The description adds no additional semantic detail beyond the schema, so a baseline score of 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly identifies the verb ('Search') and resource ('companies on LinkedIn'), so an agent can understand the basic function. However, it does not differentiate itself from sibling tools like get_company_profile or search_people, leaving some ambiguity about scope or result type.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no guidance on when to use this tool instead of search_people, get_company_profile, or get_company_employees. The description implies keyword-based discovery but provides no explicit context, prerequisites, or exclusions.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_conversationsSearch ConversationsBRead-only
Search messages by keyword.
| Name | Required | Description | Default |
|---|---|---|---|
| keywords | Yes | Search keywords to filter conversations | |
| limit | No | Maximum number of search-result rows to enumerate as conversation references (1-50, default 20). Each enumeration selects the row in LinkedIn's UI and may mark it as read, so a low cap is preferable for noisy queries. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The main description is minimal, and annotations declare readOnlyHint=true, suggesting a safe read-only operation. However, the limit parameter's schema description states that each enumeration 'may mark it as read', which is a side effect that contradicts the readOnlyHint annotation. This is a serious inconsistency.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single short sentence with no redundant wording. It is efficiently concise, though it could be slightly more informative without becoming verbose.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The description is adequate for a simple search tool, and the schema plus output schema fill in some details. However, it lacks usage guidance and does not mention what the returned conversation references represent, leaving the overall picture incomplete.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The input schema provides full descriptions for both parameters, including range, default, and a behavioral warning for limit. The main description adds no additional parameter meaning, so the baseline score of 3 applies given 100% schema coverage.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a clear action ('Search') and resource ('messages'), which aligns with the tool name. It does not explicitly distinguish from sibling search tools, but the resource is obvious enough.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
No explicit guidance is given about when to use this tool versus alternatives like get_conversation or search_people. The usage is implied by the name and description, but no when-to-use or when-not-to-use context is provided.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_jobsSearch JobsARead-only
Search for jobs on LinkedIn.
Returns job_ids that can be passed to get_job_details for full info.
| Name | Required | Description | Default |
|---|---|---|---|
| keywords | Yes | Search keywords (e.g., "software engineer", "data scientist") | |
| location | No | Optional location filter (e.g., "San Francisco", "Remote") | |
| max_pages | No | Maximum number of result pages to load (1-10, default 3) | |
| date_posted | No | Filter by posting date (past_hour, past_24_hours, past_week, past_month) | |
| job_type | No | Filter by job type, comma-separated (full_time, part_time, contract, temporary, volunteer, internship, other) | |
| experience_level | No | Filter by experience level, comma-separated (internship, entry, associate, mid_senior, director, executive) | |
| work_type | No | Filter by work type, comma-separated (on_site, remote, hybrid) | |
| easy_apply | No | Only show Easy Apply jobs (default false) | |
| sort_by | No | Sort results (date, relevance) |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already mark this as read-only and open-world. The description adds value by stating that the tool returns job_ids that serve as inputs to get_job_details, which is useful contextual behavior. It does not discuss pagination, max_pages behavior, or filtering limits, but the schema covers those details.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two concise sentences with no filler. The primary purpose is front-loaded, and the follow-up guidance about job_ids is immediately useful. Every sentence earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a search tool with a rich, fully documented schema and an output schema present, the description is sufficient: it states the purpose and the relationship to get_job_details. It does not need to explain parameters or return values in depth because those are already structured elsewhere.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the heavy lifting is already done by the input schema. The description adds no additional meaning about parameters beyond what the schema documents, matching the baseline for full schema coverage.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states a specific action ('Search') on a specific resource ('jobs on LinkedIn') and adds the key behavioral result: it returns job_ids intended for get_job_details. This differentiates it from sibling search tools like search_people and search_companies.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides clear context: use this tool when you want to search LinkedIn jobs, and use the returned IDs with get_job_details for fuller information. It does not explicitly list exclusions or compare against alternatives like get_saved_jobs, but the intended use case is obvious.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_peopleSearch PeopleARead-only
Search for people on LinkedIn.
| Name | Required | Description | Default |
|---|---|---|---|
| keywords | Yes | Search keywords (e.g., "software engineer", "recruiter at Google") | |
| location | No | Optional location filter (e.g., "New York", "Remote") | |
| network | No | Optional connection-degree filter. Each element is one of "F" (1st-degree), "S" (2nd-degree), "O" (3rd-degree and beyond). Example: ["F"] to only return 1st-degree connections. | |
| current_company | No | Optional current-employer filter. LinkedIn's currentCompany facet only filters on the numeric company URN id (e.g. "1115" for SAP); plain company names are accepted by the URL but ignored by LinkedIn and return the unfiltered result set. Look up a company's URN via get_company_profile -- it is exposed under references["about"]. For company-wide employee demographics (location/education/function breakdown) plus a slug-based lookup, use get_company_employees instead. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover the read-only and open-world nature of this tool. The schema adds a useful caveat that plain company names are silently ignored by LinkedIn's currentCompany filter, but the main description discloses no additional behavioral traits such as result limits, pagination, or network-scope behavior. This is adequate but not rich.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single front-loaded sentence with no wasted words. It is appropriately concise, though it sacrifices useful context that could have been included without much bloat, keeping it just below a top score.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The schema fully documents all parameters, annotations cover safety and open-world behavior, and an output schema exists, so the definition is mostly sufficient for invocation. The main gap is the lack of explicit sibling differentiation in the description itself, but the tool name and parameter guidance compensate.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the parameters are already well documented and the baseline is 3. The tool description itself adds no parameter-level meaning. The detailed current_company guidance lives in the schema, not in the description.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states a verb and resource: it searches for people on LinkedIn. This distinguishes it from other search tools like search_companies and search_jobs, though it does not explicitly contrast it with person-related siblings such as get_sidebar_profiles or get_person_profile.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description establishes a clear use case: finding people. The current_company parameter description adds valuable routing guidance, directing agents to get_company_profile for URN lookup and get_company_employees for company-wide demographics. However, the tool description itself does not provide a broader when-not-to-use statement versus other people lookup tools.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
send_messageSend MessageADestructive
Send a message to a LinkedIn user.
The recipient must be directly messageable from the profile page. This is a write operation when confirm_send is True.
| Name | Required | Description | Default |
|---|---|---|---|
| linkedin_username | Yes | LinkedIn username of the recipient | |
| message | Yes | The message text to send | |
| confirm_send | Yes | Must be True to send the message | |
| profile_urn | No | Optional profile URN (e.g. ACoAAB...) to construct the compose URL directly. Providing this bypasses the Message-button lookup and is more reliable when available. Obtain via get_person_profile. Note: inbox may not always show all messages; use search_conversations as a fallback. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the annotations (destructiveHint and openWorldHint), the description clarifies that the write happens only when confirm_send is True, which is a safeguard. It also adds the requirement for direct messageability. This extra context helps the agent understand the tool's behavior without relying solely on annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is extremely concise: two sentences that immediately state the purpose and key constraint. It front-loads the core function and avoids unnecessary elaboration, making it easy for an agent to scan.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a write operation, the description covers the essential behavioral prerequisites (messageability, confirm_send flag) and relies on the schema for parameter details. It does not mention authentication or output, but those are supplied by annotations and the presence of an output schema. The description is adequate for the tool's complexity.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The input schema already provides complete descriptions for all four parameters (100% coverage), so the tool description does not need to repeat them. It does not add any additional meaning beyond what the schema gives, which is the baseline expectation.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the action (send) and target (a LinkedIn user), with an important qualifier about direct messageability from the profile page. This establishes the tool's core purpose and differentiates it from read-only LinkedIn tools by explicitly labeling it a write operation.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides a prerequisite (the recipient must be directly messageable) and notes that the send only occurs when confirm_send is True, implicitly advising caution. However, it does not mention when to choose this tool over alternatives like connecting first or using the inbox, so guidance on context and exclusions is limited.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
17 tool updates
v4.13.2- Added
close_session - Added
connect_with_person - Added
get_company_employees - Added
get_company_posts - Added
get_company_profile - Added
get_conversation - Added
get_feed - Added
get_inbox - Added
get_job_details - Added
get_my_profile - Added
get_person_profile - Added
get_sidebar_profiles - Added
search_companies - Added
search_conversations - Added
search_jobs - Added
search_people - Added
send_message
17 tool updates
v4.13.1- Removed
close_session - Removed
connect_with_person - Removed
get_company_employees - Removed
get_company_posts - Removed
get_company_profile - Removed
get_conversation - Removed
get_feed - Removed
get_inbox - Removed
get_job_details - Removed
get_my_profile - Removed
get_person_profile - Removed
get_sidebar_profiles - Removed
search_companies - Removed
search_conversations - Removed
search_jobs - Removed
search_people - Removed
send_message
4 tool updates
v4.13.0- Added
get_company_employees - Added
get_my_profile - Added
get_person_profile - Added
search_companies
3 tool updates
v4.12.0- Added
get_feed - Removed
get_person_profile - Changed
search_people2 fields changed- added
Input schema / properties / current_companyAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Optional current-employer filter. LinkedIn's\ncurrentCompany facet only filters on the numeric company URN id\n(e.g. \"1115\" for SAP); plain company names are accepted by the\nURL but ignored by LinkedIn and return the unfiltered result\nset. Look up a company's URN via get_company_profile -- it is\nexposed under references[\"about\"]." +} - added
Input schema / properties / networkAdded value: +{ + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Optional connection-degree filter. Each element is one of\n\"F\" (1st-degree), \"S\" (2nd-degree), \"O\" (3rd-degree and beyond).\nExample: [\"F\"] to only return 1st-degree connections." +}
2 tool updates
v4.10.1- Changed
get_conversation1 field changed- added
Input schema / properties / indexAdded value: +{ + "default": 0, + "description": "0-based selector for which thread to open when the\nparticipant has multiple threads (e.g. an organic 1-on-1 plus\nan InMail). Ignored when thread_id is provided. To enumerate\nthread IDs first, call search_conversations.", + "minimum": 0, + "type": "integer" +}
- Changed
search_conversations1 field changed- added
Input schema / properties / limitAdded value: +{ + "default": 20, + "description": "Maximum number of search-result rows to enumerate as\nconversation references (1-50, default 20). Each enumeration\nselects the row in LinkedIn's UI and may mark it as read, so\na low cap is preferable for noisy queries.", + "maximum": 50, + "minimum": 1, + "type": "integer" +}
14 tool updates
v4.9.4- Changed
close_session1 field changed- added
Input schema / additionalPropertiesAdded value: +false
- Added
connect_with_person - Added
get_company_posts - Changed
get_company_profile5 fields changed- added
Input schema / additionalPropertiesAdded value: +false - added
Input schema / properties / company_name / descriptionAdded value: +"LinkedIn company name (e.g., \"docker\", \"anthropic\", \"microsoft\")" - removed
Input schema / properties / company_name / titleRemoved value: -"Company Name" - removed
Input schema / properties / get_employeesRemoved value: -{ - "default": false, - "title": "Get Employees", - "type": "boolean" -} - added
Input schema / properties / sectionsAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Comma-separated list of extra sections to scrape.\nThe about page is always included.\nAvailable sections: posts, jobs\nExamples: \"posts\", \"posts,jobs\"\nDefault (None) scrapes only the about page." +}
- Added
get_conversation - Added
get_inbox - Changed
get_job_details3 fields changed- added
Input schema / additionalPropertiesAdded value: +false - added
Input schema / properties / job_id / descriptionAdded value: +"LinkedIn job ID (e.g., \"4252026496\", \"3856789012\")" - removed
Input schema / properties / job_id / titleRemoved value: -"Job Id"
- Changed
get_person_profile5 fields changed- added
Input schema / additionalPropertiesAdded value: +false - added
Input schema / properties / linkedin_username / descriptionAdded value: +"LinkedIn username (e.g., \"stickerdaniel\", \"williamhgates\")" - removed
Input schema / properties / linkedin_username / titleRemoved value: -"Linkedin Username" - added
Input schema / properties / max_scrollsAdded value: +{ + "anyOf": [ + { + "maximum": 50, + "minimum": 1, + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Maximum pagination attempts per section to load more content.\nOn detail sections (experience, certifications, skills, etc.) this\nis the max number of \"Show more\" button clicks. On activity/posts\nit is the max scroll-to-bottom iterations. Applies to all sections\nin this call. Default (None) uses 5 for detail sections and 10 for\nposts. Increase when a profile has many items in a section\n(e.g., 30+ certifications, max_scrolls=20). To avoid slowing down\nother sections, request heavy sections in a separate call." +} - added
Input schema / properties / sectionsAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Comma-separated list of extra sections to scrape.\nThe main profile page is always included.\nAvailable sections: experience, education, interests, honors, languages, certifications, skills, projects, contact_info, posts\nExamples: \"experience,education\", \"contact_info\", \"skills,projects\", \"honors,languages\", \"posts\"\nDefault (None) scrapes only the main profile page." +}
- Removed
get_recommended_jobs - Added
get_sidebar_profiles - Added
search_conversations - Changed
search_jobs17 fields changed- added
Input schema / additionalPropertiesAdded value: +false - added
Input schema / properties / date_postedAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Filter by posting date (past_hour, past_24_hours, past_week, past_month)" +} - added
Input schema / properties / easy_applyAdded value: +{ + "default": false, + "description": "Only show Easy Apply jobs (default false)", + "type": "boolean" +} - added
Input schema / properties / experience_levelAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Filter by experience level, comma-separated (internship, entry, associate, mid_senior, director, executive)" +} - added
Input schema / properties / job_typeAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Filter by job type, comma-separated (full_time, part_time, contract, temporary, volunteer, internship, other)" +} - added
Input schema / properties / keywordsAdded value: +{ + "description": "Search keywords (e.g., \"software engineer\", \"data scientist\")", + "type": "string" +} - added
Input schema / properties / locationAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Optional location filter (e.g., \"San Francisco\", \"Remote\")" +} - added
Input schema / properties / max_pagesAdded value: +{ + "default": 3, + "description": "Maximum number of result pages to load (1-10, default 3)", + "maximum": 10, + "minimum": 1, + "type": "integer" +} - removed
Input schema / properties / search_termRemoved value: -{ - "title": "Search Term", - "type": "string" -} - added
Input schema / properties / sort_byAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Sort results (date, relevance)" +} - added
Input schema / properties / work_typeAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Filter by work type, comma-separated (on_site, remote, hybrid)" +} - changed
Input schema / requiredPrevious value: -[ - "search_term" -]New value: +[ + "keywords" +] - added
Output schema / additionalPropertiesAdded value: +true - removed
Output schema / propertiesRemoved value: -{ - "result": { - "items": { - "additionalProperties": true, - "type": "object" - }, - "title": "Result", - "type": "array" - } -} - removed
Output schema / requiredRemoved value: -[ - "result" -] - removed
Output schema / titleRemoved value: -"_WrappedResult" - removed
Output schema / x-fastmcp-wrap-resultRemoved value: -true
- Added
search_people - Added
send_message
6 tool updates
- First observed
close_session - First observed
get_company_profile - First observed
get_job_details - First observed
get_person_profile - First observed
get_recommended_jobs - First observed
search_jobs
TDQS
Scored across 17 tools
Each tool targets a distinct resource or action (profile, company, job, messaging, search, session) with clear boundaries. Even similar tools like get_person_profile and get_my_profile are distinct through target user. Detailed descriptions prevent confusion.
All tools follow a consistent verb_noun pattern using lowercase and underscores (e.g., get_company_profile, search_people, send_message). The naming is uniform and predictable.
With 17 tools, the set is slightly above the typical 3-15 range but still well-scoped for the breadth of LinkedIn interactions covered (profiles, companies, jobs, messaging, feed, search). No tool feels redundant.
The tool set covers core read operations and limited write actions (connect, send message). Missing are posting, liking, commenting, or profile updates. For a general LinkedIn assistant, these gaps are notable but the existing tools handle key workflows.
Maintenance
Related MCP Connectors
Give AI agents the LinkedIn tools to find, qualify, engage, and follow up with prospects.
LinkedIn data for AI agents: profiles, companies, jobs, posts, search. Sales research, recruiting.
LinkedIn for AI agents: inbox, invitations, Sales Navigator search, posts. Quotas and webhooks.
Live LinkedIn data for AI agents: profiles, companies, jobs, posts, email finding. No account risk.
Related MCP Servers
- AlicenseAqualityFmaintenanceEnables searching and scraping of LinkedIn for structured data on people, companies, and job listings. It allows AI clients to retrieve detailed profiles, experience, and activity sections using browser automation.7185MIT
- AlicenseNot gradedqualityDmaintenanceEnables AI assistants to search, filter, and extract job listings from LinkedIn using an automated headless browser with semantic AI filtering and deduplication.9 npmMIT
- AlicenseAqualityBmaintenanceLets an AI assistant operate LinkedIn through an authenticated browser session, enabling profile management, posting, networking, messaging, job search, and automated applications.10056 npm1MIT
- AlicenseAqualityBmaintenanceConnects LinkedIn to AI assistants, enabling lead search, profile analysis, messaging, and workflow automation through a cloud browser. Supports sales, recruiting, and market research tasks.59194 npmMIT