linkedin-agent-mcp
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-agent-mcpsearch for remote software engineer jobs"
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.
linkedin-agent-mcp
Experimental project. Use entirely at your own risk.
This is a personal engineering experiment in agent safety design (typed tools, out-of-band human confirmation, failure-honest errors). It is published to show the code, not as a product or a recommendation to automate LinkedIn.
How I have used it: only on my own account, a handful of times, with me watching: read-only checks (search, a job page, my own profile, my saved jobs) and a single save/unsave of one posting to verify the confirmation flow. I have never used it to scrape data, send messages or connection requests, apply to jobs, or run anything in bulk or unattended.
LinkedIn's terms: LinkedIn's User Agreement (§8.2) prohibits bots and other automated access, and this project drives a real browser on a real account. Running it may get your account restricted or banned.
Your responsibility: anyone who runs this code does so at their own risk and is solely responsible for complying with LinkedIn's terms and applicable law. The software is provided "as is", without warranty of any kind (see LICENSE), and the author accepts no liability for how it is used.
A personal, human-in-the-loop LinkedIn job-search agent, exposed as an MCP server so an MCP client such as Claude Code can search jobs, read postings and your profile, track what you have seen, and prepare actions, while every consequential step on LinkedIn requires your explicit confirmation, given out-of-band in your own terminal.
It is deliberately not a mass-automation or scraping system: one browser page at a time, small capped result sets, spaced navigation, an hourly cap on external actions, and no evasion techniques of any kind.
At a glance
What it is: a TypeScript (Node 24) MCP server with 20 typed tools that let an AI agent search jobs, read postings and your profile, track what you have seen, and prepare actions on your own LinkedIn account.
Ports and adapters: the MCP layer is thin (zod schemas + error mapping); services depend on a
LinkedInClientinterface and repository interfaces, so Playwright lives in exactly two files and SQLite sits behind swappable repositories.READ / PREPARE / EXECUTE, enforced structurally: each tool group is handed only the ports it may use, so a READ or PREPARE tool cannot reach the executor, not by prompt instructions but by construction.
Out-of-band confirmation: there is no
confirmtool. Every external action needs a human to runnpm run confirmin a real terminal and type a code; the payload is SHA-256 bound to that approval, single-use (database compare-and-swap), and time-limited.Failure-honest: ambiguous writes become
OUTCOME_UNKNOWNand are never auto-retried; LinkedIn markup changes surface as a typedLINKEDIN_CHANGED, never as made-up data; the audit log is append-only via database triggers.Tested: 199 vitest tests (parsers on captured/modelled fixtures, state machines, the whole confirmation workflow including tampering and races, and a spawned stdio server through the real MCP SDK client), plus an opt-in read-only E2E suite run against a real account.
Documented decisions: 13 ADRs in docs/decisions/.
Status (2026-09-23): read-only flows (auth, search, job detail, own profile, saved jobs) are verified live on a real signed-in account, and so is the one registered write, Save/Unsave, run once end to end through the terminal confirmation with the owner present. Easy Apply, messaging, connections and profile writes are not implemented yet. Details in Verification status and CLAUDE.md.
Read this first: ToS and status. LinkedIn's User Agreement (§8.2) prohibits bots and other automated access. There is no official LinkedIn API for job search, saved jobs or applications for job seekers (see LinkedIn API limitations). This project drives a real browser on your own account. That is a risk to your account that only you can accept, and the browser will not start until you have (
npm run login). Also see Verification status: parts are verified live, parts are only unit-tested.
Contents
Architecture · Why MCP · Stack · READ / PREPARE / EXECUTE · Security model · Install · Authentication · Configuration · Claude Code · Tools · Confirmation · Application workflow · Persistence · Testing · Verification status · Limitations · Roadmap · Responsible use · Decision log
Related MCP server: linkedin-mcp-server
Architecture
flowchart TB
CC["Claude Code / MCP client"] -->|"stdio JSON-RPC"| MCP["MCP server (thin: schemas, kind labels, error mapping)"]
subgraph tools["Tool groups get only the ports they may use"]
R["READ tools"]
P["PREPARE tools"]
E["EXECUTE tools (actionId only)"]
end
MCP --> R & P & E
R --> JS["JobService / ProfileService"]
P --> JS
P --> AP["ActionPreparer"]
E --> AX["ActionExecutor"]
AX --> AS["ActionService<br/>state machine, hash check, CAS, audit"]
AP --> AS
JS --> LC["LinkedInClient (interface)"]
AS -->|"executors"| LC
LC --> BC["Browser client (Playwright)"]
LC -.->|"future: official API where one exists"| API["LinkedIn API"]
BC --> BM["BrowserManager<br/>persistent profile, 1 page, throttled, linkedin.com only"]
BM --> LI[("linkedin.com")]
JS & AS & AP --> REPO["Repository interfaces"] --> DB[("SQLite<br/>jobs · job_views · applications · application_answers<br/>profile_changes · messages · actions · audit_events")]
HUMAN(["You, in a terminal"]) -->|"npm run confirm (TTY + typed code)"| CF["ConfirmationService"] --> ASRules the layout enforces:
The MCP layer is thin. Tools validate input (zod), call a service, and map errors. No business logic, no SQL, no Playwright.
Services depend on interfaces.
LinkedInClienthides how LinkedIn is reached; repositories hide where state lives.Only two files import Playwright:
src/browser/browser-manager.ts(site-agnostic) andsrc/linkedin/browser-client.ts(LinkedIn-specific). All DOM knowledge is insrc/linkedin/selectors.ts; parsing is pure functions insrc/linkedin/parsers/.The confirmation port is not reachable from MCP. See Confirmation workflow.
src/
index.ts server.ts container.ts config.ts logger.ts consent.ts state-machine.ts
browser/ browser-manager.ts persistent context, serialization, throttling, allowlist
linkedin/ linkedin-client.ts (interface) browser-client.ts session.ts url.ts selectors.ts types.ts
parsers/ (search, job, profile, dom)
jobs/ profile/ messaging/ applications/ actions/ services + state machines
persistence/ database.ts migrations.ts repositories/
tools/ define.ts schemas.ts views.ts read/ prepare/ execute/
cli/ login.ts confirm.ts
errors/ errors.ts
docs/decisions/ 13 ADRs + index + template scripts/decisions.mjs
tests/ fixtures/Why MCP
MCP gives an agent typed, discoverable tools over a standard protocol, so the same server works with Claude Code today and other clients later. It also gives a natural place to put the safety model: the tool surface (what exists, what each accepts) is part of the security boundary. There is no confirm tool, and execute tools accept only an actionId.
Why this technology stack
Layer | Choice | Why (details in the ADR) |
Language/runtime | TypeScript on Node 24, run directly via native type stripping | MCP SDK and Playwright are TypeScript-first; no loader dependency (ADR-0001) |
MCP |
| Current stable line; local transport; no network surface (ADR-0003) |
Validation | zod 4 (input and output schemas) | Types vanish at runtime; SDK validates both directions |
Browser | Playwright, persistent profile, system Chrome/Edge | Manual login, no credentials handled (ADR-0006) |
Parsing | cheerio + pure functions | Fixture-testable without a browser (ADR-0008) |
Persistence |
| No native addon; replaceable by Postgres (ADR-0005) |
Tests | vitest + MCP SDK client (in-memory and real stdio) | (ADR-0011) |
Four runtime dependencies in total. Logging and state machines are hand-rolled on purpose.
READ / PREPARE / EXECUTE
Kind | May do | May not do | Enforced by |
READ | Read LinkedIn; write local tracking (jobs seen, views) | Change LinkedIn; create actions | READ tools are handed only read ports ( |
PREPARE | Write local state: drafts, | Perform any external action | PREPARE tools are handed |
EXECUTE | Change LinkedIn, for one confirmed action | Accept a payload from the agent; run unconfirmed/expired/altered/repeated actions |
|
This is enforced structurally (interfaces handed to each group, state machine, database), not by prompt instructions. The prompt-level guidance in the server instructions is only a courtesy on top.
Security model
Threat model: an LLM (possibly manipulated by hostile text in a job posting) acting only through MCP tools and non-interactive shell, on a machine you control. Out of scope: malware or a person with full access to your account/machine (they could edit the SQLite file or read your browser profile).
Concern | Control |
Agent performs an external action unprompted | Execute tools need a |
Agent changes what gets executed after approval | Payload is immutable; its SHA-256 is bound to the confirmation and re-verified at execution (tested by tampering with the DB row) |
Replay / double execution | Confirmation is single-use: |
Stale approval | Confirmation expires (default 10 min); pending actions expire (60 min) |
Ambiguous write outcome (browser dies mid-submit) |
|
Runaway agent | Hourly cap on executed actions; 25-result cap; single serialized page; spaced navigation |
Credential exposure | Passwords, MFA and cookies are never handled by this program: login is manual in a browser window; logs redact sensitive keys; no tool returns them; cookie values are never read (only presence of |
Browser used elsewhere | Host allowlist: only |
Prompt-injected data | Tool inputs are strictly validated; free text is only ever stored/previewed, never executed; LinkedIn URLs are parsed and canonicalised |
Error leakage | Unknown errors collapse to |
Tampering with history |
|
Local secrets | Browser profile, DB and consent live under |
Defence in depth: .claude/settings.json adds Claude Code ask rules for the execute tools.
Installation
Requirements: Node.js ≥ 24, and Google Chrome or Microsoft Edge installed (or npx playwright install chromium and BROWSER_CHANNEL=chromium).
git clone <this repo> linkedin-agent-mcp && cd linkedin-agent-mcp
npm install
npm run build
npm testAuthentication
npm run loginFirst run only: shows the ToS/automation notice; you must type
I UNDERSTAND. (Refuses to run without an interactive terminal.)Opens a visible Chrome/Edge window on a dedicated profile (
~/.linkedin-agent-mcp/browser-profile).You sign in: password, MFA, and any CAPTCHA (the program never automates or bypasses these).
It detects the session (presence of the
li_atcookie and a non-wall page), double-checks by loading the feed, and closes the browser so the profile is saved.
Re-run when the session expires (LOGIN_REQUIRED). Check state any time with the linkedin_auth_status tool.
Configuration
Typed and validated at startup (src/config.ts); see .env.example. No LinkedIn credentials exist in configuration.
Variable | Default | Meaning |
|
| Root for all local state |
|
| Browser profile |
|
| SQLite file |
|
| ( |
|
|
|
|
|
|
|
| Hard cap is 25 |
|
| Spacing between page loads |
|
| Confirmation validity |
|
| Cap on executed external actions |
|
| Save HTML on |
npm run confirmreads the same variables. If you overrideLINKEDIN_AGENT_HOME/DATABASE_PATHfor the MCP server, set them in your shell too, or the CLI will look at a different database.
Claude Code integration
Syntax verified against the current Claude Code docs (claude mcp add [options] <name> -- <command> [args...]; options go before the name; -- is mandatory).
Option A: command line (use absolute paths; build first with npm run build):
# Windows example
claude mcp add --scope user linkedin-agent -- node C:\Users\you\code\linkedin-agent-mcp\dist\index.js
# macOS/Linux
claude mcp add --scope user linkedin-agent -- node /home/you/linkedin-agent-mcp/dist/index.js--scope local (default) = this project only, private; --scope project = writes .mcp.json; --scope user = all your projects. node is invoked directly, so the cmd /c wrapper (needed for npx on native Windows) is not required.
Option B: .mcp.json: a ready-made one is in this repo (works when Claude Code is opened in this folder; the first time it asks you to approve the project server):
{
"mcpServers": {
"linkedin-agent": {
"command": "node",
"args": ["dist/index.js"]
}
}
}The path is relative on purpose: stdio servers start in the project folder, and ${CLAUDE_PROJECT_DIR} is not expanded in .mcp.json (Node received the literal text and exited, which showed up as CONNECTION_CLOSED; the real error is in %LOCALAPPDATA%\claude-cli-nodejs\Cache\<project>\mcp-logs-linkedin-agent). Run npm run build first, and restart the session (or /mcp → Reconnect) after changing this file.
For use from other projects, put an absolute path in args, or use Option A with --scope user. Verify with claude mcp list, or /mcp inside a session. To debug interactively: npm run inspect (MCP Inspector).
Recommended permissions (.claude/settings.json is included). Keep the execute tools on ask, never allow:
{ "permissions": { "ask": ["mcp__linkedin-agent__linkedin_save_job", "mcp__linkedin-agent__linkedin_unsave_job"] } }Available tools
Every description is prefixed with its kind. Full schemas are visible via tools/list.
READ
Tool | Purpose |
| Is the browser profile signed in? (one page load) |
|
|
| Full posting by URL; unknown fields are omitted, never guessed |
| LinkedIn's own Saved-jobs list (My items → Job tracker → Saved), |
| Your own profile; |
| Company "About" page |
| Local tracking only (no LinkedIn access) |
| State + exact preview of prepared actions |
| Local state of an in-progress application: status, CV, fields, every question with its answer/source/ |
| Exact human-readable payload (job / CV / fields / questions) to review before any confirmation; |
PREPARE (local state only)
Tool | Purpose |
| Local status: |
|
|
| Current vs proposed headline/about (reads current value from LinkedIn) |
| Stores a draft; nothing is sent |
| Stores a request preview; nothing is sent |
| Records the user's own answer to one application question that could not be established automatically; never fabricated |
| Cancels a pending/confirmed action |
EXECUTE (requires a user-confirmed action)
Tool | Purpose |
| Take only |
Planned, not registered so the agent never meets a dead end: submit_application, update_profile, send_message, send_connection_request, prepare_application. See ADR-0009.
Example
// 1. agent → linkedin_search_jobs
{ "keywords": "NestJS", "location": "Tel Aviv, Israel", "workType": "hybrid", "maxResults": 5, "onlyNew": true }
// ← { "jobs": [{ "id": "3812345678", "title": "...", "company": "...", "isNew": true, "localStatus": "discovered", ... }],
// "fetched": 5, "newCount": 3, "filteredOut": 2 }
// 2. agent → linkedin_prepare_save_job { "url": "https://www.linkedin.com/jobs/view/3812345678/" }
// ← { "action": { "actionId": "26751bd3-…", "status": "PENDING", "preview": "SAVE \"…\" at … on LinkedIn" },
// "executionAvailable": true, "nextStep": "Show the preview… run `npm run confirm -- 26751bd3`…" }
// 3. YOU, in a terminal: npm run confirm -- 26751bd3 (shows the preview, asks you to type a code)
// 4. agent → linkedin_save_job { "actionId": "26751bd3-…" } ← COMPLETED (single use)Confirmation workflow
prepare tool ─► PENDING ──(you: npm run confirm, TTY + code)──► CONFIRMED ──► EXECUTING ──► COMPLETED
│ │ ▲ │ │ ├─► FAILED (proven: nothing happened)
│ │ └── payload hash fixed here │ │ └─► OUTCOME_UNKNOWN (ambiguous: never auto-retry)
│ └─► CANCELLED / EXPIRED └─► CANCELLED / EXPIRED (10 min)
└─ returns actionId + exact previewWhy out-of-band? The agent calls the tools, so any in-band
confirmed:truecan be forged. See ADR-0004.npm run confirm(list) ·npm run confirm -- <id>(review + confirm) ·-- --cancel <id>·-- --audit <id>·-- --resolve <id> completed|failed(after anOUTCOME_UNKNOWN, once you have checked LinkedIn)."Apply to good jobs", "go ahead with all of them", or any vague instruction is not a confirmation: each action needs its own confirmation of its exact payload.
Application workflow
Target flow: search → inspect → analyze → prepare_application → (needs_user_input | ready_to_submit) → your review → your confirmation → submit_application.
Implemented today: the guarded core: ApplicationService and the application state machine (DISCOVERED → PREPARING → NEEDS_USER_INPUT ⇄ READY_TO_SUBMIT → CONFIRMED → SUBMITTING → SUBMITTED, plus FAILED, SUBMISSION_UNCERTAIN, EXTERNAL, ABANDONED), with these rules enforced and tested: answers are never fabricated (unknown ⇒ needs_input); READY_TO_SUBMIT is impossible with open questions; external applications are detected and reported, never auto-submitted; SUBMITTED is terminal; an uncertain submission can only be resolved, never retried; the preview format (Job / Company / Position / CV / fields / questions / final action) is built by buildPreview. The user-input-collection + preview half of the tool surface is registered (linkedin_get_application, linkedin_provide_application_answer, linkedin_preview_application, 2026-09-22) — but since nothing yet creates an application with real questions (that is prepare_application, next), these tools have no reachable data on the real account until then; they are unit- and protocol-tested against directly-seeded application state.
Not implemented: the Easy Apply form driver, prepare_application and submit_application. A real signed-in session now exists, but no Easy Apply form has been captured yet; the driver is built from a real, read-only capture of one (see Roadmap), starting with prepare_application, never with submit. They are not exposed as tools.
Persistence
SQLite at ~/.linkedin-agent-mcp/agent.db (WAL). Tables: jobs (unique canonical_key = li:<jobId> for dedupe, first_seen_at, last_seen_at, seen_count, status), job_views, applications, application_answers, profile_changes, messages, actions, audit_events (append-only). Job statuses: discovered, viewed, considering, saved, rejected, preparing, ready_to_submit, submitted, failed, changed only through a transition table. Migrations are forward-only (src/persistence/migrations.ts).
Testing
npm test # unit + protocol tests, no network
npm run typecheck
LINKEDIN_E2E=true npx vitest run tests/e2e # OPT-IN, real account, read-only, see file headerPowerShell: $env:LINKEDIN_E2E="true"; npx vitest run tests/e2e
Profile lock. The E2E suite and the MCP server share one persistent browser profile, and Chrome allows one process per profile. If the MCP server has used the browser recently (any browser-backed tool call, including
linkedin_auth_status), the E2E run fails immediately with "Could not start the chrome browser" until the server closes its idle browser (BROWSER_IDLE_CLOSE_MS, default 5 min). Run E2E with the MCP server disconnected, or wait.
Covers: input validation through the real MCP SDK client; job parsing (synthetic and real captured fixtures); job-ID extraction and canonicalisation; deduplication (in the database and in the service); application, action and job state machines (including forbidden transitions such as SUBMITTED → PREPARING); the full confirmation workflow (unconfirmed, expired, wrong type, tampered payload, single-use, race, hourly limit, non-TTY, wrong code); failure classification (FAILED vs OUTCOME_UNKNOWN) and audit trail; error mapping and leak checks; logger redaction; the consent gate; repository behaviour incl. append-only triggers; and a real spawned stdio server proving stdout carries only JSON-RPC.
Verification status
Honest accounting of what has and has not been exercised.
Area | Status |
Build, typecheck, unit + protocol + spawned-stdio tests | ✅ Run, passing |
Real Chrome launch with persistent profile; consent gate; session probe | ✅ Verified live (signed out) |
Job search parsing on real LinkedIn markup, with location filter and | ✅ Verified live, signed-out/guest view (React, Java queries) |
Job detail parsing | ✅ Verified against a captured real signed-out page (fixture) |
Dedupe across separate runs; invalid URL; 404 → | ✅ Verified live / with real captured wall |
NestJS search | ⚠️ Signed out: LinkedIn returned HTTP 999 (throttling); handling of the 999 itself is verified. ✅ Signed in: verified live alongside React and Java (2026-09-20) |
Logged-in login, session probe, search, job detail, profile (name, headline, location, About, Experience, Education, Skills incl. the | ✅ Verified live on a real account, 2026-09-20 ( |
Signed-in fixtures | ⚠️ Modelled on the real markup (structure, hashed classes, |
Save/Unsave click (prepare → | ✅ Verified live once, owner present, on a throwaway job, 2026-09-23; the role-based button lookup worked unchanged |
Grouped multi-role experience entries, company page, Easy Apply | ❌ Not verified on a real account (the test profile has no grouped roles) |
Saved-jobs list ( | ✅ Probed live (read-only) 2026-09-22; fixture is modelled, not a capture (real content deleted). |
| ✅ Run by the owner in a real terminal during the Save/Unsave check (2026-09-23). Its logic ( |
Easy Apply, submit, messaging, connection requests, profile writes | ⛔ Not implemented |
Limitations
Selectors will break when LinkedIn changes markup. Failure mode is a typed
LINKEDIN_CHANGED, never fake data;DEBUG_SNAPSHOTS=truesaves the page for a quick fix.Signed-out pages are throttled by LinkedIn within a few requests (HTTP 999); the intended mode is signed in.
The signed-in layout has no
<h1>, no stable class names and no<li>in profile lists. It is read through<title>, headings,data-testidandcomponentkeyanchors (ADR-0013); expect these to churn too.linkedin_get_profileis slow by design (main page, lazy-load scrolling, up to three "show all" pages, spaced navigation): tens of seconds. Make sure the MCP client's tool timeout allows it.The E2E suite and the MCP server cannot use the browser profile at the same time (see Testing).
In signed-out views an "Apply" button is ambiguous, so
easyApply/applyTypeare reported as unknown.One browser page at a time; a search returns at most 25 results by design.
node:sqliteis still flagged experimental by Node.The confirmation model does not protect against something with full shell/database access to your machine.
LinkedIn API limitations
Officially available to any developer: Sign in with LinkedIn (OpenID Connect) and Share on LinkedIn. Job-related APIs (Talent Solutions: Job Posting, Apply Connect, Recruiter System Connect, …) are partner-gated and built for employers/ATS vendors. There is no official API for a job seeker to search jobs, read saved jobs, apply, message, or read their own full profile. LinkedInClient is the seam to swap in an official API if that changes.
Browser automation limitations
Violates LinkedIn ToS §8.2 on its face (see top); subject to bot detection (HTTP 999, CAPTCHA, verification checkpoints: all surfaced as typed errors and never bypassed); DOM churn; sessions expire; needs a real browser installed.
Roadmap
Done: signed-in read-only flows verified live and parsers fixed (2026-09-20, ADR-0013); Chrome-exit-at-launch now classified as a profile lock, not "could not start Chrome" (2026-09-22); get_saved_jobs implemented, unit-tested against a modelled fixture, registered, and verified live by tests/e2e (2026-09-22); user-input-collection + application-preview tools (linkedin_get_application, linkedin_provide_application_answer, linkedin_preview_application) implemented, registered, unit- and protocol-tested (2026-09-22) — built ahead of prepare_application, so not yet reachable on the real account; Save/Unsave verified live end to end with the owner present (2026-09-23).
Next, in order (each gated on live verification, read-only first; the same list with working notes is in CLAUDE.md):
prepare_application: open a real Easy Apply form read-only, detect fields/questions, fill only what the profile/CV/config establishes, stop atneeds_user_input, detect external apply. This is where the agentic logic starts, and the first thing that will actually feed data intolinkedin_get_application/linkedin_provide_application_answer/linkedin_preview_application. Do not start withsubmit_application.submit_applicationwith idempotency checks (inspect LinkedIn's "Applied" state before any retry).Only then
update_profile,send_message,send_connection_request.
Later: richer job tracking (notes, follow-ups); optional MCP elicitation as a second confirmation channel; PostgreSQL repositories if ever multi-user.
Responsible use
This is for your own account and a small number of deliberate actions. Do not use it to mass-apply, mass-message, mass-connect or harvest data. Do not point it at other people's accounts. Do not add stealth, CAPTCHA solving or rate-limit evasion: when LinkedIn says no (999, checkpoint, CAPTCHA), stop. You are responsible for compliance with LinkedIn's terms and applicable law, and for the accuracy of anything submitted in your name. Never let an agent fabricate answers on applications; the design makes it ask you.
Decision log
Every significant choice, with options considered and an interview soundbite: docs/decisions/ · docs/INTERVIEW.md · npm run decisions.
License
MIT. The licence covers this code only; using it against LinkedIn is still subject to their User Agreement (see Responsible use).
Available Tools
20 toolslinkedin_auth_statusLinkedIn sign-in statusARead-onlyIdempotent
[READ - no LinkedIn state is changed] Checks whether the persistent browser profile is signed in to LinkedIn. Use it to diagnose NOT_AUTHENTICATED / LOGIN_REQUIRED errors. Never returns cookies or tokens. If not signed in, the USER must run npm run login; the agent cannot log in.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| reason | No | |
| authenticated | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, destructiveHint=false and idempotentHint=true, so safety is covered. The description adds genuinely useful, non-redundant context: it returns no cookies or tokens, and the agent has no capability to log in itself. It omits how the check is performed or whether results are cached, which is why this sits at 4 rather than 5.
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?
Four short sentences, front-loaded with the read-only constraint, then purpose, then the security boundary, then remediation. Every sentence carries distinct information and none is padded.
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?
An output schema exists, so return values need not be described; the description nonetheless clarifies the negative case ('not signed in') and the agent-capability boundary. For a zero-parameter diagnostic tool with annotations covering safety, this is complete.
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 takes zero parameters, so per the rubric the baseline is 4; there is nothing for the description to clarify beyond the schema, and it correctly does not invent parameter guidance.
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+resource: checks whether the persistent browser profile is signed in to LinkedIn. No sibling tool addresses authentication state, so it is trivially distinguishable, and the leading '[READ - no LinkedIn state is changed]' frames the operation type immediately.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly says when to use it ('diagnose NOT_AUTHENTICATED / LOGIN_REQUIRED errors') and what to do when it reports a negative result ('the USER must run `npm run login`'). The when-to-use condition and the follow-up path are both stated, leaving nothing to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
linkedin_cancel_actionCancel a prepared actionA
[PREPARE - changes local state only; performs NO external action] Cancels a PENDING or CONFIRMED action so it can never be executed (local state only). Use when the user changes their mind or the draft is superseded.
| Name | Required | Description | Default |
|---|---|---|---|
| actionId | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| type | Yes | |
| error | No | |
| status | Yes | |
| preview | Yes | Exactly what will happen if the user confirms and the action is executed. |
| actionId | Yes | |
| createdAt | Yes | |
| expiresAt | Yes | |
| completedAt | No | |
| confirmedAt | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare openWorldHint=false and destructiveHint=false, and the description reinforces this by stressing 'local state only; performs NO external action' twice. It also adds state-machine detail (PENDING/CONFIRMED) that the annotations do not convey. No contradiction, and it contributes beyond the structured fields.
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?
Front-loads the critical PREPARE/local-only caveat in brackets, then the operation and its usage in two short sentences. Every clause earns its place with no redundancy.
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 values need no explanation, and the description covers purpose, effect, and usage. It is nearly complete for a one-parameter tool; the only gap is the unexplained actionId semantics.
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?
There is a single parameter (actionId) with 0% schema description coverage, so the description must compensate and largely does not. It only implies actionId refers to an action via 'PENDING or CONFIRMED action'; it never explains ID format, source, or the 8-64 char constraint.
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 (cancels) and a precisely scoped resource (a PENDING or CONFIRMED action), including the consequence ('can never be executed'). This clearly separates it from siblings like linkedin_get_action and linkedin_list_actions, which read rather than cancel.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly gives the triggering context: 'Use when the user changes their mind or the draft is superseded.' That is clear when-to-use guidance, though it names no explicit alternative tool for related operations.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
linkedin_draft_messageDraft a messageA
[PREPARE - changes local state only; performs NO external action] Stores a DRAFT LinkedIn message (max 2000 chars) to a recipient for the user to review. Nothing is sent. Draft only when the user asked for a message to a specific person; never because a recruiter was merely found. This version cannot send messages: show the draft so the user can send it manually.
| Name | Required | Description | Default |
|---|---|---|---|
| message | Yes | ||
| recipientUrl | Yes | Recipient's profile URL, e.g. https://www.linkedin.com/in/jane-doe/. Must be an https://www.linkedin.com URL. | |
| recipientName | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| action | Yes | |
| message | Yes | |
| nextStep | Yes | |
| recipient | Yes | |
| recipientUrl | Yes | |
| executionAvailable | Yes | False when this version cannot execute the action; the user must do it manually. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only declare generic hints (readOnlyHint=false, destructiveHint=false, openWorldHint=false); the description adds the substantive behavior: changes local state only, nothing is transmitted, and the tool cannot send at all. It also states the 2000-char cap and that the draft must be surfaced to the user. No contradiction with the annotations, since writing a local draft is consistent with readOnlyHint=false.
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?
Front-loaded with the bracketed safety tag, then purpose, then usage rules, then workflow instruction. Every clause earns its place; 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?
With an output schema present, return values need no explanation; the description covers the safety profile, recipient/length constraints, and the user-review workflow an agent needs to invoke this correctly. No material gap remains for a 3-param local-state tool.
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 33% (only recipientUrl documented in the schema). The description restates the 2000-char message limit but adds no meaning for recipientName or recipientUrl beyond the schema, so it only partially compensates for the coverage gap. Baseline 3 for a low-coverage 3-param tool.
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+resource ('Stores a DRAFT LinkedIn message') with an explicit [PREPARE] tag and the crucial scope limit 'performs NO external action; Nothing is sent'. It clearly distinguishes itself from send/action siblings and from the found-recruiter case.
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?
Gives an explicit when-to-use condition ('only when the user asked for a message to a specific person') and an explicit when-not ('never because a recruiter was merely found'), plus the follow-up workflow ('show the draft so the user can send it manually'). Nothing 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.
linkedin_get_actionGet a prepared actionARead-onlyIdempotent
[READ - no LinkedIn state is changed] Returns the state and exact preview of a prepared action (local state only). Use it to check whether the user has confirmed an action, or after an OUTCOME_UNKNOWN error.
| Name | Required | Description | Default |
|---|---|---|---|
| actionId | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| type | Yes | |
| error | No | |
| status | Yes | |
| preview | Yes | Exactly what will happen if the user confirms and the action is executed. |
| actionId | Yes | |
| createdAt | Yes | |
| expiresAt | Yes | |
| completedAt | No | |
| confirmedAt | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint and destructiveHint=false, so the safety profile is covered. The description still adds value beyond them: it clarifies that only local state is involved and no LinkedIn state is changed, and it names the OUTCOME_UNKNOWN scenario as a reason to call it.
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 short clauses, zero filler, with the read-only constraint and local-state scope front-loaded before the usage triggers. 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?
An output schema exists, so return values need not be described, and the description still usefully frames what comes back (state and exact preview). For a one-parameter read tool with full annotations, this is nearly complete; only the actionId sourcing is left implicit.
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?
Single parameter with 0% schema description coverage, so the description carries the burden. It implies actionId refers to a previously 'prepared action', but never explains the id format, length constraints, or where to obtain it (e.g., from linkedin_list_actions output). Minimal viable semantics rather than compensating for the gap.
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 and resource ('Returns the state and exact preview of a prepared action') and pins the scope as local state only, which separates it from state-changing siblings like linkedin_save_job or linkedin_prepare_save_job. It does not explicitly name the nearby sibling it could be confused with (linkedin_list_actions), so it falls just short of a 5.
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?
Gives two concrete trigger conditions: checking whether a user has confirmed an action, and recovering after an OUTCOME_UNKNOWN error. This is clear when-to-use guidance, but there are no explicit when-not conditions or named alternatives.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
linkedin_get_applicationGet an applicationARead-onlyIdempotent
[READ - no LinkedIn state is changed] Returns the local state of an in-progress application: status, CV, fields and every question with its answer/source (profile, config, user, or proposed) and whether it still needsInput. Local state only, never touches LinkedIn.
| Name | Required | Description | Default |
|---|---|---|---|
| applicationId | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| jobId | Yes | |
| fields | Yes | |
| status | Yes | |
| answers | Yes | |
| cvLabel | Yes | |
| applicationId | Yes | |
| unansweredCount | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint, non-destructive and closed-world. The description adds real value: it clarifies that only local state is read and LinkedIn is never contacted (a stronger claim than readOnlyHint alone) and describes the shape of the returned state including answer provenance and needsInput flags.
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?
Front-loaded with the [READ] safety marker, then two dense sentences that each carry useful detail with no filler or repetition.
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 tool with an output schema and full annotation coverage, the description is essentially complete; the only minor gap is any hint about the applicationId format/lookup, which the schema also leaves blank.
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 0% for the single applicationId parameter, so the schema does not document it. The name is largely self-explanatory and the description says nothing further about it, which is adequate but not compensating for the coverage gap.
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 (Returns/Get) and resource (local state of an in-progress application), then enumerates the returned content: status, CV, fields, questions with answer/source and needsInput. The phrase 'local state only, never touches LinkedIn' implicitly distinguishes it from siblings like linkedin_preview_application, though no sibling is named explicitly.
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?
Usage is implied by 'in-progress application', which tells the agent the applicable context, but there is no explicit when-to-use/when-not guidance and no routing to alternatives such as linkedin_preview_application or linkedin_provide_application_answer.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
linkedin_get_companyGet a LinkedIn companyARead-onlyIdempotent
[READ - no LinkedIn state is changed] Reads a company's public LinkedIn 'About' page (description, industry, size, headquarters, website).
| Name | Required | Description | Default |
|---|---|---|---|
| url | Yes | Company page URL, e.g. https://www.linkedin.com/company/acme/. Must be an https://www.linkedin.com URL. |
Output Schema
| Name | Required | Description |
|---|---|---|
| name | Yes | |
| website | No | |
| industry | No | |
| location | No | |
| description | No | |
| linkedinUrl | Yes | |
| employeeCount | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint, openWorldHint and destructiveHint=false, so the '[READ - no LinkedIn state is changed]' prefix largely restates structured data. The one genuinely additive fact is that only the public 'About' section is retrieved, which hints at limited result scope, but nothing about failure modes or rate limits is given.
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, front-loaded with the read-only guarantee, then the resource and its payload. No filler or redundancy.
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 not required, and the description still lists the expected fields. For a one-parameter read tool this is essentially complete, with only usage routing left implicit.
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 single url parameter is fully documented in the schema (format, https://www.linkedin.com requirement, example). The description adds no syntax or format detail beyond it, so the baseline 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?
Specific verb (reads) plus resource (company's public LinkedIn 'About' page) and an explicit enumeration of the returned fields. It is clearly distinguishable from siblings like linkedin_get_profile or linkedin_get_job.
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 context by labeling the tool READ-only, but it gives no when-to-use condition, no prerequisite, and no distinction from siblings that also fetch LinkedIn entities. Usage is implied rather than stated.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
linkedin_get_jobGet a LinkedIn jobARead-onlyIdempotent
[READ - no LinkedIn state is changed] Opens one LinkedIn job posting and returns its full description and metadata, plus local tracking info. Fields LinkedIn does not show are omitted (never guessed). Marks the job as "viewed" locally. Use it before analysing a job against the user's CV.
| Name | Required | Description | Default |
|---|---|---|---|
| url | Yes | Job posting URL, e.g. https://www.linkedin.com/jobs/view/3812345678/. Must be an https://www.linkedin.com URL. |
Output Schema
| Name | Required | Description |
|---|---|---|
| id | No | |
| url | Yes | |
| title | Yes | |
| posted | No | |
| salary | No | |
| skills | No | |
| company | Yes | |
| location | No | |
| tracking | Yes | |
| workType | No | |
| applyType | No | |
| easyApply | No | |
| seniority | No | |
| description | Yes | |
| employmentType | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly/ idempotent/ non-destructive, so the safety profile is covered. The description adds non-obvious behavior beyond that: a local side effect ('Marks the job as "viewed" locally') and a data-fidelity policy ('Fields LinkedIn does not show are omitted (never guessed)'). Auth requirements and failure modes are not covered, keeping it at 4 rather than 5.
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?
Four short sentences, front-loaded with the '[READ - no LinkedIn state is changed]' tag so the safety signal lands first. Every sentence adds distinct information (state safety, return content, field policy, local side effect, usage timing) 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?
For a single-parameter read tool with full annotations and an output schema, the description covers what an agent needs: what it fetches, that remote state is untouched, that a local viewed-flag is set, how missing fields are handled, and when to call it. Return-value detail is rightly left to the output schema.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
There is a single required parameter with 100% schema description coverage, including format and URL constraints, so the schema carries the meaning. The description adds nothing about the url parameter itself, which is the expected baseline-3 outcome when coverage is complete.
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+resource ('Opens one LinkedIn job posting and returns its full description and metadata') and scopes it to a single posting, which cleanly separates it from linkedin_search_jobs and linkedin_get_saved_jobs. What is returned is stated concretely, so an agent can tell what this tool does without opening 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?
It gives a clear condition of use: 'Use it before analysing a job against the user's CV.' That is a real usage cue, but it does not name competing alternatives (e.g. search_jobs vs get_saved_jobs) or state when not to use it, so it falls short of the explicit when/when-not routing a 5 requires.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
linkedin_get_profileGet my LinkedIn profileARead-onlyIdempotent
[READ - no LinkedIn state is changed] Reads the signed-in user's OWN profile (headline, about, experience, education, skills). incompleteSections lists anything that could not be read completely; treat those parts as unknown, not empty.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| name | No | |
| about | No | |
| skills | No | |
| headline | No | |
| location | No | |
| education | No | |
| experience | No | |
| incompleteSections | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint, and destructiveHint=false, so the '[READ - no LinkedIn state is changed]' line is partly redundant. However, the incompleteSections caveat is genuine added value: it warns the agent that partial results must be treated as unknown rather than empty, which is not derivable from 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?
Two tight sentences with no filler; the safety marker and scope come first, followed by the returned fields and the caveat. Every clause 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?
With an output schema present, the description need not explain return structure, and it correctly avoids that. Scope, safety, and the partial-read caveat cover everything an agent needs to call and interpret this no-parameter read tool.
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 takes zero parameters, and the extended schemas correctly apply the baseline of 4 when there is nothing to document. The description's field list describes output rather than input, so it neither helps nor hurts here.
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 (Reads) and a precisely scoped resource (the signed-in user's OWN profile), then enumerates the fields returned (headline, about, experience, education, skills). This clearly separates it from siblings like linkedin_get_company (other entities) and linkedin_prepare_profile_update (writes).
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 OWN-profile scoping and the READ marker give clear context for when this tool applies, and the sibling set makes read-vs-write intent obvious. It stops short of naming an explicit alternative or a when-not condition, so it does not reach a 5.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
linkedin_get_saved_jobsGet my LinkedIn saved jobsARead-onlyIdempotent
[READ - no LinkedIn state is changed] Reads LinkedIn's own Saved-jobs list (My items -> Job tracker -> Saved), i.e. jobs actually saved on the real account right now, not just local tracking. Each result is recorded in local tracking (localStatus becomes "saved" unless it was already further along, e.g. submitted). hasMore=true means there are more saved jobs than maxResults returned; call again with a larger maxResults to see them.
| Name | Required | Description | Default |
|---|---|---|---|
| maxResults | No | 1-25. Default is small on purpose; this is not a scraper. |
Output Schema
| Name | Required | Description |
|---|---|---|
| jobs | Yes | |
| fetched | Yes | |
| hasMore | Yes | True when LinkedIn has more saved jobs beyond this page (increase maxResults to see more). |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the readOnlyHint annotation, the description discloses a non-obvious side effect: every returned saved job is written into local tracking (localStatus becomes 'saved' unless already further along). It also explains the hasMore pagination contract, which is behavioral information the annotations do not cover.
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?
Front-loaded with a bracketed [READ] marker and a single dense paragraph; every sentence carries information (scope, side effect, pagination). The sentences are long and pack several ideas together, which slightly hurts skimability but there is 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?
Scope, the local-tracking side effect, and the pagination loop are all covered, and the presence of an output schema means return values need no further explanation. An agent has everything required to call this 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% and the schema already documents maxResults' 1-25 range and default, so the baseline is 3. The description adds relational meaning by tying maxResults to hasMore and the retry pattern, which the schema alone does not convey.
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 and resource ('Reads LinkedIn's own Saved-jobs list') and explicitly scopes it ('jobs actually saved on the real account right now, not just local tracking'), which separates it from the local-tracking siblings. An agent can identify the tool's job without opening 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?
It gives clear context for use: read the real saved list, and if hasMore=true call again with a larger maxResults. It implicitly contrasts with local tracking but never names the alternative sibling (e.g. linkedin_list_tracked_jobs) as an explicit routing choice, so it falls short of the 5 bar.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
linkedin_list_actionsList prepared actionsARead-onlyIdempotent
[READ - no LinkedIn state is changed] Lists recent prepared actions and their states (local state only), optionally filtered by status.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | ||
| status | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| actions | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly/idempotent/non-destructive, so the safety profile is covered and the '[READ - no LinkedIn state is changed]' prefix largely repeats it. The genuinely additive detail is '(local state only)', which tells the agent these are staged local records rather than live LinkedIn data — a meaningful behavioral clarification.
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 front-loaded sentence with the read-only constraint first and the filter optionality last; every clause adds information and nothing 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 values need not be described, and the description covers scope, safety, and filtering adequately. It could note ordering or the default page size implied by 'recent', but nothing critical to correct invocation 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 0% for 2 parameters, and the description only restates the status filter without explaining its enum values or the meaning/default of 'limit' (max 50). Baseline 3 is warranted since the enum carries the filter values but limit stays fully undocumented in prose.
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 and resource ('Lists recent prepared actions and their states') and scopes it as local-only, which distinguishes it from the single-record linkedin_get_action. It never names a sibling explicitly, so differentiation still relies on the reader inferring from the plural 'actions'.
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?
'Optionally filtered by status' implies when to narrow results, but there is no explicit when-to-use framing or routing against alternatives such as linkedin_get_action for a single action. Usage must be inferred.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
linkedin_list_tracked_jobsList locally tracked jobsARead-onlyIdempotent
[READ - no LinkedIn state is changed] Lists jobs from LOCAL tracking only (no LinkedIn access), optionally by local status (discovered, viewed, considering, saved, rejected, ...). Use it to avoid re-presenting jobs the user already handled.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | ||
| status | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| jobs | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover readOnly, idempotent, non-destructive, and closed-world hints. The description adds that no LinkedIn state is changed and that it operates on LOCAL tracking only, reinforcing that it doesn't touch the remote platform. It does not mention pagination or ordering, but the output schema likely covers return shape, so the gap is minor.
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, front-loaded with the READ qualifier and local-only scope, then the usage guidance. No wasted words; every phrase adds value.
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 an output schema and annotations covering safety, the description provides enough context: local-only scope, status filtering, and usage intent. It could mention pagination or default behavior for limit, but those are minor given the output schema exists.
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 0%, so the schema itself provides no help. The description partially compensates by explaining the 'status' parameter meaning (local status values like discovered, viewed, etc.) and implying 'limit' via 'optionally by local status'. But it doesn't explain 'limit' or clarify that status is a filter. Baseline 3 is appropriate given partial compensation.
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 and resource ('Lists jobs from LOCAL tracking'). It clarifies the local-only scope, which distinguishes it from sibling tools like linkedin_search_jobs or linkedin_get_saved_jobs. However, it doesn't explicitly name which sibling to use for LinkedIn-side job retrieval.
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?
Provides clear context: 'Use it to avoid re-presenting jobs the user already handled.' This gives a concrete when-to-use scenario. But it doesn't contrast with alternatives like linkedin_get_saved_jobs or explain when NOT to use it (e.g., to fetch remote jobs).
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
linkedin_mark_jobMark a job locallyA
[PREPARE - changes local state only; performs NO external action] Sets the LOCAL tracking status of a job the agent already knows (viewed, considering or rejected). Does not touch LinkedIn. Use "rejected" only when the USER said they are not interested, so it is not shown again.
| Name | Required | Description | Default |
|---|---|---|---|
| url | Yes | Job URL. Must be an https://www.linkedin.com URL. | |
| status | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| id | No | |
| title | Yes | |
| status | Yes | |
| company | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations declare readOnlyHint=false and destructiveHint=false, but do not explain WHAT is mutated. The description supplies the critical missing context: this is a local-state-only write with no external side effect, plus a durable consequence ('so it is not shown again'). That is meaningful disclosure beyond the annotations, not a restatement.
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?
Front-loads the most decision-relevant fact (local only, no external action) in a bracketed prefix, then states the resource and the one risky enum rule. No filler sentences; every clause carries routing or behavioral 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?
Return values need not be explained since an output schema exists, and the local-vs-remote distinction is fully covered. Minor gaps remain: no mention of overwrite/idempotency behavior when re-marking the same job, and no note on what happens to the url parameter's sibling linkage.
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 50% (url documented, status enum undocumented). The description compensates by explaining the enum semantics ('viewed, considering or rejected') and adding a decision rule for the non-obvious value 'rejected', which the schema's bare enum cannot convey.
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 and resource ('Sets the LOCAL tracking status of a job') and immediately scopes it as a local-only write that performs NO external action. This differentiates it from the mutating siblings linkedin_save_job and linkedin_unsave_job, which an agent would otherwise confuse it with.
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?
Gives explicit when-to-use guidance, including a strong conditional rule: use "rejected" only when the USER said they are not interested. It also clarifies the negative scope (does not touch LinkedIn), though it does not name a specific alternative tool to use for external actions.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
linkedin_prepare_connection_requestPrepare a connection requestA
[PREPARE - changes local state only; performs NO external action] Prepares (does NOT send) a LinkedIn connection request with an optional note (max 300 chars). Only for a specific person the user named. Never bulk-prepare. This version cannot send connection requests: show the preview so the user can do it manually.
| Name | Required | Description | Default |
|---|---|---|---|
| note | No | ||
| recipientUrl | Yes | Recipient's profile URL. Must be an https://www.linkedin.com URL. | |
| recipientName | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| action | Yes | |
| nextStep | Yes | |
| executionAvailable | Yes | False when this version cannot execute the action; the user must do it manually. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The annotations only tell the agent it is a non-destructive mutation; the description adds the crucial facts that it changes local state only, performs no external action, and cannot actually send. That 'no external action' guarantee is a behavioral trait the annotations do not convey and materially affects how the agent should present the result.
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?
Front-loaded bracketed tag followed by dense but purposeful sentences; nothing is filler. Slightly heavy punctuation, but appropriately sized for a state-changing preparation step.
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?
An output schema exists so return values need no explanation, and the safety profile is covered by annotations. Combined with the explicit no-send/no-bulk constraints, the description gives the agent everything needed to call and frame this 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 low (33%), and the description only restates the note's 300-char limit (already in the schema) and implies a named recipient. It does not explain recipientUrl, its LinkedIn-URL requirement, or how the agent should source those values, leaving the schema to carry most parameter meaning.
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 (prepares, not sends) and resource (a LinkedIn connection request with optional note), and immediately distinguishes itself from a send action and from bulk preparation. An agent would not confuse it with any sibling.
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 conditions are given: 'Only for a specific person the user named' and 'Never bulk-prepare,' plus the instruction to show the preview so the user acts manually. It doesn't name a specific sibling alternative, but the constraint set is clear.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
linkedin_prepare_profile_updatePrepare a profile changeA
[PREPARE - changes local state only; performs NO external action] Prepares (does NOT apply) a change to the user's own LinkedIn headline (max 220 chars) or about section (max 2600 chars). Reads the current value from LinkedIn and returns current vs proposed as a PENDING action. Never propose changes the user did not ask for. This version cannot apply profile changes: show the preview so the user can apply it manually.
| Name | Required | Description | Default |
|---|---|---|---|
| field | Yes | ||
| proposedValue | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| field | Yes | |
| action | Yes | |
| nextStep | Yes | |
| currentValue | Yes | |
| proposedValue | Yes | |
| executionAvailable | Yes | False when this version cannot execute the action; the user must do it manually. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Discloses beyond the annotations that it mutates only local state, performs no external action, reads the current LinkedIn value, and returns a PENDING action rather than applying it. This resolves the tension around readOnlyHint=false (it does create a local pending action) rather than contradicting it.
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?
Front-loaded bracketed scope marker plus tight sentences, but the no-apply constraint is restated twice ('does NOT apply' and 'cannot apply profile changes'), which is mild redundancy.
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-shape detail is unnecessary, yet the description still usefully says it returns current vs proposed as a pending action. For a two-field local-state tool, nothing an agent needs to call it correctly 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 0%, so the description must compensate, and it does: it names the two valid fields and gives field-specific limits (220 for headline, 2600 for about) that the schema only captures as a generic maxLength of 2600. It does not elaborate on proposedValue formatting, so it stops short of full compensation.
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 (prepares) and resource (user's own LinkedIn headline or about section), and explicitly demarcates the PREPARE-only scope versus applying. An agent can distinguish it from apply-capable siblings without opening any 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?
Explicitly states when to use it (to produce a preview) and when it must not be treated as final, routing the user to manual application. It also sets the boundary 'never propose changes the user did not ask for', leaving no inference needed.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
linkedin_prepare_save_jobPrepare saving/unsaving a jobA
[PREPARE - changes local state only; performs NO external action] Prepares (does NOT perform) saving or un-saving a job on LinkedIn. Returns a PENDING action with a preview. Recommending a job does not require saving it; only prepare this when the user asked to save it. To execute: the user confirms in their terminal, then call linkedin_save_job / linkedin_unsave_job.
| Name | Required | Description | Default |
|---|---|---|---|
| url | Yes | Job URL. Must be an https://www.linkedin.com URL. | |
| mode | No | save |
Output Schema
| Name | Required | Description |
|---|---|---|
| action | Yes | |
| nextStep | Yes | |
| executionAvailable | Yes | False when this version cannot execute the action; the user must do it manually. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the annotations, the description discloses the key behavioral distinction: it 'changes local state only; performs NO external action' and returns 'a PENDING action with a preview.' It also explains the confirmation workflow required before execution, which is crucial for a non-idempotent, non-destructive prepare tool.
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 bracketed PREPARE warning, then states purpose, behavior, usage constraint, and execution path. Every sentence adds necessary information; none is redundant.
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, the description needn't explain return values fully, yet it still notes the PENDING action and preview. With annotations and schema present, the description covers the essential local-state-only behavior, confirmation workflow, and sibling execution tools, leaving no critical gaps.
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 50%: the url parameter is documented in the schema, but the mode enum lacks a description. The description's wording 'saving or un-saving' loosely maps to the save/unsave modes, but it adds no syntax, default behavior, or constraint details 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 verb and resource: 'Prepares (does NOT perform) saving or un-saving a job on LinkedIn.' It immediately distinguishes itself from the actual execution tools by naming linkedin_save_job and linkedin_unsave_job, so an agent can tell it 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?
It gives explicit when-to-use guidance ('only prepare this when the user asked to save it'), a when-not condition ('Recommending a job does not require saving it'), and the exact execution path ('the user confirms in their terminal, then call linkedin_save_job / linkedin_unsave_job'). Alternatives and sequencing are both covered.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
linkedin_preview_applicationPreview an application before submissionARead-onlyIdempotent
[READ - no LinkedIn state is changed] Renders the exact human-readable payload for an application (job, CV, fields, every question and its answer) so the user can review it before any confirmation. readyToSubmit is false while any question is unanswered; use linkedin_provide_application_answer for those first. Local state only, never touches LinkedIn.
| Name | Required | Description | Default |
|---|---|---|---|
| applicationId | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| status | Yes | |
| preview | Yes | Exact human-readable payload the user should review before any confirmation. |
| unanswered | Yes | Questions still needing the user's answer, if any. |
| readyToSubmit | Yes | True once every question has an established or user-supplied answer. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint, and destructiveHint=false, so the safety profile is covered. The description still adds real behavioral context beyond them: it discloses that only local state is touched, that no LinkedIn state changes, and the readyToSubmit gating semantics. It does not describe output shape, but an output schema exists for that.
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, front-loaded with a bracketed [READ] tag and the core action. There is mild redundancy — '[no LinkedIn state is changed]' and 'never touches LinkedIn' convey the same fact — but nothing is wasted verbatim.
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?
An output schema exists, so return-value explanation is not required. The description covers purpose, timing, prerequisites, and state guarantees; the only gap is any hint about how to obtain a valid applicationId, which is minor for a low-complexity read tool.
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 0% and the single applicationId parameter has no schema description, so the description must compensate. It only implies the parameter identifies the application to preview; it gives no guidance on the ID's origin, format, or the min/max length constraints. This is the minimum-viable baseline for a one-parameter tool.
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 and resource ('Renders the exact human-readable payload for an application') and enumerates what the preview contains (job, CV, fields, every question and answer). It clearly distinguishes itself from the sibling linkedin_provide_application_answer, which it names directly.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly states the moment to use it ('so the user can review it before any confirmation') and routes the agent to the alternative when a prerequisite is unmet ('readyToSubmit is false while any question is unanswered; use linkedin_provide_application_answer for those first'). When/when-not and the alternative are all present.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
linkedin_provide_application_answerAnswer an application questionA
[PREPARE - changes local state only; performs NO external action] Records the USER's own answer to one application question that could not be established from their profile/CV/config. Local state only, nothing is sent to LinkedIn. Never invent the answer yourself: only call this with an answer the user actually gave. Once every question is answered the application becomes READY_TO_SUBMIT; check with linkedin_preview_application.
| Name | Required | Description | Default |
|---|---|---|---|
| answer | Yes | ||
| answerId | Yes | ||
| applicationId | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| jobId | Yes | |
| fields | Yes | |
| status | Yes | |
| answers | Yes | |
| cvLabel | Yes | |
| applicationId | Yes | |
| unansweredCount | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations flag readOnlyHint=false and openWorldHint=false; the description confirms and enriches this by declaring the operation is local-state-only and that nothing is sent to LinkedIn, plus it discloses the downstream state machine effect (application becomes READY_TO_SUBMIT once all questions are answered). That is behavioral context beyond what the annotations convey.
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?
Front-loaded with the scope tag and operation, then constraints, then the state-transition and cross-reference. Every clause carries information; 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?
Output schema exists so return values need no explanation, and the safety/scope profile is fully covered. The only gap is that the agent isn't told how to obtain valid applicationId/answerId values, which is a real if minor omission for a 3-required-param tool.
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 0% and no parameter descriptions exist, so the description must compensate; it clarifies the intent of 'answer' (must be a real user-provided answer, not generated) but gives no guidance on where applicationId or answerId come from, nor the 2000/64-char limits. Partial compensation only.
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 action ('records the USER's own answer to one application question') plus a bracketed scope tag [PREPARE - changes local state only; performs NO external action] that separates it from the sibling prepare/save tools. It also names the state transition target (READY_TO_SUBMIT) so the agent knows what this call accomplishes.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly scopes when to use it ('one application question that could not be established from their profile/CV/config'), states a hard prohibition ('Never invent the answer yourself: only call this with an answer the user actually gave'), and routes to the verification sibling ('check with linkedin_preview_application').
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
linkedin_save_jobSave a job on LinkedInA
[EXECUTE - external action; REQUIRES the user to have confirmed this exact action] Saves a job on LinkedIn (changes LinkedIn state; reversible with linkedin_unsave_job). Requires an actionId from linkedin_prepare_save_job that the USER has confirmed in their own terminal. Fails with CONFIRMATION_REQUIRED otherwise; do not retry in a loop, ask the user. Never save jobs just because they look good.
| Name | Required | Description | Default |
|---|---|---|---|
| actionId | Yes | The actionId returned by the matching prepare tool, after the user confirmed it. |
Output Schema
| Name | Required | Description |
|---|---|---|
| data | No | |
| action | Yes | |
| summary | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only tell the agent this is a non-read-only, non-idempotent, non-destructive write. The description adds the crucial operational context they lack: user confirmation is mandatory, the specific CONFIRMATION_REQUIRED error, and that retrying is harmful because the call is not idempotent. That is genuine value beyond the structured fields.
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?
Front-loaded with the EXECUTE warning and a dense but single-paragraph body where each clause earns its place (precondition, failure, no-retry, reversal). The closing 'Never save jobs just because they look good' is slightly editorial but still functional guidance.
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 an output schema exists, the description needn't document returns, and it covers everything else an agent needs: prerequisite actionId, required user confirmation, error behavior, and reversibility. No meaningful gap remains for invoking this 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% for the single actionId parameter, and the description largely restates the schema's own wording ('after the user confirmed it'). Baseline 3 applies since the schema already carries the semantics; the description adds only the terminal-confirmation nuance.
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 and resource ('Saves a job on LinkedIn') with an execution tag up front. It clearly distinguishes itself from the sibling prepare/unsave tools by naming both, so an agent can route without opening any 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?
Explicitly states the precondition (an actionId from linkedin_prepare_save_job that the USER confirmed in their own terminal), the failure path (CONFIRMATION_REQUIRED), and the anti-pattern (do not retry in a loop, ask the user). It names the reversal tool linkedin_unsave_job and even gives a policy guardrail against gratuitous saves.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
linkedin_search_jobsSearch LinkedIn jobsARead-onlyIdempotent
[READ - no LinkedIn state is changed] Searches LinkedIn jobs and returns at most 25 results (one page). Every result is recorded in local tracking and annotated with isNew / localStatus, so you can tell jobs the user has never seen from ones already seen, saved, rejected or applied to. Set onlyNew=true to return only never-before-seen jobs. Use LinkedIn job IDs (the id field) to refer to jobs. Do not call repeatedly in a loop.
| Name | Required | Description | Default |
|---|---|---|---|
| onlyNew | No | Return only jobs this agent has never returned before (local dedupe). | |
| keywords | Yes | Search keywords, e.g. "React TypeScript". | |
| location | No | City/region/country, e.g. "Tel Aviv, Israel". | |
| workType | No | ||
| easyApply | No | Only jobs with LinkedIn Easy Apply. | |
| maxResults | No | 1-25. Default is small on purpose; this is not a scraper. | |
| postedWithin | No | ||
| employmentType | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| jobs | Yes | |
| fetched | Yes | Unique jobs LinkedIn returned before local filtering. |
| newCount | Yes | |
| filteredOut | Yes | Jobs hidden because onlyNew=true and they were seen before. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover safety (readOnlyHint, destructiveHint=false) and openWorld/idempotent traits, but the description adds real value: the 25-result cap, the local tracking side effect, and the isNew/localStatus annotation of results. It does not explain the interaction between idempotentHint and the tracking-based dedupe, which is a minor gap.
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?
Front-loads the '[READ - no LinkedIn state is changed]' tag, then packs scope, return annotations, the onlyNew switch, ID convention, and the loop warning into three tight sentences 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?
With an output schema present, return values need not be described, yet the definition still explains the isNew/localStatus annotations the agent will see. For an 8-parameter search tool at 63% schema coverage it is largely complete, with only the unmentioned filter parameters left to the schema.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 63%, so the schema documents most parameters already. The description adds meaningful semantics for onlyNew and reinforces the maxResults cap and job-ID convention, but says nothing about location, workType, easyApply, postedWithin, or employmentType 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?
States a precise verb+resource ('Searches LinkedIn jobs'), quantifies the scope ('at most 25 results (one page)'), and clarifies the read-only posture. An agent can distinguish it from linkedin_list_tracked_jobs and linkedin_get_job without opening their 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?
Gives clear operating guidance: set onlyNew=true for unseen jobs, use the `id` field to refer to jobs, and 'Do not call repeatedly in a loop.' It lacks an explicit named alternative (e.g. list_tracked_jobs for re-checking already-seen jobs), which keeps it short of a 5.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
linkedin_unsave_jobUnsave a job on LinkedInA
[EXECUTE - external action; REQUIRES the user to have confirmed this exact action] Removes a job from the saved list on LinkedIn (changes LinkedIn state). Requires an actionId from linkedin_prepare_save_job (mode "unsave") that the USER has confirmed in their own terminal.
| Name | Required | Description | Default |
|---|---|---|---|
| actionId | Yes | The actionId returned by the matching prepare tool, after the user confirmed it. |
Output Schema
| Name | Required | Description |
|---|---|---|
| data | No | |
| action | Yes | |
| summary | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=false, openWorldHint=true, and destructiveHint=false; the description adds the crucial external-side-effect context ('changes LinkedIn state'), the terminal-confirmation requirement, and the execute-stage guard. This goes well beyond what the structured annotations convey.
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?
Front-loaded with the execute marker and the core action, and stays to two sentences. Slight redundancy between 'REQUIRES the user to have confirmed this exact action' and 'that the USER has confirmed in their own terminal' costs it the top mark.
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 execution tool with an output schema, everything an agent needs is present: the state-changing nature, the confirmation gate, and the source of the required actionId. No return-value explanation is needed since an output schema exists.
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 actionId definition is already documented. The description adds provenance meaning (must come from the matching prepare tool, post-confirmation), which is useful but largely mirrors the schema text.
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 and resource: 'Removes a job from the saved list on LinkedIn'. This clearly distinguishes it from linkedin_save_job and linkedin_prepare_save_job, and the '[EXECUTE]' framing signals the execution stage versus the prepare stage.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly names the prerequisite chain: the actionId must come from linkedin_prepare_save_job (mode "unsave") and must have been confirmed by the user in their own terminal. An agent knows both which sibling to call first and the gate condition for invoking this one.
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.
20 tool updates
v0.1.0- First observed
linkedin_auth_status - First observed
linkedin_cancel_action - First observed
linkedin_draft_message - First observed
linkedin_get_action - First observed
linkedin_get_application - First observed
linkedin_get_company - First observed
linkedin_get_job - First observed
linkedin_get_profile - First observed
linkedin_get_saved_jobs - First observed
linkedin_list_actions - First observed
linkedin_list_tracked_jobs - First observed
linkedin_mark_job - First observed
linkedin_prepare_connection_request - First observed
linkedin_prepare_profile_update - First observed
linkedin_prepare_save_job - First observed
linkedin_preview_application - First observed
linkedin_provide_application_answer - First observed
linkedin_save_job - First observed
linkedin_search_jobs - First observed
linkedin_unsave_job
TDQS
Scored across 20 tools
Each tool targets a distinct resource and action, with clear READ/PREPARE/EXECUTE labels and local-vs-LinkedIn boundaries. Related pairs like get_action/list_actions and get_application/preview_application are differentiated by singular-vs-list and structured-state-vs-rendered-preview purposes.
All tools use the same linkedin_ prefix and snake_case convention, mostly following a verb_noun pattern. Minor variations like linkedin_auth_status remain readable and do not break the overall consistency.
At 20 tools, the set is on the heavy side and falls into the borderline 16-25 range. While many tools earn their place through the prepare/execute safety model, some pairs could potentially be consolidated.
Core job search, tracking, job detail, and application preparation are covered. However, execute tools are missing for application submission, message sending, connection request sending, and profile update application, creating notable dead ends for end-to-end workflows.
Maintenance
Related MCP Connectors
- mcpOAuthcom.curviate
LinkedIn actions for AI agents: search, messaging, posts and invites, as hosted MCP tools.
Hosted LinkedIn MCP server that connects your own LinkedIn account to Claude, ChatGPT, Cursor, n8n and any MCP client. 33 tools: people and Sales Navigator search, profiles, companies, jobs, inbox, posts, invites, email and phone finding. Free 7-day trial, no card (connecting your own LinkedIn account needs a paid plan). Starter $19/month, Pro $49/month, Max $199/month. $10 top-ups.
Managed LinkedIn MCP server for AI agents: search, connect, message and enrich on accounts you own.
Research saved LinkedIn contacts, review monitored public activity, and prepare engagement campaigns. Four product/setup tools work anonymously. Company tools require OAuth or a scoped API key: explicitly sign in and refresh tools. Paid actions require a credit budget. MCP cannot post comments or start extension delivery. Setup and examples: https://opencomment.ai/mcp
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceA Model Context Protocol (MCP) server that provides tools to interact with LinkedIn's Feeds and Job API. You can do "search for 3 data engineer jobs in . For each job check if it a good match for me by analyzing it against my resume in file resume.md."207-
- AlicenseNot gradedqualityDmaintenanceEnables interaction with LinkedIn tools and services through the Universal MCP framework, allowing operations like posting and profile management via natural language.1MIT
- AlicenseNot gradedqualityBmaintenanceEnables natural language interaction with LinkedIn, including profile retrieval, job searching, messaging, and post engagement.3Apache 2.0
- AlicenseAqualityDmaintenanceEnables searching and scraping of LinkedIn profiles, companies, jobs, and posts using natural language through MCP-compatible AI clients.13MIT