linkedin-mcp-server
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-serverfind me software engineer jobs in Berlin"
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
Disclaimer: This is an independent, community project. It is not affiliated with, authorized by, endorsed by, or sponsored by LinkedIn Corporation or Microsoft. "LinkedIn" is a registered trademark of LinkedIn Corporation and is used here only descriptively to identify the third-party service this software interoperates with.
An MCP server that lets AI assistants like Claude read LinkedIn data through your own logged-in browser session. Access profiles and companies, search for jobs, or get job details.
Sponsor
This MCP server is free and open source, supported by Unipile. It runs locally with your own browser session. Unipile is the fully managed cloud alternative: a hosted LinkedIn API for Classic, Sales Navigator, and Recruiter that handles auth, sessions, and infrastructure for you. Try it free for 7 days →
Related MCP server: LinkedIn Agent MCP
Installation Methods - MCP Server for LinkedIn
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 |
| Send a message to a LinkedIn user (requires confirmation) |
| 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) |
| Close browser session and clean up resources |
🚀 uvx Setup (Recommended - Universal)
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
Transport Modes:
Default (stdio): Standard communication for local MCP servers
Streamable HTTP: For web-based MCP server
If no transport is specified, the server defaults to
stdioAn interactive terminal without explicit transport shows a chooser prompt
CLI Options:
--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.--logout- Clear the stored session--no-headless- Show the browser window (useful for debugging)--log-level {DEBUG,INFO,WARNING,ERROR}- Logging level (default: WARNING)--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)--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-viewer- Docker only: show the--loginbrowser at a token-protected URL on port 6080 (see Authentication)--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)--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)--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.--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.--chrome-path PATH- Path to a Chrome/Chromium executable--proxy-server URL- Route browser traffic through a proxy, asscheme://host:port. Set the password viaPROXY_PASSWORD, which keeps it out of the process list.
Import a session from your everyday browser:
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
Installation issues:
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.
Session issues:
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
Login issues:
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.
Timeout issues:
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.
Told to run --login on the host when you already did:
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.
Using a proxy:
Most people should not use one. LinkedIn's own guidance for reducing security challenges is to avoid a VPN or proxy, and it scores the addresses a session signs in from. A home connection you have used for years is a trust signal; a commercial exit node with a history you cannot see is not, and switching to one is itself the kind of change that triggers a checkpoint. A proxy is worth it in one case: the server runs somewhere its address is obviously a data centre, or in a different country from the account's history. Even then, a WireGuard or Tailscale exit node on your own home network beats any paid provider, because the address really is yours. If you do buy one, take a dedicated static ISP address and keep it, rather than a rotating residential pool.
Route the browser through a proxy with
--proxy-server http://host:port(http,https,socks4andsocks5are accepted). Only browser traffic is routed, not the MCP transport.Credentials go in
PROXY_USERNAMEandPROXY_PASSWORD. There is no--proxy-passwordflag on purpose: command-line arguments are readable by every other user on the machine.PROXY_SERVERalso accepts the combinedhttp://user:pass@host:portform most providers hand out.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.Local addresses go through the proxy too. Chromium's usual direct route for
localhostis removed when a proxy is set, so addPROXY_BYPASS=localhost,127.0.0.1,::1if you need local targets reached directly.Auto-import is skipped while a proxy is configured: a session taken from a local browser was created on your real address, and moving it to the proxy is the very change that triggers a checkpoint. Use
--login.A wrong proxy password does not report itself: Chromium retries the authentication challenge until the page times out, so it surfaces as a timeout or a failed sign-in. If sessions stop working right after you add a proxy, check the credentials before assuming the session expired.
Set the proxy up before creating the session. Run
--loginwith the proxy already configured. Turning a proxy on for an existing profile moves a logged-in session to a new IP, which is what triggers a LinkedIn checkpoint. The same applies to--import-from-browser, which imports a session created on your real IP. Use a sticky session, not a rotating pool, for the same reason.
Custom Chrome path:
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
First-time setup behavior:
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.
Login issues:
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.
Timeout issues:
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.
Told to run --login on the host when you already did:
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.
🐳 Docker Setup
Prerequisites: Make sure Docker is installed and running.
Authentication
Log in once. The container opens a LinkedIn login browser that you drive from your own browser tab:
# 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-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 -v ~/.linkedin-mcp:/home/pwuser/.linkedin-mcp mount on every later docker run, otherwise the server cannot find the session.
Configure Claude Desktop with Docker
{
"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. On Windows write it with forward slashes, C:/Users/you/.linkedin-mcp; a backslash opens an escape sequence in JSON and C:\Users is not one the client can read.
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
Transport Modes:
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
CLI Options:
--log-level {DEBUG,INFO,WARNING,ERROR}- Logging level (default: WARNING)--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)--logout- Clear the stored session and every profile derived from it--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-viewer- With--login, show the login browser at a token-protected URL on port 6080. Needs the profile mount from Authentication.--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)--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)--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.--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.--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 the password viaPROXY_PASSWORD, which keeps it out of the process list.
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):
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 /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
Docker issues:
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.
Login issues:
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.
Timeout issues:
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.
Told to run --login on the host when you already did:
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.
Using a proxy:
Most people should not use one. LinkedIn's own guidance for reducing security challenges is to avoid a VPN or proxy, and it scores the addresses a session signs in from. A home connection you have used for years is a trust signal; a commercial exit node with a history you cannot see is not, and switching to one is itself the kind of change that triggers a checkpoint. A proxy is worth it in one case: the server runs somewhere its address is obviously a data centre, or in a different country from the account's history. Even then, a WireGuard or Tailscale exit node on your own home network beats any paid provider, because the address really is yours. If you do buy one, take a dedicated static ISP address and keep it, rather than a rotating residential pool.
Route the browser through a proxy with
--proxy-server http://host:port(http,https,socks4andsocks5are accepted). Only browser traffic is routed, not the MCP transport.Credentials go in
PROXY_USERNAMEandPROXY_PASSWORD. There is no--proxy-passwordflag on purpose: command-line arguments are readable by every other user on the machine.PROXY_SERVERalso accepts the combinedhttp://user:pass@host:portform most providers hand out.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.Local addresses go through the proxy too. Chromium's usual direct route for
localhostis removed when a proxy is set, so addPROXY_BYPASS=localhost,127.0.0.1,::1if you need local targets reached directly.Auto-import is skipped while a proxy is configured: a session taken from a local browser was created on your real address, and moving it to the proxy is the very change that triggers a checkpoint. Use
--login.A wrong proxy password does not report itself: Chromium retries the authentication challenge until the page times out, so it surfaces as a timeout or a failed sign-in. If sessions stop working right after you add a proxy, check the credentials before assuming the session expired.
Set the proxy up before creating the session. Run
--loginwith the proxy already configured. Turning a proxy on for an existing profile moves a logged-in session to a new IP, which is what triggers a LinkedIn checkpoint. The same applies to--import-from-browser, which imports a session created on your real IP. Use a sticky session, not a rotating pool, for the same reason.
Custom Chrome path:
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.
🐍 Local Setup (Develop & Contribute)
Contributions are welcome! See CONTRIBUTING.md for architecture guidelines and checklists. Please open an issue first to discuss the feature or bug fix before submitting a PR.
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_serverThe local server uses the same managed-runtime flow as MCPB and uvx: it prepares the Patchright Chromium browser cache in the background and opens LinkedIn login on the first auth-requiring tool call. You can still run uv run -m linkedin_mcp_server --login when you want to create the session explicitly.
Local Setup Help
CLI Options:
--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.--status- Check whether the stored session is valid, then exit--logout- Clear the stored session--no-headless- Show the browser window (useful for debugging)--log-level {DEBUG,INFO,WARNING,ERROR}- Logging level (default: WARNING)--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)--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.--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.--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--proxy-server URL- Route browser traffic through a proxy, asscheme://host:port. Set the password viaPROXY_PASSWORD, which keeps it out of the process list.--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.
Login issues:
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.
Scraping issues:
Use
--no-headlessto see browser actions and debug scraping problemsAdd
--log-level DEBUGto see more detailed logging
Session issues:
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
Python/Patchright issues:
Check Python version:
python --version(should be 3.12+)Reinstall Patchright:
uv run patchright install chromiumReinstall dependencies:
uv sync --reinstall
Timeout issues:
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.
Told to run --login on the host when you already did:
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.
Using a proxy:
Most people should not use one. LinkedIn's own guidance for reducing security challenges is to avoid a VPN or proxy, and it scores the addresses a session signs in from. A home connection you have used for years is a trust signal; a commercial exit node with a history you cannot see is not, and switching to one is itself the kind of change that triggers a checkpoint. A proxy is worth it in one case: the server runs somewhere its address is obviously a data centre, or in a different country from the account's history. Even then, a WireGuard or Tailscale exit node on your own home network beats any paid provider, because the address really is yours. If you do buy one, take a dedicated static ISP address and keep it, rather than a rotating residential pool.
Route the browser through a proxy with
--proxy-server http://host:port(http,https,socks4andsocks5are accepted). Only browser traffic is routed, not the MCP transport.Credentials go in
PROXY_USERNAMEandPROXY_PASSWORD. There is no--proxy-passwordflag on purpose: command-line arguments are readable by every other user on the machine.PROXY_SERVERalso accepts the combinedhttp://user:pass@host:portform most providers hand out.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.Local addresses go through the proxy too. Chromium's usual direct route for
localhostis removed when a proxy is set, so addPROXY_BYPASS=localhost,127.0.0.1,::1if you need local targets reached directly.Auto-import is skipped while a proxy is configured: a session taken from a local browser was created on your real address, and moving it to the proxy is the very change that triggers a checkpoint. Use
--login.A wrong proxy password does not report itself: Chromium retries the authentication challenge until the page times out, so it surfaces as a timeout or a failed sign-in. If sessions stop working right after you add a proxy, check the credentials before assuming the session expired.
Set the proxy up before creating the session. Run
--loginwith the proxy already configured. Turning a proxy on for an existing profile moves a logged-in session to a new IP, which is what triggers a LinkedIn checkpoint. The same applies to--import-from-browser, which imports a session created on your real IP. Use a sticky session, not a rotating pool, for the same reason.
Custom Chrome path:
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 it is welcome, including under a different license. Apache-2.0 attaches conditions to that, set out in section 4 of the license. The one most often missed is that the attribution in NOTICE has to travel with what you ship.
Available Tools
21 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.
comment_on_postComment On PostADestructive
Post a first-level comment on a LinkedIn post.
The tool is annotated with destructiveHint so MCP clients will prompt for user confirmation before execution. This publishes the signed-in account's own words under someone else's post — it is public and permanent in every practical sense, which is why it never runs without confirm_comment=True.
With confirm_comment=False this does a dry run: it navigates to the post, verifies LinkedIn exposes a usable comment box, and reports back without writing anything. No draft text is left behind, so the call is safe to make first to check reachability before committing the words.
Keep each comment's own wording distinct from the others you post in a session. Identical or near-identical text under several posts within a short window reads as spam to LinkedIn regardless of how slowly it was typed, and that is the fastest way this tool can earn a restriction.
| Name | Required | Description | Default |
|---|---|---|---|
| comment | Yes | The exact text to publish as the signed-in account's own words. It is posted verbatim; nothing is rewritten. | |
| post_url | Yes | The permalink of the single post to comment on. Both of LinkedIn's forms are accepted — /posts/<slug> (as reported in get_feed or search_posts references) and /feed/update/urn:li:activity:<id>/ — from a full URL, a missing scheme, or a site-relative path. | |
| confirm_comment | Yes | Must be True to post. False returns confirmation_required after verifying the comment box exists. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the destructiveHint annotation, the description explains what 'destructive' means here: the comment is public and permanent, and it will not run without explicit confirmation. It also discloses dry-run behavior, that no draft text is left behind, and the real risk of LinkedIn restrictions from near-duplicate comments. All of this adds meaningful behavioral context beyond the 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 front-loaded with the core purpose, then each subsequent sentence earns its place: safety model, dry-run workflow, and spam-risk warning. It is longer than a one-liner but every section adds actionable information, with no filler or repetition of schema details.
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 input schema is fully covered, an output schema exists, and the annotations flag destructiveness, the description adds exactly the missing operational context: how confirmation gates publishing, what the dry run does, and the behavioral consequences of misuse. An agent has enough to invoke the tool safely and 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?
Schema description coverage is 100%, and the schema already documents all three parameters thoroughly, including URL formats and the confirm_comment boolean semantics. The description reinforces these points but does not add substantial new meaning beyond what the parameter descriptions already state, 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?
The description opens with 'Post a first-level comment on a LinkedIn post', which names a specific verb, resource, and scope. It clearly distinguishes this from sibling tools like send_message, connect_with_person, or get_feed, and the qualifier 'first-level' adds precision about the kind of interaction.
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 concrete usage guidance: confirm_comment=False performs a dry run to verify the comment box exists before committing, and the tool never runs without confirm_comment=True. It does not explicitly name alternatives or say when not to use this tool, but the practical workflow (dry run first, then confirm) is clearly conveyed.
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 |
|---|---|---|---|
| note | No | Optional note to include with the invitation | |
| linkedin_username | Yes | LinkedIn username (e.g., "stickerdaniel", "williamhgates"). A full profile URL is accepted too and is reduced to the username. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description mostly restates the destructiveHint annotation by saying MCP clients will prompt for confirmation. It does not add meaningful behavioral context beyond that, such as irreversibility, notification to the recipient, authentication requirements, or rate-limit implications.
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 short and front-loaded with the primary action. The second sentence about destructiveHint is slightly redundant with the annotations, but it communicates the user-confirmation behavior clearly and does not add much bulk.
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 two well-documented parameters and an output schema present, the description is mostly sufficient for invoking the tool correctly. It lacks explicit guidance on when to accept versus send, or on side effects, but these are not critical given the schema and annotations.
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 parameter docs already explain linkedin_username and note in detail. The description adds no additional parameter-level meaning beyond what the schema provides.
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 specific action: send or accept a LinkedIn connection request. It names the exact resource and distinguishes it from sibling tools like send_message or search_people.
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 makes it clear this tool handles connection requests and acceptances, which provides usable context for when to choose it. It does not explicitly name alternatives or exclusions, but the action is specific enough to avoid major ambiguity.
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 |
|---|---|---|---|
| keywords | No | Optional filter by name, job title, or skill (e.g., "engineer", "sales") | |
| company_name | Yes | LinkedIn company URL slug (e.g., "docker", "anthropicresearch", "microsoft"). A full company URL is accepted too and is reduced to the slug. |
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 safety. It adds meaningful behavioral context: the data comes from the /people/ page, demographics are unique to this view, and the company_name must be the exact URL slug, not the display name, with a concrete example. This goes beyond annotations without contradicting them.
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 moderately long but every sentence earns its place: core purpose first, then usage routing, then keyword semantics, then the critical slug caveat. The Anthropic example is concrete and high-value, not filler. Structure is logical 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?
Given the output schema exists, annotations cover safety, and the description covers purpose, routing, param semantics, and a caveat with an example, the definition is complete for an agent to select and invoke the tool correctly. No critical gap is evident.
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%, but the description adds substantial semantic value beyond the schema. It clarifies that company_name must be the exact LinkedIn URL slug, warns that display names often differ, gives the Anthropic example, and tells the agent to resolve ambiguity via search_companies. This is exactly the kind of parameter nuance that prevents incorrect calls.
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 'List employees at a company from the LinkedIn /people/ page' and uniquely identifies its value-add: the demographics aggregate (location, education, function breakdown). It distinguishes itself from siblings by noting these demographics are unique to this tool, so an agent can immediately tell it apart from search_people or get_company_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?
Explicit guidance is provided for when to use this tool vs alternatives: for filtered search by network degree or location, prefer search_people with current_company set. It even notes search_people returns more result pages, and instructs the agent to call search_companies first if the slug is uncertain. This is strong, actionable routing guidance.
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 PostsBRead-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"). A full company URL is accepted too and is reduced to the slug. |
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 description only needs to add behavioral context beyond those. It adds the 'recent' scoping and company-feed source, but says nothing about pagination, ordering, result limits, or how recent the posts are. 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 conveys the core action and resource clearly in minimal space.
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 single-parameter, read-only tool with an output schema and strong schema parameter documentation, the description covers the essential invocation context. It lacks sibling differentiation, but that is already reflected in the usage dimension; overall, nothing critical is missing for basic correct 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?
Schema description coverage is 100%, and the schema itself explains company_name with examples and URL-slug normalization. The tool description adds no additional parameter semantics, 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?
The description states a specific verb and resource: 'Get recent posts from a company's LinkedIn feed.' It clearly identifies the tool's scope as company posts, though it does not explicitly contrast it with related post/feed tools like get_feed or search_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?
No guidance is provided on when to use this tool versus alternatives such as get_feed, search_posts, or get_company_profile. The usage context is only implied by the name and description, with no exclusions or routing hints.
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 |
|---|---|---|---|
| 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. | |
| company_name | Yes | LinkedIn company name (e.g., "docker", "anthropic", "microsoft"). A full company URL is accepted too and is reduced to the slug. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The annotations already declare readOnlyHint=true and openWorldHint=true, covering the safety and live-data aspects. The description itself adds little behavioral context beyond the schema's mention of 'scrape', and it does not disclose potential scraping limitations, authentication needs, or rate-limit behavior. No contradiction with annotations exists.
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, direct sentence with no filler or redundancy. It is concise, though it mostly restates the tool name and relies on the schema for substantive detail.
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 tool with two well-documented parameters and an output schema, the definition is mostly complete. The main missing piece is usage guidance around sibling tools, but that gap is already captured in the usage_guidelines dimension.
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 both parameters are already well documented: company_name accepts a slug or full URL, and sections specifies comma-separated extras with defaults and examples. The description adds no additional parameter meaning beyond what the schema provides.
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 ('Get') and resource ('a specific company's LinkedIn profile'), which clearly distinguishes it from sibling tools like get_person_profile or search_companies. The word 'specific' also signals that this tool targets one known company rather than returning a list.
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 explicit guidance on when to use this tool versus alternatives such as search_companies, get_company_posts, or get_company_employees. It does not mention that search_companies should be used when the exact company slug is unknown, nor does it note that get_company_posts is the tool for post-specific data.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_conversationGet ConversationA
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 |
|---|---|---|---|
| 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. | |
| thread_id | No | LinkedIn messaging thread ID | |
| linkedin_username | No | LinkedIn username of the conversation participant; a full profile URL is accepted too |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With only openWorldHint in annotations, the description carries the burden of disclosing side effects. It explicitly warns that username-based resolution click-visits matching rows and 'may mark it as read,' which is a non-obvious behavioral trait an agent must know before invoking the tool. It also explains the LinkedIn UI limitation that forces this 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?
Three sentences, zero filler. The core purpose is front-loaded, and the longer second sentence earns its length by explaining a critical behavioral caveat and its root cause. The final sentence gives a clear escape hatch. No word is wasted.
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 an output schema present, return-value documentation is already handled. The description covers all invocation paths, parameter interactions, side effects, and a rationale for the unusual behavior. An agent has everything needed to call this tool correctly, including how to avoid the side-effectful path.
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 baseline is 3. The description adds meaningful parameter context: explaining that linkedin_username triggers a UI enumeration process, that index selects among multiple threads, and that thread_id bypasses the enumeration. This goes beyond the schema's dry definitions without being redundant.
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: 'Read a specific messaging conversation.' It then details two unambiguous lookup modes, making it clear this tool targets one conversation rather than inbox-wide operations, which distinguishes it from siblings like get_inbox and search_conversations even without naming them.
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 guidance on when to use each input path: pass thread_id to avoid the expensive username enumeration, and use linkedin_username when you don't have a thread_id. It does not explicitly state when to prefer this tool over sibling tools, but the read-specific-conversation scope is strongly implied.
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 |
|---|---|---|---|
| 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. | |
| linkedin_username | Yes | LinkedIn username (e.g., "stickerdaniel", "williamhgates"). A full profile URL is accepted too and is reduced to the username. |
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 and external-data nature are already known; the description adds no contradiction. The description itself does not reveal behavioral details like scraping behavior or result shape, but the rich parameter descriptions cover pagination and sections, so this is adequate.
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 sentence with no filler words. It front-loads the core purpose and does not repeat information already in the schema or annotations.
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 schema plus output schema provide substantial detail about sections, pagination, and return values. The description could be slightly richer by mentioning that the main profile page is always included, but this is already in the sections parameter description, so 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 detailed descriptions for all three parameters: linkedin_username includes examples and URL normalization, sections lists all options and defaults, and max_scrolls explains its behavior and defaults. The main description adds no parameter-level meaning, but the baseline of 3 applies because the schema already does the heavy lifting.
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 ('Get') and resource ('a specific person's LinkedIn profile'), making the operation unambiguous. It clearly distinguishes this tool from siblings like get_my_profile (own profile) and get_company_profile (company profile) without needing to read the schema.
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 versus alternatives such as search_people, get_sidebar_profiles, or connect_with_person. The description only states what the tool does, not the conditions that make it the right choice, leaving selection to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_profile_post_urlsGet Profile Post URLsARead-only
Resolve canonical post permalinks from a profile's recent activity.
Navigates to the person's /activity/all/ page, scrolls until enough posts have loaded their SDUI/RSC payloads, and returns up to count unique post permalinks (most recent first). Each URL is in the form https://www.linkedin.com/posts/ — directly usable by comment_on_post.
| Name | Required | Description | Default |
|---|---|---|---|
| count | No | Maximum number of post URLs to return (1-20, default 5). | |
| username | Yes | LinkedIn profile slug (e.g. "paola-neira"). A full URL is accepted and reduced to the slug. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description goes well beyond the readOnlyHint and openWorldHint annotations by disclosing the actual mechanism: navigating to /activity/all/, scrolling for SDUI/RSC payloads, returning unique permalinks with most-recent-first ordering, and providing the exact URL shape. This gives the agent accurate expectations of what the tool does and how it behaves.
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 a one-sentence purpose, then gives compact, relevant mechanics: the page path, loading behavior, count semantics, ordering, and URL format. Every sentence earns its place and there is no redundant filler.
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 moderate complexity, the presence of an output schema, and the readOnly/openWorld annotations, the description covers all essential context: what page is visited, how results are gathered, the max count, ordering, URL form, and downstream usage. An agent has enough to select and invoke the tool 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?
Schema coverage is 100%, so the schema fully documents both username and count. The description reinforces count's meaning with 'up to *count* unique post permalinks' and adds ordering context, but it does not add substantive parameter-level detail beyond what the schema already provides. Baseline 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 opens with a specific action and resource: 'Resolve canonical post permalinks from a profile's recent activity.' It clearly distinguishes this from sibling tools like get_person_profile and search_posts by emphasizing canonical permalinks and direct use by comment_on_post.
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 a clear use case: when you need canonical, recent post URLs from a specific profile for commenting. It names the downstream consumer (comment_on_post) and limits scope to a profile's activity page. However, it does not explicitly state when not to use this tool or name alternatives, so it falls just short of full routing guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_saved_jobsGet Saved JobsARead-only
List job postings saved by the authenticated LinkedIn user.
Returns job_ids that can be passed to get_job_details for full info.
| Name | Required | Description | Default |
|---|---|---|---|
| max_pages | No | Maximum number of saved-jobs pages to load (1-10, default 3) |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The annotations declare readOnlyHint and openWorldHint, so the safety profile is already covered. The description adds value by clarifying that the result is limited to the authenticated user's saved job postings and that it returns job_ids, not full job details—an important behavioral distinction from get_job_details. No contradictions with 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?
Two sentences with no filler. The first sentence states the action and scope, and the second sentence provides the important downstream usage detail about feeding job_ids into get_job_details. 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?
For a simple read-only list tool with one optional parameter and an output schema, the description is complete. It identifies the authenticated-user scope, the return value, and the logical next step. The annotations cover read-only and open-world behavior, and the schema covers the only parameter. Nothing essential 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?
The input schema already fully documents max_pages with type, default, range, and description, so the description does not need to repeat it. The tool has only one optional parameter, and the schema coverage is 100%. The description adds no parameter-specific meaning, but none is needed.
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 ('List') and a clear resource ('job postings saved by the authenticated LinkedIn user'), which precisely distinguishes this tool from the sibling search_jobs and get_job_details. It also states the key output type (job_ids) and how they connect to another tool.
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 the right usage context: it is for the authenticated user's own saved jobs, not general job search. It also gives explicit guidance that the returned job_ids should be passed to get_job_details for full information, which effectively routes the agent to the correct follow-up tool. It does not explicitly list when not to use it, but the context is clear.
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; a full profile URL is accepted too (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 description adds meaningful behavioral detail beyond the readOnlyHint and openWorldHint annotations: it follows 'Show all' links to get full lists and skips sections that redirect to linkedin.com/premium. This helps the agent understand what the tool will and will not return without inventing expectations.
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 compact and front-loaded with the core purpose, followed by two sentences of genuinely useful behavior details. Every sentence earns its place, with no filler or repetition of schema 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?
For a single-parameter read-only tool with an output schema and safe annotations, the description is complete. It covers what sections are scraped, how 'Show all' behavior is handled, and which sections are skipped, so an agent has enough context to invoke it 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?
Schema description coverage is 100%, and the parameter linkedin_username is already well documented in the schema, including the accepted full-URL format. The description only restates that the parameter identifies the profile page to scrape, so it adds little semantic value beyond the schema.
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 action and resource: 'Get profile links from sidebar recommendation sections on a LinkedIn profile page.' It also names the concrete sections ('More profiles for you', 'Explore premium profiles', 'People you may know'), which clearly distinguishes it from broader sibling tools like get_person_profile or search_people.
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 when to use the tool: when sidebar recommendation profiles are needed from a LinkedIn profile page. However, it does not explicitly contrast this with alternatives or state when not to use it, so the agent is left to infer the boundary between this and other profile-related tools.
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 ConversationsC
Search messages by keyword.
| Name | Required | Description | Default |
|---|---|---|---|
| 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. | |
| keywords | Yes | Search keywords to filter conversations |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The only annotation is openWorldHint, so the description carries most of the behavioral disclosure burden. It does not mention that enumerating results may mark conversations as read (noted only in the limit parameter description), nor does it disclose result-shaping behavior. 'Search' implies a read operation, but side effects are hidden.
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, front-loaded sentence with no filler. It is easy to parse, but it is so terse that it contributes little beyond the tool name and parameter names, making it less effective than it could be.
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 two-parameter tool with an output schema and fully described parameters, the description is minimally adequate for invocation. However, it lacks usage guidance, scope clarification, and behavioral disclosures, so an agent cannot fully judge when to use it or what side effects may occur.
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 input schema already fully documents keywords and limit. The description adds no new parameter semantics beyond restating the keyword filter; it does not provide format details, examples, or constraints beyond what the schema offers.
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 action (search) and resource (messages), making the tool's core purpose clear. It is somewhat distinct from sibling search tools by focusing on messages, but it does not explicitly differentiate itself from related tools like get_inbox, get_conversation, or search_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 gives no guidance on when to use this tool versus alternatives such as get_inbox, get_conversation, or search_posts. There is no mention of scope, prerequisites, or situations where another tool should be preferred.
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 |
|---|---|---|---|
| sort_by | No | Sort results (date, relevance) | |
| job_type | No | Filter by job type, comma-separated (full_time, part_time, contract, temporary, volunteer, internship, other) | |
| 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) | |
| work_type | No | Filter by work type, comma-separated (on_site, remote, hybrid) | |
| easy_apply | No | Only show Easy Apply jobs (default false) | |
| date_posted | No | Filter by posting date (past_hour, past_24_hours, past_week, past_month) | |
| experience_level | No | Filter by experience level, comma-separated (internship, entry, associate, mid_senior, director, executive) |
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 |
|---|---|---|---|
| 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. | |
| keywords | Yes | Search keywords (e.g., "software engineer", "recruiter at Google") | |
| location | No | Optional location filter (e.g., "New York", "Remote") | |
| 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.
search_postsSearch PostsARead-only
Search LinkedIn posts/content globally by keyword (the "Posts" tab).
Use this to catch informal hiring posts ("we're hiring", "Buscamos ...", "estamos contratando", "join our team") that often appear before a formal job listing exists. This is global content search, distinct from get_feed (your own home feed) and get_company_posts (one company's page).
| Name | Required | Description | Default |
|---|---|---|---|
| keywords | Yes | Search keywords (e.g., "Buscamos Unity", "AI automation hiring") | |
| max_pages | No | Scroll depth as result "pages" of ~5 scrolls each (1-10, default 3). Content search is an infinite scroll, so this caps how far the page is scrolled rather than fetching discrete pages. | |
| date_posted | No | Optional recency filter. One of "past-24h", "past-week", "past-month"; the "past_24_hours" / "past_week" / "past_month" spellings used by search_jobs are accepted too. Omit for any time. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With readOnlyHint and openWorldHint already present, the description adds useful context: this is a global content search, not a feed or company-scoped search. It also implies results may include informal/unofficial hiring language, which is relevant behavioral context beyond the 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 compact and front-loaded: one sentence defines the action, the next gives a concrete use case, and the final sentence distinguishes it from siblings. Every sentence earns its place with no filler.
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 100% parameter schema coverage, the presence of an output schema, and annotations covering safety and openness, the description covers all essential decision factors. It tells the agent what the tool does, when to use it, what alternatives exist, and how to craft effective queries.
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 baseline is 3. The description adds value by providing richer keyword examples and clarifying that content search is global rather than scoped, supplementing the schema's parameter descriptions. It meaningfully supports keyword selection without redundancy.
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 verb and resource: 'Search LinkedIn posts/content globally by keyword.' It also explicitly differentiates itself from get_feed and get_company_posts, so an agent can immediately identify what this tool is and what it is not.
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 a concrete use case—catching informal hiring posts before formal job listings exist—and provides example keyword patterns. It also names the two sibling alternatives and explains why they are different, making the selection decision explicit.
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 |
|---|---|---|---|
| message | Yes | The message text to send | |
| 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. | |
| confirm_send | Yes | Must be True to send the message | |
| linkedin_username | Yes | LinkedIn username of the recipient; a full profile URL is accepted too |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description explicitly warns that this is a write operation when confirm_send is True, which adds useful side-effect context beyond the destructiveHint annotation. It also states the recipient must be directly messageable, giving the agent an important precondition. No contradiction with annotations exists.
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 short and front-loaded with the core purpose, followed by the key prerequisite and side-effect warning. Every sentence earns its place, and there is no redundant filler.
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 destructiveHint annotation, an output schema, and full schema coverage, the description is nearly complete. It communicates the prerequisite and the guarded write behavior, though it could briefly mention that messages are not necessarily retrievable afterward or suggest a verification path, which the schema partially does via search_conversations.
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 schema already documents all parameters well. Baseline 3 applies because the description adds no extra parameter meaning beyond what is in the schema; however, the schema descriptions themselves are informative, especially for profile_urn and confirm_send.
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 sends a message to a LinkedIn user, which distinguishes it from connection requests, comments, and profile lookups. It does not explicitly contrast with sibling tools, but the action and resource are specific enough for an agent to understand the core function.
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 key prerequisite: the recipient must be directly messageable from the profile page, and it notes that confirm_send controls whether the message is actually sent. It does not explicitly state when to use this over connect_with_person or comment_on_post, but the parameter descriptions offer some contextual guidance like using get_person_profile for the URN and search_conversations as a fallback.
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.
21 tool updates
v4.23.1- First observed
close_session - First observed
comment_on_post - First observed
connect_with_person - First observed
get_company_employees - First observed
get_company_posts - First observed
get_company_profile - First observed
get_conversation - First observed
get_feed - First observed
get_inbox - First observed
get_job_details - First observed
get_my_profile - First observed
get_person_profile - First observed
get_profile_post_urls - First observed
get_saved_jobs - First observed
get_sidebar_profiles - First observed
search_companies - First observed
search_conversations - First observed
search_jobs - First observed
search_people - First observed
search_posts - First observed
send_message
TDQS
Scored across 21 tools
Each tool targets a distinct resource and action: people, companies, jobs, messaging, and posts are clearly separated. Even the three post-related tools are explicitly differentiated by source (own feed, company page, global search), and the conversation tools distinguish list/read/search. No two tools appear to do the same thing.
All tool names use lowercase snake_case with a consistent verb-first pattern: get_*, search_*, connect_with_*, send_*, comment_on_*, and close_*. Longer noun phrases like get_sidebar_profiles and get_profile_post_urls still fit the verb_noun convention. There is no mixing of camelCase, acronyms, or inconsistent verb styles.
21 tools is on the heavy side, but the server covers a genuinely broad domain: profiles, companies, jobs, messaging, feed, posts, and session management. Each tool maps to a plausible distinct workflow, so the count feels justified rather than bloated. It is slightly above the ideal 3-15 range but not excessive for a full LinkedIn client.
The tool set covers the main LinkedIn surfaces well: people search/profile/connection, company profile/posts/employees, job search/details/saved, messaging, feed, and post commenting. Minor gaps remain, such as no ability to list the user's own connections, create a post, or like/react to content, but these are workaround enough for core recruiting/sales/researcher workflows. The included write tools (connect, message, comment) are balanced with the read tools.
Maintenance
Related MCP Connectors
Let AI tools securely access your LinkedIn network and DMs
Give AI agents the LinkedIn tools to find, qualify, engage, and follow up with prospects.
Full LinkedIn access for AI agents: leads, messaging, and campaigns with safe limits built in.
LinkedIn data for AI agents: profiles, companies, jobs, posts, search. Sales research, recruiting.
Related MCP Servers
- AlicenseAqualityAmaintenanceEnables AI assistants to interact with LinkedIn by scraping profiles, companies, job postings, and getting personalized job recommendations using authenticated browser automation.1715,550 PyPI3,543Apache 2.0
- AlicenseNot gradedqualityDmaintenanceEnables AI assistants to connect to LinkedIn, accessing profiles and companies, searching for jobs and people, managing saved jobs, updating job-search profile settings, and inspecting analytics.1Apache 2.0
- AlicenseNot gradedqualityDmaintenanceEnables AI assistants to interact with LinkedIn using the official API for profile access, company management, and job postings.Apache 2.0
- AlicenseAqualityBmaintenanceLets an AI assistant operate LinkedIn through an authenticated browser session, enabling profile management, posting, networking, messaging, job search, and automated applications.10056 npm1MIT