Skip to main content
Glama

linkedin-mcp

실제 Chromium 브라우저에서 로그인된 여러분의 LinkedIn 세션을 직접 구동하고, 모든 쓰기 작업 앞에 명시적인 확인 단계를 두는 Model Context Protocol 서버입니다.


⚠️ 먼저 읽어주세요

이것은 일반적인 API 클라이언트가 아닙니다. 설치하기 전에 아래 내용을 모두 읽으십시오.

  • 실제 로그인된 브라우저 세션을 사용자를 대신해 구동합니다. 서버는 Chromium을 실행하고 저장된 LinkedIn 쿠키를 복원하며, 사람이 클릭하는 것과 같은 버튼을 클릭합니다.

  • 공식 LinkedIn API를 사용하지 않습니다. 이 뒤에는 OAuth 앱도, 파트너 계약도, 지원되는 통합도 없습니다. 공개 웹사이트에 대한 브라우저 자동화입니다.

  • 이렇게 LinkedIn을 자동화하는 것은 LinkedIn 사용자 약관(LinkedIn User Agreement) 밖의 행위입니다. LinkedIn의 약관은 사이트 스크래핑과 자동 접근을 금지합니다.

  • 계정이 속도 제한, 이용 제한, 또는 영구 정지를 당할 수 있습니다. 그 위험은 가상적인 것이 아니라 실제입니다. LinkedIn은 자동화를 탐지하며, 제재는 경고 없이, 그리고 항소의 여지 없이 수행될 수 있습니다.

  • 세션 쿠키는 이 기계에 보관됩니다. 로그인에 성공하면 Playwright storageState 파일이 로컬 디스크에 기록됩니다. 그 파일을 읽을 수 있는 사람은 누구나 LinkedIn에서 여러분을 대신해 행동할 수 있습니다.

  • 사용은 전적으로 계정 소유자의 책임입니다. 여기에는 명시적이든 묵시적이든 보증이 없으며, 정지된 계정에 대한 구제도 없습니다.

위 내용에서 비롯된 세 가지 설계상의 결정 사항은 설정으로 변경할 수 없습니다.

  1. 단일 계정, 단일 사용자. 서버에는 하나의 저장된 세션을 담는 상태 디렉터리가 하나만 있습니다. 멀티 계정 지원도, "사용자"라는 개념도 없습니다. 키보드 앞의 사람을 위한 로컬 도구입니다.

  2. 모든 쓰기 작업은 명시적 확인을 요구합니다. 게시, 연결 요청, 메시지 전송, 채용 지원은 모두 두 번에 호출하는 핸드셰이크입니다. 단 한 번의 도구 호출로는 LinkedIn에 아무것도 전송될 수 없습니다. 자세한 내용은 다음 섹션을 참조하세요.

  3. 서버는 결코 CAPTCHA를 해결하지 않습니다. 비밀번호를 입력하지 않고, 2FA 요청을 해제하지도 않으며, 보안 확인 구간을 우회하지 않습니다. LinkedIn이 태잤을 때 서버는 멈추고 일반 브라우저에서 직접 처리하도록 알려줍니다.

이 중 하나라도 계정에 부적절하다면 설치하지 마십시오.


Related MCP server: remote-mcp-linkedin

확인 후 실행(confirm-before-execute) 작동 방식

모든 쓰기 도구(linkedin_create_post, linkedin_send_connection_request, linkedin_send_message, linkedin_apply_to_job)는 두 번 호출 핸드셰이크 입니다.

호출 1 — 미리보기. 도구를 실제 인수와 함께 그리고 confirm 없이 호출하세요. 서버는 페이지를 검사하고 어떤 일이 정확히 일어날지 식별한 다음 미리보기 결과를 반환합니다. 아무것도 전송되지 않하며 일일 할당량도 소모되지 않습니다.

{
  "profileUrl": "https://www.linkedin.com/in/dana-whitfield-example/",
  "note": "Hi Dana — we both worked on the OpenTelemetry collector. Would like to connect."
}

호출 2 —확인. 동일한 호출에 confirm: true로 다시 실행합니다. 선택적으로 발급받은 previewToken을 함께 전달할 수 있습니다. 전달하면 서버는 미리보기 이후 인수가 변경되지 않았는지 확인하며, 변경된 경우 confirmation_mismatch 오류와 함께 실패합니다.

{
  "profileUrl": "https://www.linkedin.com/in/dana-whitfield-example/",
  "note": "Hi Dana — we both worked on the OpenTelemetry collector. Would like to connect.",
  "previewToken": "9f2c41ab77e05d13",
  "confirm": true
}

미리보기 결과는 다음과 같이 생겼습니다(실제 linkedin_send_connection_request 미리보기):

{
  "status": "preview",
  "action": "linkedin_send_connection_request",
  "executed": false,
  "confirmationRequired": true,
  "summary": {
    "profileUrl": "https://www.linkedin.com/in/dana-whitfield-example/",
    "name": "Dana Whitfield",
    "headline": "Staff Engineer, Observability",
    "note": "Hi Dana — we both worked on the OpenTelemetry collector. Would like to connect.",
    "noteLength": 88,
    "notePreview": "Hi Dana — we both worked on the OpenTelemetry collector. Would like to connect.",
    "connectionDegree": 2,
    "connectionDegreeLabel": "2nd",
    "connectPathway": "direct",
    "alreadyPending": false,
    "wouldSucceed": true
  },
  "previewToken": "9f2c41ab77e05d13",
  "quota": {
    "action": "connectionRequests",
    "used": 3,
    "cap": 20,
    "remaining": 17,
    "resetsAt": "2026-08-25T07:00:00.000Z",
    "allowed": true
  },
  "warnings": [],
  "howToConfirm": "Nothing has been sent to LinkedIn yet. To execute, re-issue the exact same `linkedin_send_connection_request` call with `confirm: true` (optionally echoing `previewToken: \"9f2c41ab77e05d13\"` so the arguments are verified as unchanged)."
}

미리보기를 읽는 방법:

  • 미리보기에는 항상 executed: false와 confirmationRequired: true가 포함됩니다. 실행된 결과에는 대신 "status": "executed", "executed": true, 그리고 result 객체가 포함됩니다.

  • warnings에는 서버가 문제가 있다고 판단하는 내용—잘린 게시글, 지나친 연결 강의 노트, 소진된 할당량, dry-run 모드 등—이 담겨있습니다.

  • 미리보기에서 호출이 성공할 수 없다고 뭐(이미 연결됨, 이미 초대 처리 중임, Connect는 없음, 1-stage 연결 상태가 아님 등)라고 알리면 howToConfirm이 해석을 알려주고, 확정을 시도해도 할당량을 소모하지 않고 invalid_input로 거절됩니다.

  • previewToken은 작업(describe) 이름과 정규화된 페이로드의 부호에 대한 짧은 다이제스트입니다. 일치 확인용 보호 세서이며, 보안 토큰이 아닙니다.

요구 사항

  • Node.js 20 이상 ("engines": { "node": ">=20" }).

  • 보이는 브라우저 창을 열 수 있는 데스크톱 환경 — 대화형 로그인에는 이 기능이 필요합니다.

  • Playwright의 Chromium 빌드(아래에서 설치).

설정

의존성을 설치합니다:

npm install

Playwright가 구동하는 Chromium 빌드를 다운로드합니다:

npx playwright install chromium

환경 파일을 생성합니다(파일 안의 모든 변수는 선택 사항이고 자격 증명을 포함하지 않습니다):

cp .env.example .env

구성 파일을 생성합니다(일일 상한과 타임아웃만 포함):

cp config.example.json config.json

TypeScript를 dist/로 컴파일합니다:

npm run build

첫 실행, 순서대로

여기 있는 어떤 것도 체한 마지막 단계까지 LinkedIn 계정에 접하지 않습니다.

  1. npm install

  2. npx playwright install chromium

  3. cp .env.example .env 머시 cp config.example.json config.json — 둘 다 선택적이고 자격 증명을 포함하지 않습니다.

  4. npm run build

  5. npm test — 브라우저도 네트워크도 사용하지 않는 힐미적(hermetic) 단위 테스트 75개

  6. npm run verify:dry — 지역 fixtures로 11개 도구 모두를 구동해, 계정에 연결하기 전에 배선이 제대로 동작하는지 확인합니다 (자세히)

  7. “MCP 클라이언트에 args에 --dry-run을 포함해서 먼저 서버를 등록합니다 (자세히). 클라이언트가 11개 도구를 켜고 쓰기 도구가 미리보기를 반환하는지 확인하세요.

  8. args에서 --dry-run을 제거하고 클라이언트를 다시 시작한 다음 linkedin_login을 호출합니다 (자세히). 이것이 linkedin.com에 도달하는 첫 단계입니다. 실재하는 Chromium 창이 열리고 여러분이 직접 로그인합니다.

  9. linkedin_session_status를 호출하여 저장된 세션이 동작하는지 확인합니다.

8단계가 경계입니다. 그 이전의 모든 단계는 디렉터리를 삭제하면 원상태로 되돌릴 수 있습니다.

로그인

이 프로젝트에는 어디에도 자격 증명 설정이 없습니다. 그것은 의도적인 설계입니다. 여러분은 한 번, 실제 브라우저 창에서 직접 로그인합니다.

  1. linkedin_login 도구를 호출합니다. 인수를 가지고하지 않습니다.

  2. LinkedIn 로그인 페이지에 실제로 보이는 Chromium 창이 열립니다. headless: true로 설정해도 이때에는 보이는 창이 나타납니다. 대화형 로그인은 항상 창(head) 브라우저를 강제합니다.

  3. 이메일과 비밀번호는 직접 입력하고, LinkedIn이 요청하는 다음 단계 — SMS 또는 인증기 코드, 이메일 PIN, 기기 확인, CAPTCHA — 도 직접 해결합니다. 서버는 자격 증명을 입력하지 않니다, 비밀번호 필드를 읽지 않으며, 보안 확인 위젯을 건드리지 않습니다. 그저 2초마다 폴링하면서 LinkedIn 내비게이션에 로그인된 사용자 요소가 나타났는지 기다릴 뿐입니다.

  4. 천천히 하세요. 기다리는 시간은 loginTimeoutMs로 제한되어 있으며 기본값은 300000입니다(5분). 더 필요하면 config.json에서 높이세요.

  5. LinkedIn에 로그인된 피드가 나타나면, 브라우저 세션은 상태 디렉터리 안의 storageState.json 에 저장됩니다. 파일 권한은 **0600**입니다(소유자만 읽기/쓰기). 이 파일은 gitignore 처리되어 있습니다.

  6. 이후 모든 도구 호출은 저장된 세션을 재사용합니다. 앞으로 몇 주 동안은 다시 로그인할 필요가 없어야 합니다.

언제든 linkedin_session_status (인자 없음) 으 세션을 확인하십시오. 피드릴 한 번 로드하여 다음을 보고합니다:

{
  "valid": true,
  "lastVerified": "2026-08-24T18:42:10.114Z",
  "sessionSavedAt": "2026-08-11T09:03:55.002Z"
}

valid: false일 때는 reason이 함께 포함됩니다(예: LinkedIn이 피드를 로그인 페이지로 리디렉션한 경우). 이 도구또는 어떠한 도구도 쿠키나 세션 내용을 반환하지 않습니다.

세션은 만료됩니다. 그런 때에는 도구 들이 session_expired오류를 표시하며 수정 방법은 언제나 같습니다. linkedin_login을 다시 실행하고 직접 로그인하면 됩니다.

속도 제한 및 페이싱

서버는 자체 일상 상한을 적용하고 브라우저 작업 전에 지정된 범위의 무작위 지연을 넣습니다. 이것은 계정을 보호하는 가장 중요한 방어막이며, 시업자 기본값도 의도적으로 보수적으로 설정되어 있습니다.

config.example.json — config.json에 복사한 뒤 수정하세요:

{
  "dailyCaps": {
    "connectionRequests": 20,
    "messages": 30,
    "posts": 5,
    "jobApplications": 10
  },
  "delayRangeMs": {
    "min": 1500,
    "max": 6000
  },
  "headless": false,
  "navigationTimeoutMs": 30000,
  "actionTimeoutMs": 15000,
  "loginTimeoutMs": 300000
}

네 가지 일일 상한

상한

제한 대상

기본값

connectionRequests

linkedin_send_connection_request 전송하는 연결 초대

20 / 일

messages

linkedin_send_message로 보내는 메시지

30 / 일

posts

linkedin_create_post로 게시하는 게시글

5 / 일

jobApplications

linkedin_apply_to_job로 접수하는 채용 지원

10 / 일

동작 방식은 다음과 같습니다:

  • 상한은 확인된 실행에서만 소비됩니다. 미리보기는 현재 할당량을 알려줄 뿐 소비하지 않으며, 서버가 애당초 거절하는 호출도 할당량을 소비하지 않습니다.

  • 카운터는 상태 디렉터리의 counters.json에 저장되므로 서버를 다시 시작해도 초기화되지 않습니다.

  • 상한에 도달하면 도구는 실행 대신 rate_limited로 실패합니다. 상한을 0으로 설정하면 해당 작업을 완전히 비활성화합니다.

  • 모든 미리보기 및 실행 결과에는 used, cap, remaining, resetsAt, allowed를 담는 quota 블록이 포함됩니다.

무작위 지연

delayRangeMs는 서버가 브라우저 작업에 앞서 균등하게 무작위로 딜십 구위입니다. — 기본값은 1500 ms에서 6000 ms입니다. 무작위화가 중요합니다: 고정된 간격은 기계 알고리즘의 시그니처이기 때문입니다. min은 max와 같을 수 있지만(고정 지연), min은 max를 초과할 수 없습니다. 그렇지 않으면 구성 로드가 config_invalid로 실패합니다.

상한은 자정(로컬 시간)에 초기화됩니다

카운터는 로컬 달력 날짜를 키로 사용합니다. 따라서 네 가지 상한은 UTC 자정도 아니고 오랫동안 흐르는 24시간 창도 아닌, 사용자의 시간대의 자정에 재설정됩니다. quota 블록의 resetsAt은 해당 다음 로컬 자정을 UTC ISO 타임스탬프로 직렬화한 값입니다.

기본값보다 낮춰서 시작하세요

제공된 기본값은 상한이지 권장값이 아닙니다. 계정이 새로 계정이고 연결 이력이 별로 없거나 자동화를 사용한 적이 없다면, 기본값보다 훨씬 낮춰서 시작하세요. 예: connectionRequests: 5, messages: 5, posts: 1, jobApplications: 2 — 그리고 LinkedIn 경고를 관찰하면서 수 주 동안 천천히 올려보세요. 평소 사용과 상관없어 보이는 활동의 치발증은 정확히 계정이 제한되는 원인이 됩니다.

도구 목록

서버가 등록하는 순서대로 11개 도구입니다.

다음에 있는 스크랩/채용 도구 4개(probe_linkedin_scrape_profile, linkedin_scrape_feed, linkedin_search_jobs, linkedin_apply_to_job)는 src/types.ts와 src/selectors.ts의 접촉 계약(shared contract)을 기준으로 문서화되었습니다. 클라이언트의 tools/list 출력이 여기의 인수 이름과 다르면 tools/list가 권위를 가집니다 — 서버는 항상 실제 스키마를 보고합니다.

linkedin_login

실제 가시되는 Chromium 창을 열고 사용자가 직접 로그인하기 를 기다립니다. 2FA나 CAPTCHA 단계도 포함됩니다. 성공하면 로컬 디스크의 세션 저장합니다. 인수가 없으며, --dry-run에서는 사용할 수 없습니다.

{}

linkedin_session_status

저장된 세션이 여전히 유효한지, 언제 저장되었는지, 언제 마지막으로 검증되었는지 보고합니다. 피드를 한 번 로드하여 확인합니다. 읽기 전용이며, --dry-run에서는 항상 유효 상태를 보고합니다. 인수가 없습니다.

{}

linkedin_create_post (쓰기 — 확인 필요)

텍스트 글을 자신의 피드에 게시합니다. posts 상한을 소모합니다.

미리보기:

{
  "text": "Spent the week reading Playwright's tracing internals. Notes soon.",
  "visibility": "connections"
}

확인:

{
  "text": "Spent the week reading Playwright's tracing internals. Notes soon.",
  "visibility": "connections",
  "confirm": true
}
  • text — 필수, 1~3000자. 약 1300자를 넘으면 LinkedIn이 게시물을 "더 보기" 뒤로 접습니다. 미리보기에서 경고를 안내합니다.

  • visibility — 선택, "public" 또는 "connections". 기본값은 "public"입니다.

  • mediaUrl — 선택 URL. 서버는 원격 미디어를 일체 가져오지 않습니다. 이 값을 넘기면 미리보기에서 경고가 표시되고 confirm 시 invalid_input으로 확정이 거절됩니다.

  • 보기previewToken — 선택 사항입니다. 그대로 발신하면 인수가 변경되지 않았는지 검증됩니다.

  • 이기본confirm — 선택 불리언. 실행되려면 정확히 true여야 합니다.

linkedin_send_connection_request (쓰기 — 확인 필요)

연결 초대를 보내며 선택적으로 쪽지를 포함할 수 있습니다. connectionRequests 상한을 소모합니다.

미리보기:

{
  "profileUrl": "https://www.linkedin.com/in/dana-whitfield-example/",
  "note": "Hi Dana — we both worked on the OpenTelemetry collector. Would like to connect."
}

확인:

{
  "profileUrl": "https://www.linkedin.com/in/dana-whitfield-example/",
  "note": "Hi Dana — we both worked on the OpenTelemetry collector. Would like to connect.",
  "previewToken": "9f2c41ab77e05d13",
  "confirm": true
}
  • profile_URL — 필수, 비어 있지 않아야 합니다. 전체 프로필 URL 또는 밴니티 슬러그(vanity slug)만 넣어도 됩니다.

  • note — 선택, 최대 300자. 200자를 넘는 쪽지는 많은 계정에서 LinkedIn Premium이 필요하므로, 미리보기에서 200자가 넘으면 경고합니다.

  • previewToken, confirm — 위와 동일합니다.

상대방이 이미 1촌 관계이거나, 초대가 이미 전송 중이거나, 페이지에 Connect 컨트롤이 없으면 할당량에 대한 누락을 소모하기 전에 invalid_input으로 거절됩니다.

linkedin_send_message (쓰기 — 확인 필요)

직접 메시지를 보냅니다. messages 캡을 사용합니다.

미리보기:

{
  "profileUrlOrConversationId": "https://www.linkedin.com/in/dana-whitfield-example/",
  "text": "Thanks for the pointer to the collector RFC — that answered my question."
}

확인:

{
  "profileUrlOrConversationId": "https://www.linkedin.com/in/dana-whitfield-example/",
  "text": "Thanks for the pointer to the collector RFC — that answered my question.",
  "confirm": true
}
  • profileUrlOrConversationId — 필수이며, 비어 있지 않아야 합니다. 프로필 URL 또는 슬러그, 또는 기존 대화 스레드 id(/messaging/thread/<id>/) 중 하나입니다.

  • text — 필수이며, 1자 이상 8000자 이하입니다.

  • previewToken, confirm — 위와 동일합니다.

프로필 모드에서는 수신자가 1촌 연결 상태여야 합니다. 그 외 사람은 할당량(quota)을 소비하기 전에 not_connected 오류로 거부됩니다. 이 도구는 InMail을 보내지 않으며, 작성 모드에서 Enter 키를 누르지 않고 Send 버튼을 클릭합니다.

linkedin_scrape_profile

프로필 하나를 읽고 구조화된 Profile을 반환합니다: name, headline, about, location, 연결 관계(connection degree), 본인 프로필 여부, 경력 항목, 학력 항목, 기술 항목입니다. 읽기 전용입니다.

{
  "profileUrl": "https://www.linkedin.com/in/dana-whitfield-example/"
}

linkedin_scrape_feed

피드의 최근 게시물을 읽고 FeedPost 항목을 반환합니다: 작성자 이름과 헤드라인, 본문, 게시물 URL, 좋아요 및 댓글 수, 게시 시각입니다. 읽기 전용입니다.

{
  "count": 20
}

linkedin_search_jobs

LinkedIn 채용 검색을 실행하고 JobListing 항목을 반환합니다: jobId, 제목, 회사, 위치, Easy Apply 여부, 채용 공고 URL입니다. 읽기 전용입니다.

{
  "keywords": "site reliability engineer",
  "location": "Berlin, Germany",
  "easyApplyOnly": true,
  "count": 25
}

keywords는 필수입니다. 나머지는 모두 선택 사항입니다.

  • location — 자유 형식의 장소 이름으로, LinkedIn의 자체 location 매개변수에 매핑됩니다.

  • easyApplyOnly — Easy Apply 공고로만 결과를 제한합니다(LinkedIn의 f_AL facet). linkedin_apply_to_job은 Easy Apply만 처리하므로, 이 서버를 통해 지원할 계획이라면 설정할 가치가 있습니다.

  • datePosted — "past24h", "pastWeek" 또는 "pastMonth".

  • experienceLevel — "internship", "entry", "associate", "midSenior", "director" 또는 "executive".

  • remote — true로 설정하면 원격 근무 공고로 결과가 제한됩니다.

  • count — 반환할 공고 수(기본값 25, 최대 100)입니다. 결과는 lazy-load 방식이라 count가 크면 각 항목 사이에 무작위 지연을 두고 반복 스크롤해야 하므로 시간이 걸릴 수 있습니다.

이 네 가지 facet은 위처럼 최상위 레벨에서 전달하거나, filters 객체 안에 묶어 전달할 수 있습니다. 두 방식 모두 허용되며, filters에 같은 키를 다시 전달하면 filters가 우선합니다.

{
  "keywords": "site reliability engineer",
  "filters": { "easyApplyOnly": true, "datePosted": "pastWeek", "remote": true },
  "count": 50
}

LinkedIn이 사용 가능한 job id 없이 렌더링하는 카드(프로모션 슬롯, placeholl드)는 반쯤 채워진 상태로 반환하지 않고 건너뛰며, 그 개수는 응답의 skipped 필드에 집계됩니다. 응답에는 사용된 searchUrl도 포함되므로, 브라우저에서 동일한 검색 쿼리를 직접 열 수 있습니다.

linkedin_apply_to_job (쓰기 — 확인 필요)

LinkedIn Easy Apply 지원을 제출합니다. jobApplications 캡을 사용합니다.

미리보기:

{
  "jobId": "3912847561",
  "resumePath": "/Users/parthbansal/Documents/resume.pdf"
}

확인:

{
  "jobId": "3912847561",
  "resumePath": "/Users/parthbansal/Documents/resume.pdf",
  "confirm": true
}
  • jobId — 필수입니다. 단순 job id, /jobs/view/<id>/ URL, ?currentJobId=를 포함하는 검색 URL 또는 job커블 모두 동일한 id로 해석됩니다.

  • resumePath — 이 컴퓨터에 있는 이력서 파일의 절대 경로(선택 사항). 파일이 없으면 file_not_found 오류로 실패합니다. 파일 내용은 결고 로그에 남지 않습니다.

  • 외부 지원자 추적 시스템(applicant-tracking system)으로 넘어가는공고는 external_application 오류, Easy Apply 컨트롤이 없는 공고는 not_easy_apply 오류로 실패합니다. 확인 전에 미리보기를 읽으세요 — 당신을 대신해 폼이 어떤 질문에 답할지 확인할 수 있는 유일한 기회입니다.

  • raw, confirm — 위와 동일합니다.

linkedin_list_pending_invites

대기 중인 초대를 PendingInvite 항목으로 나열합니다: name, headline, 프로필 URL, 보낸 시각(sent-at), 방향(direction)입니다. 읽기 전용입니다.

{
  "direction": "received",
  "count": 25
}
  • direction — 선택 사항. "received"(누군가 나에게) 또는 "sent"(내가 누군가에게)입니다. 기본값은 "received"입니다.

  • count — 선택 사항, 1에서 100까지의 정수. 기본값은 25입니다.

linkedin_list_connections

내 연결을 ConnectionSummary 항목으로 나열합니다: name, headline, 프로필 URL, 연결 시각(connected-at)입니다. 읽기 전용입니다.

{
  "count": 50,
  "query": "observability"
}
  • count — 선택 사항, 1에서 200까지의 정수. 기본값은 50입니다.

  • query — 선택 사항. 서버가 이미 읽은 연결 목록에 적용되는 로컬, 대소문자를 구분하지 않는 부분 문자열 필터입니다. LinkedIn 검색으로 전송되지 않습니다.


MCP 클라이언트 설정

서버는 stdio에서 JSON-RPC를 사용하며, 수동이 아니라 MCP 클라이언트가 실행하도록 설계되었습니다. 먼저 build(npm run build)한 다음 클라이언트가 dist/server.js를 가리키게 하세요.

서버는 config.json, 상태 디렉터리, 그리고 fixtures/를 staffing디렉토리relative as path 작업 디렉터리 기준으로 해석하므로 — 그리고 MCP 클라이언트의 작업 디렉터리는 대개 이 프로젝트가 아니므로 — env 블록에 절대 경로를 명접척으로 설정하는 것이 좋습니다.

Claude Code / Claude Desktop

claude_desktop_config.json에서:

{
  "mcpServers": {
    "linkedin": {
      "command": "node",
      "args": ["/Users/parthbansal/Desktop/claude code/linkedin-mcp/dist/server.js"],
      "env": {
        "LINKEDIN_MCP_CONFIG": "/Users/parthbansal/Desktop/claude code/linkedin-mcp/config.json",
        "LINKEDIN_MCP_STATE_DIR": "/Users/parthbansal/Desktop/claude code/linkedin-mcp/.linkedin-mcp",
        "LINKEDIN_MCP_LOG_LEVEL": "info"
      }
    }
  }
}

Cursor

.cursor/mcp.json에서:

{
  "mcpServers": {
    "linkedin": {
      "command": "node",
      "args": ["/Users/parthbansal/Desktop/claude code/linkedin-mcp/dist/server.js"],
      "env": {
        "LINKEDIN_MCP_CONFIG": "/Users/parthbansal/Desktop/claude code/linkedin-mcp/config.json",
        "LINKEDIN_MCP_STATE_DIR": "/Users/parthbansal/Desktop/claude code/linkedin-mcp/.linkedin-mcp",
        "LINKEDIN_MCP_LOG_LEVEL": "info"
      }
    }
  }
}

일반 stdio 클라이언트(예: Codex CLI)

stdio MCP 서버를 실행하는 모든 클라이언트는 명령어(command), 인자(args), 환경(env) 이렇게 같은 세 가지가 필요합니다:

{
  "name": "linkedin",
  "command": "node",
  "args": ["/Users/parthbansal/Desktop/claude code/linkedin-mcp/dist/server.js"],
  "env": {
    "LINKEDIN_MCP_CONFIG": "/Users/parthbansal/Desktop/claude code/linkedin-mcp/config.json",
    "LINKEDIN_MCP_STATE_DIR": "/Users/parthbansal/Desktop/claude code/linkedin-mcp/.linkedin-mcp",
    "LINKEDIN_MCP_LOG_LEVEL": "info"
  }
}

Codex CLI는 JSON 대신 TOML을 사용하지만 매핑은 일대일입니다. command = "node", args = ["/Users/parthbansal/Desktop/code/linkedin-mcp/dist/server.js"], 그리고 [env] 테이블입니다.

안전한 실험

계정에 아무런 변경을 가하지 않는 완전히 작동하는 서버를 등록하려면 args에 "--dry-run"을 추가하세요:

"args": [
  "/Users/parthbansal/Desktop/claude code/linkedin-mcp/dist/server.js",
  "--dry-run"
]

dry-run 모드에서는 어떤 요청도 linkedin.com에 도달하지 않으며 어떤 게시물, 초대, 메시지, 지원서도 제출되지 않습니다. 즉 에이전트가 처음으로 도구 표면을 탐색하도록 할 때 가장 적자한 방법입니다.

서버가 허용하는 다른 플래그: --headless, --config <path>, --log-level <debug|info|warn|error>, -h / --help(--help는 stdout이 프로토콜을 전달하므로 stderr로 출력됩니다). 알 수 없는 플래그는 하드 오류입니다. 철자가 틀린 --dry-runn으로 인해 실제로 dry-run 모드가 아닌데 dry-run인 것으로 설마 믿게 되어서는 안 됩니다. 우선 순위는 항상 명령줄 플래그 > 환경 변수 > config.json > 기본값 순서입니다.

모든 환경 변수는 .env.example에 문서화되어 있으며, 모두 선택 사항이고 어떤 것도 자격 증명을 보관하지 않습니다.


개발

출력(emit) 없이 타입 검사만 수행:

npm run typecheck

유닛 테스트 실행:

npm test

저장 시 다시 컴파일:

npm run dev

--dry-run

npm run dry-run은 빌드된 서버를 --dry-run으로 시작합니다. 이 모드에서는 브라우저가 linkedin.com 대신 fixtures/에 있는 로컬 HTML 픽스처를 가리키며, 모든 쓰기 흐름의 마지막 제출 클릭은 건너뛰기 때문에 확인된 작업이 유효성 검사, 할당량(quota) 검사, 대화형 상호작용 등 전체 코드 경로를 지난 뒤 실제 실행 직전에 멈춥니다. 실행 결과에는 dryRun: true가 포함되며, 미리보기에서도dry-run모드임을 경고합니다. linkedin_login은 사용할 수 없으며, linkedin_session_status는 항상 유효하다고 보고합니다.

픽스처는 프로필 페이지(연결 요청 버튼이 직접 있는 2촌 프로필, Connect 버튼이 "More" 메뉴 뒤에 있는 2촌 프로필, 메시저가 작동하는 1촌 연결의 세 가지 변형)와 메시지 보기, job search 채용 정보, Easy Apply와 외부 채용 추적 시스템 모두에 대한 직업 상세 페이지, 메시징, 초대, 연결을 다룹니다. 이것을 사용하여 개발하세요. 이 서버를 일상적으로 작업하면서 실제 계정을 만질 이유가 없습니다.

어떤 URL이 어떤 픽스처로 연결되는지는 src/fixtures.ts에 있는 정렬된 부분 문자열 규칙이 결정합니다. 이 순서는 중요합니다(load-bearing). /mynetwork/invite-connect/connections/는 /mynetwork/invit를 포함하고, /in/tomas-eriksen은 /untapped — so 광범위한 규칙보다는 구체적인 규칙이 위에 있어야 하며, tests/fixtures.test.ts가 그 순서를 고정합니다.

LinkedIn 없이 검증하기

npm run verify:dry

이 검증은 컴파일된 서버를 --dry-run으로 시작한 뒤, 실제 MCP 클라이언트처럼 stdio로 JSON-RPC를 주고받으며 23개 케이스에서 모든 11개 도구를 실행합니다 — 모든 읽기 도구를 자iers 고정장치(fixture) 상대로, 모든 쓰기 도구를 전체 미리보기 → 확인 handshake로, 그리고 유효한 경우 거부사항(오래된 previewToken, 너무 긴 게시물, 2촌 프로필로 보낸 메시지, 외부 직원공고, dry-run 상태에서의 linkedin_login)을 검증합니다. 또한 레포지토 contract 검증: preview는 executed: false를, confirm은 executed: true를 보고해야 하며, stdout에는 JSON-RPC만 포함해야 합니다.

서버 스스로의 시작 줄에 dryRun: true가 확인되지 않으면 실행거부하므로 우연한 계정 대한 실수를 방지합니다. 정상 동작하는 Chromium이 필요하고 — session_status를 제외한 모든 것에서 브라우저 페이지를 엽니다 — 어떤 실패 시에도 non-zero 종료를 하며, 실패를 selector/logic 실패(서버가 잘못된 경우)와 harness 실패(검증 루프가 잘못된 경우)로 나눕니다.

테스트

이 테스트 스위트는 Vitest(tests/**/*.test.ts)로 실행되며 현재 비율 제한기(src/rateLimiter.ts), Config 로더(src/config.ts), 픽스처 라우팅(src/fixtures.ts) 를 3개 파일에서 75개 케이스로 다루고 있습니다. 세 렉트 모두 설계적으로 차단되어 있습니다(불렁 격리). 각 사례는 새 mkdtemp 디렉터리에서 실행되며, 환경은 process.env primaryIterator가 아니라 명시적으로 전달되고, ratelimiter의 클록, sleep, 난수는 RateLimiterDeps 통해 주입됩니다. 네트워크 호출 없이와 브라우저 로 marq가 없습니다.

Any browser is needed is in the above dry-run sweep, not in unit tests — that is why npm test finishes in under second and nothing needs to be installed other than node_modules.

편집하기 전에 알아 두면 좋은 규칙

  • **stdout에 무언가를 쓰면 안 됩니다. 평소 stdout is JSON-RPC channel, a stray byte breaks the framing and the client disconnects. All diagnostics use Logger on stderr.

  • 프로젝트는 ESM with moduleResolution: "NodeNext"이므로, TypeScript source에서도 상대적 imports must exit .js for the**.

Example: The source says "* src/types.ts and src/errors.ts are the shared contract. src/selectors.ts contains only strings and pure functions." This means "** src/types.ts와 src/errors.ts는 공통 계약이며, src/selectors.ts는 문자열과 순수 함수만 가능합니다."

Troubleshooting

"diúl uh, and what's the next" — 그런 갈은 정상입니다

npm start을 직접 실행하면 stderr에 한 줄이 출력된 후 아무 소리 없이 대기합니다:

{"ts":"...","level":"info","msg":"linkedin-mcp ready on stdio","version":"0.1.0","tools":11,"dryRun":false,"headless":false}

이는 정상 서버이 mari, hang이 아닙니다. MCP stdio 서버는 포트가 있는 데몬도 아니고 결과를 출력한 뒤 종료하는 CLI도 아닙니다. stdin에서 JSON-RPC 요청을 읽고 stdout으로 응답을 씁니다. 따라서 자체 소개 후에는 클라이언트가 말하기를 기다리며 대기합니다. 어떤 클라이언트도 접붙지 않았으면 할 말이 없습니다. 정상 서버는 stdout에 아무것도 출력하지 않는 편안을 유지 — 어떤 한 바이트라도 흘러들어가면 프로토콜 framing이 be desynchronized입니다.

So npm start is not how you use it. 서버를 MCP client에 등록()아래 하고 클라이언트가 시작하게 하세요. 서버가 대기 중에 응답하는 것을 검증하고 싶다면 요청 하나를 stdin에 붙여 넣고 Return 키를 눌러 보세요 — tools/list댓페이지가 바로 돌아옵니다:

printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | node dist/server.js --dry-run

Ctrl-C를 누르면 수동으로 시작한 서버가 중지됩니다. SIGINT/SIGTERM을 처리하고 shutting down로 레 응답을 기록합니다.

두 가지 관련 증상:

  • "Client shows 0 tools, or "server failed to start"." Almost all the time the path under args is wrong or dist/ was not built. dist/server.js에 상대 경로가 아니라 절대 경로를 사용하고, npm run build를 실행하고, MCP 로그를 살펴보세요 — 서버의 stderr의 특반이 거기 들어오며, config_invalid 또는 Node 모듈 해석 오류가 대기합니다.

  • 라인의 표시는 보이지만 호출마다 오류가 떨어집니다. 라인 내용을 확인하세요. dryRun:true는 픽스처만 사용하며 실제 LinkedIn에 어떤 동작도 적용되지 않는다는 뜻입니다. 호출마다 not_authenticated는 저장된 세션이 아직 없다는 뜻이므로 linkedin_login 실행하세요.

오류 코드

실패는 고윳값 code를 가진 MCP 도구 오류로 반환됩니다. 실제로 흔한 것:

verification_required — LinkedIn이 CAPTCHA, checkpoint 또는 인 supported wall을 띄웠습니다. 서버는 의도적으로 여기서 멈추며 절대 해결하지 않아야 합니다. 일반 브라우저에서 linkedin.com을 열어 직접 챌린지를 해결한 다음, linkedin_login을 다시 실행하세요. 이게 반복된다면 일일 한도(cap)를 낮춰야 하는 신호로 받아들이십시오.

session_expired — 저장된 세션이 더 이상 인증되지 않거나, LinkedIn이 피드를 로그인 페이지로 redirect했습니다. linkedin_login을 실행하고 직접 다시 로그인하세요. linkedin_session_status에 specific reason이 표시됩니다.

selector_not_found — 서버가 필요한 요소를 찾을 수 없었습니다. 이는 거의 항상 LinkedIn이 마크업을 변경했음을 뜻하며, 사용자가 잘못한 것이 아닙니다. 프로젝트의 모든 DOM 선택자는 **src/selectors.ts**에 모여 있으며, 이것이 유일한 유지보수 지점입니다. 관련 후보 목록을 찾아 새 선택자를 그 앞에 추가하고 다시 빌드하면 됩니다. 후보는 순서가 있는 목록이므로 추가된 선택자가 기존 선택자를 깨뜨리지 않습니다. --log-level debug로 실행하면 어떤 조회가 실패했는지 알 수 있습니다.

rate_limited — 일일 상한에 도달했습니다. 오류와 quota 블록에서 어느 상한인지, 언제 초기화되는지(다음 로컬 자정)를 확인할 수 있습니다. 기다리거나, config.json에서 그 상한을 올리면 됩니다. 다만 상한을 올리는 것은 계정이 제한되는 원인이 되는 행동이므로 신중하게 올리십시오.

external_application — 채용 공고가 지원자를 LinkedIn의 Easy Apply 양식 대신 외부 지원자 추적 시스템으로 넘기는 경우입니다. 서버는 제3자 사이트를 작성하지 않습니다. 브라우저에서 채용 공고를 열어 그쪽에서 지원하세요.

not_connected — 1촌 연결이 아닌 사람에게 메시지를 보내려고 했습니다. 먼저 연결 요청을 보내고 수락될 때까지 기다린 후 메시지를 보내세요. 서버는 InMail로 이 문제를 우회하지 않습니다.

만날 수 있는 다른 코드: not_authenticated(아직 디스크에 세션이 없음 — linkedin_login 실행), not_easy_apply, invalid_input, confirmation_mismatch(미리보기와 확인 사이에 인자가 변경됨 — 다시 미리보기), navigation_failed, browser_error, dry_run_unsupported, config_invalid(형식이 잘못된 config.json — 서버 시작 전에 보고됨), 그리고 file_not_found.


수행하지 않는 작업

  • InMail 없음. 메시징은 1촌 연결과 기존 대화만 가능합니다.

  • 외부 ATS 지원 없음. Easy Apply만 가능하며, LinkedIn을 벗어나는 모든 처리를 거부합니다.

  • CAPTCHA 해결 없음. 2FA 자동화 또는 체크포인트 우회는 절대 없습니다.

  • 다중 계정 지원 없음. 저장된 세션 하나, 계정 하나, 키보드 앞의 사용자 한 명만 지원합니다.

  • 원격 미디어 업로드 없음. 서버는 게시물에 첨부할 URL을 가져오지 않습니다.


보안 및 개인정보 보호

저장되는 내용과 위치 모든 것은 이 머신의상태 디렉터리 아래에 저장됩니다. 기본값은 <cwd>/.linkedin-mcp/이며, LINKEDIN_MCP_STATE_DIR(또는 경로별로 LINKEDIN_MCP_STORAGE_STATE, LINKEDIN_MCP_USER_DATA_DIR, LINKEDIN_MCP_SCREENSHOT_DIR, LINKEDIN_MCP_COUNTERS)로 재정의할 수 있습니다:

경로

내용

storageState.json

사용자의 LinkedIn 쿠키 및 원본 저장내용 — **모드 0600**으로 기록됨

chromium-profile/

영구 Chromium 프로필 디렉터리

counters.json

오늘 로컬 날짜와 네 가지 작업 개수

screenshots/

진단 스크린샷, 로컬에만 기록됨

아무 곳에도 전송되지 않습니다. 텔레메트리, 애널리틱스, 크래시 리포팅 및 어떠한 종류의 외부 연락 전화도 없습니다. 유일한 네트워크 대상은 linkedin.com이며, 가 사용자 브라우저 세션을 통해서만 연결됩니다. --dry-run에서는 그 통신조차 일어나지 않습니다.

스크린샷은 로컬 전용입니다. 자신의 디버깅을 위해 스크린샷 디렉터리에 기록되며, 업로드되거나 도구 출력에 포함되지 않습니다.

로깅은 의도적으로 얇게 유지됩니다. 진단 정보는 stderr로 출력됩니다. 쿠키, storageState 내용, 이력서 파일 내용은 절대 로그에 기록되거나 직렬화되지 않습니다. 도구가 실패하면 인자가 아니라 오류 코드만 기록되므로, 메시지 본문이나 이력서 경로가 사용자의 클라이언트가 캡처한 stderr에 유출될 수 없습니다. redactConfig는 시작 시 서버가 기록하는 적용 구성(effective-configuration) 줄에서 절대 경로(및 홈셰어디렉터리)를 제거합니다.

.gitignore 보장 .gitignore는 .env 및 .env.*를 제외합니다(желая .env.example 을 유지), 전체 .mcp-linkedin/ 상태 디렉터리, 어느 경로에 있든 수준의 storageState.json, chromium-profile/, counters.json, screenshots/, *.log, 그리고 node_modules/, dist/, coverage/를 제외합니다. 추적되는 파일은 config.example.json와 사용자의 config.json뿐이며, 이들에는 상한값과 시간 초과 값 외에 다른 것이 포함되지 않습니다. storageState.json을 커밋하지 마십시오 — 이것은 실제 자격 증명입니다.

상태 디렉터리는 비밀처럼 취급하십시오. storageState.json을 읽을 수 있는 사람은 비밀번호나 2FA 코드 없이 LinkedIn에서 사용자처럼 활동할 수 있습니다. 유출된 것으로 의심되면 LinkedIn의 보안 설정에서 모든 세션 로그아웃한 후 파일을 삭제하고 다시 로그인하십시오.

Need examine output for translation errors and Markdown formatting. Let's test formatting.

We have table with header. Need ensure rows/columns same. Good.

Potential issue: "어떤 조회가 실패" vs "which lookup failed" - "어떤조회" okay.

Need "state directory" as "상태디렉터리" consistent.

Maybe "등 each element appears same number of times" is fine.

Please produce final with no commentary**selector_not_found** — 서버가 필요한 요소를 찾을 수 없었습니다. 이는 거의 항상 LinkedIn이 마크업을 변경했음을 뜻하며, 사용자가 잘못한 것이 아닙니다. 프로젝트의 모든 DOM 선택자는 **src/selectors.ts**에 모여 있으며, 이것이 유일한 유지보수 지점입니다. 관련 후보 목록을 찾아 새 선택자를 그 앞에 추가하고 다시 빌드하면 됩니다. 후보는 순서가 있는 목록이므로 추가된 선택자가 기존 선택자를 깨뜨리지 않습니다. --log-level debug로 실행하면 어떤 조회가 실패했는지 알 수 있습니다.

rate_limited — 일일 상한에 도달했습니다. 오류와 quota 블록을 통해 어느 상한인지와 언제 초기화되는지(다음 로컬 자정)를 알 수 있습니다. 기다리거나 config.json에서 그 상한을 올리면 됩니다. 다만 상한을 올리는 것은 계정이 제한될 수 있는 행동이므로, 신중하게 올리십시오.

external_application — 채용 공고가 지원자를 LinkedIn의 Easy Apply 양식 대신 외부 지원자 추적 시스템에게 넘기는 경우입니다. 서버는 제3자 사이트를 작성하지 않습니다. 브라우저에서 해당 채용 공고를 열고 그곳에서 선택하세요.

not_connected — 1촌 연결이 아닌 사람에게 메시지를 보내려고 했습니다. 먼저 연결 요청을 보내고 수락될 때까지 기다린 후 메시지를 보내세요. 서버는 InMail로 이 문제를 우회하지 않습니다.

다른 코드가 발생할 수 있습니다: not_authenticated (아직 디스크에 세션이 없음 — linkedin_login 실행), not_easy_apply, invalid_input, confirmation_mismatch (미리보기와 확인 사이에 인자가 변경됨 — 다시 미리보기), navigation_failed, browser_error, dry_run_unsupported, config_invalid (형식이 잘못된 config.json ، 서버 시작 전에 보고됨) 및 file_not_found.


이 도구가 하지 않는 일

  • InMail 없음. 메시지는 1촌 연결과 기존 대화에만 가능합니다.

  • 외부 ATS 지원 없음. Easy Apply만 가능하며, LinkedIn을 벗어나는 항목은 모두 거부됩니다.

  • CAPTCHA 해결 없음. 2FA 자동화와 체크포인트 우회도 결코 없습니다.

  • 다중 계정 지원 없음. 저장된 세션 하나, 계정 하나, 키보드 앞의 한 사람만 사용할 수 있습니다.

  • 원격 미디어 업로드 없음. 서버는 게시물에 첨부할 URL을 가져오지 않습니다.


보안 및 개인정보 보호

저장되는 내용과 위치. 모든 것은 이 컴퓨터의 상태 디렉터리 아래에 저장됩니다. 기본값은 <cwd>/.linkedin-mcp/이며, LINKEDIN_MCP_STATE_DIR (또는 경로별로 LINKEDIN_MCP_STORAGE_STATE, LINKEDIN_MCP_USER_DATA_DIR, LINKEDIN_MCP_SCREENSHOT_DIR, LINKEDIN_MCP_COUNTERS)로 변경할 수 있습니다:

경로

내용

storageState.json

사용자의 LinkedIn 쿠키 및 원본 저장 데이터, **모드 0600**으로 기록

chromium-profile/

영구 Chromium 프로필 디렉터리

counters.json

오늘의 로컬 날짜와 4가지 작업 횟수

screenshots/

진단용 스크린샷, 로컬에만 기록

어디에도 전송되지 않습니다. 텔레메트리, 분석, 크래시 리포트, 어떤 종류의 연락도 없습니다. 유일한 네트워크 대상은 linkedin.com이며, 사용자의 브라우저 세션을 통해서만 연결됩니다. --dry-run에서는 그러한 연결조차 없습니다.

스크린샷은 로컬 전용입니다. 스크린샷은 사용자 자신의 디버깅을 위해 스크린샷 디렉터리에 기록되며 절대 업로드되거나 도구 출력에 포함되지 않습니다.

로깅은 의도적으로 최소화되어 있습니다. 진단 정보는 stderr로 전송됩니다. 쿠키, storageState 내용, 이력서 파일 내용은 절대 기록하거나 직렬화하지 않습니다. 도구 실패시 실제 인자 대신 오류 코드를 기록하므로, 메시지 본문이나 이력서 경로가 클라이언트가 캡처한 stderr에 유출될 수 없습니다. redactConfig는 서버의 시작 시 기록되는 구성 줄에서 절대 경로(및 홈 디렌터리)를 제거합니다.

Gitignore 보장. .gitignore는 .env 및 .env.* (.env.example는 유지), 전체 .linkedin-mcp/ 상태 디렴터删除 state.json, chromium-profile/, counters.json, screenshots/, *.log, 추가로 node_modules/, dist/, coverage/를 제외하십니다. 오직 config.example.json과 사용자의 config.json만 추적되며, 이 파일들은 한도와 타임아웃 외에는 아무것도 포함하지 않습니다. storageState.json은 절대로 커밋하지 마세요. — 이것은 실인증 정보입니다.

상태 디렉터리를 비밀처럼 취급하세요. storageState.json을 읽을 수 있는 사람은 비밀번호나 2FA 코드 없이 LinkedIn에서 사용자를 대신할 수 있습니다. 노출되었가 걱정되면 LinkedIn 자체 보안 설정에서 모든 세션을 로그아웃하고, 이 파일을 삭제한 후 다시 로그인하세요.

Available Tools

11 tools
linkedin_apply_to_jobApply to a LinkedIn job (Easy Apply only)A
Destructive

Submits a LinkedIn Easy Apply application. jobId accepts a bare id ("3812345678"), a job URL, a ?currentJobId= URL or a urn:li:jobPosting: URN. resumePath is optional — an absolute path to a local .pdf, .doc or .docx file (never uploaded anywhere except LinkedIn itself); omit it to use whatever resume is already attached to your LinkedIn profile. answers maps question text to the answer to give; keys are matched case-insensitively and by substring, so "years of experience" answers "How many years of experience do you have?". CONFIRMATION IS REQUIRED: called without confirm: true this opens the Easy Apply modal read-only, lists every question it will be asked, flags the required ones it cannot answer in unansweredRequired, closes the modal without submitting, and returns a previewToken. Re-issue the identical call with confirm: true to actually submit. Postings that apply through an employer's own site are refused with external_application and their URL handed back for you to use by hand; postings with no Easy Apply button are refused with not_easy_apply. If a required question still cannot be answered at submit time the application is abandoned with invalid_input rather than sent incomplete — this tool never submits a partial application. Counts against the daily jobApplications cap.

NameRequiredDescriptionDefault
jobIdYes
answersNo
confirmNo
resumePathNo
previewTokenNo

TDQS

A4.8/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations indicate readOnlyHint=false, destructiveHint=true, idempotentHint=false, openWorldHint=true. The description goes beyond annotations by disclosing the two-phase confirmation requirement, the refusal reasons, and that it never submits partial applications. It also clarifies that resumePath is never uploaded anywhere except LinkedIn. While it doesn't detail every failure mode, it covers essential behavioral traits beyond the annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is comprehensive yet well-structured. It front-loads the core action, then details each parameter, then explains the confirmation flow and failure modes. Every sentence adds value, and it's written in a logical order. It's long but justified given the complexity.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity (5 params, nested objects, validation logic) and lack of output schema, the description provides complete guidance: parameter formats, confirmation flow, refusal cases, failure handling, and quota impact. No critical information missing for an agent to call it correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0%, so the description must fully explain each parameter. It does: jobId formats, resumePath optionality and accepted file types, answers matching semantics (case-insensitive, substring), confirm flag behavior, and previewToken usage. This adds significant meaning beyond the bare schema properties.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: submitting a LinkedIn Easy Apply application. It specifies the verb (submits), resource (LinkedIn Easy Apply application), and key details like jobId formats and confirmation requirement. It distinguishes itself from siblings like linkedin_search_jobs by focusing on the application action.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly explains when to use: for Easy Apply postings, and when NOT to use: external applications and non-Easy Apply postings are refused. It also clarifies the confirm flag flow (preview vs submit). Mentions alternatives implicitly (handling external URL manually). Provides clear context for when this tool is appropriate versus others.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

linkedin_create_postCreate a LinkedIn postA
Destructive

Publishes a text post to the signed-in member’s LinkedIn feed. This is a two-step tool: called without confirm: true it only returns a preview (character count, line count, audience, and a previewToken) and touches nothing on LinkedIn; call it again with the same arguments plus confirm: true to actually publish. Counts against the daily posts cap. visibility defaults to "public" (Anyone); "connections" restricts the post to 1st-degree connections. LIMITATION: mediaUrl is accepted for validation but images and video are NOT supported — this server never performs its own network fetches, so it cannot download remote media, and confirming a call that sets mediaUrl is refused with invalid_input rather than silently publishing text only. Post without media, or attach the image by hand in LinkedIn afterwards. Posts over roughly 1300 characters are collapsed by LinkedIn behind "…see more" (a warning, not an error). Under --dry-run the composer is driven against a local HTML fixture and the final "Post" click is skipped.

NameRequiredDescriptionDefault
textYes
confirmNo
mediaUrlNo
visibilityNo
previewTokenNo

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already mark destructive=true, but the description adds substantial context: the two-step preview/publish mechanism, daily posts cap, visibility default, media refusal with invalid_input, 1300-character collapse warning, and dry-run behavior. This goes well beyond what annotations provide and contains no contradictions.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

While long, every sentence adds operational value: purpose first, then invocation pattern, then limitations and edge cases. There is no filler or redundant phrasing; the structure logically leads the agent through what to expect.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Covers the preview response fields, error handling for media, warning threshold, and dry-run behavior. The only notable gap is that it doesn't describe what the final publish call returns (e.g., post ID or confirmation response), which would be useful since there is no output schema. Still, this is minor relative to the volume of useful context.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema has zero descriptions, so the description carries the full burden. It explains text (content), confirm (publish gate), mediaUrl (accepted but unsupported, refused on confirm), visibility (default 'public', meaning of 'connections'), and previewToken (returned from preview). All parameters are meaningfully clarified.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource: 'Publishes a text post to the signed-in member’s LinkedIn feed.' This clearly distinguishes it from siblings like send_message or scrape_profile and leaves no ambiguity about what the tool does.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It explains the two-step invocation pattern (preview without confirm, publish with confirm) and the media limitation as a clear 'not supported' case with manual workaround. It doesn't explicitly mention sibling alternatives, but the usage context and when-to-publish guidance are strong.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

linkedin_list_connectionsList LinkedIn connectionsA
Read-onlyIdempotent

Lists the signed-in member’s 1st-degree connections, most recently added first (LinkedIn’s default sort). count caps how many are returned (default 50, max 200) and the list is scroll-paginated until that many are loaded, the list stops growing, or an internal scroll cap is reached. query is an optional case-insensitive substring filter applied LOCALLY to the name and headline of the connections that were loaded — it is not LinkedIn’s own search, so a match that sits far down a very long list may not be reached. Read-only: takes no confirm and changes nothing. Each entry carries name, headline, canonical profile URL, and a "connected at" timestamp when LinkedIn exposes one. total is the account-wide count from the page header when it can be read (totalSource: "page-header"), otherwise the number of cards that were loaded.

NameRequiredDescriptionDefault
countNo
queryNo

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, openWorldHint, idempotentHint, and destructiveHint, so the bar is lower. The description adds significant behavioral detail beyond that: pagination mechanics (scroll-paginated until count met, stops growing, internal cap), how the query filter is applied locally, the total count source behavior, and the per-entry fields including 'connected at' timestamp. This goes well beyond the structured hints.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is dense but efficient—about 5 sentences covering all important aspects. It is front-loaded with the core purpose and sorted order, then details parameters and output. Could be slightly more concise (e.g., merging some clauses) but every sentence adds value without fluff.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool with only 2 optional parameters and no output schema, the description covers all necessary context: purpose, sorting, pagination, parameter semantics, output fields, total count source, and read-only nature. An agent can confidently call it and interpret results without needing additional external knowledge.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0%, so the description must fully explain both parameters. It does: count is given a default (50), maximum (200), and describes pagination; query is defined as a case-insensitive substring filter applied locally to name and headline. This fully compensates for the lack of schema descriptions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool lists the signed-in member's 1st-degree connections with a specific sort order (most recently added first). This distinguishes it from siblings like linkedin_send_connection_request (mutating) and linkedin_scrape_profile (single profile), leaving no ambiguity about what it does.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explains when to use it (listing connections) and clarifies that the query filter is local, not LinkedIn's search, which warns against misuse for large lists. However, it does not explicitly point to alternative tools for actions like sending requests or scraping profiles, though the sibling names make that implicit. Slight gap in explicit exclusions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

linkedin_list_pending_invitesList pending LinkedIn invitationsA
Read-onlyIdempotent

Lists invitations that are still awaiting a decision. direction: "received" (the default) returns invitations other people sent you; direction: "sent" returns invitations you sent that have not been accepted yet. count caps how many are returned (default 25, max 100). Read-only: this never accepts, ignores or withdraws anything, and takes no confirm. Each entry carries name, headline, canonical profile URL, and a timestamp (the datetime attribute when LinkedIn provides one, otherwise the relative text such as "3 days ago"). An empty list is a normal result, not an error. total is how many invitation cards were found on the page, which can exceed returned when count is smaller.

NameRequiredDescriptionDefault
countNo
directionNo

TDQS

A4.4/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description explicitly states the tool never accepts, ignores, or withdraws anything, and takes no `confirm`, adding real behavioral context beyond the readOnlyHint annotation. It also discloses edge cases such as empty lists being normal and the difference between `total` and `returned`.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is dense but every sentence earns its place: purpose, parameter semantics, read-only guarantee, output format, and edge-case handling. It is front-loaded with the core action and then efficiently covers nuances without redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description fully explains what entries contain, how timestamps are represented, that an empty list is valid, and how `total` relates to `returned`. Combined with parameter coverage and side-effect disclosure, nothing critical is missing for calling this tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description carries full responsibility for explaining parameters. It thoroughly documents the `direction` enum values with defaults, `count` default and maximum, and even explains result-field meanings that relate to the parameters, fully compensating for the schema gap.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool lists 'invitations that are still awaiting a decision' and specifies the resource precisely. It also distinguishes sent vs received directions, but does not explicitly differentiate from the similar sibling tool `linkedin_list_connections`, 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.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives strong operational guidance: direction defaults, count limits, and read-only behavior. However, it never mentions when to prefer this tool over alternatives like `linkedin_list_connections` or `linkedin_send_connection_request`, leaving selection context implied rather than explicit.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

linkedin_loginLog in to LinkedIn (interactive)A
Idempotent

Opens a visible Chromium window on LinkedIn’s login page and waits for the human to sign in themselves, including any 2FA or CAPTCHA step. This tool never types credentials and never reads the password field. When LinkedIn shows a signed-in feed, the browser session is saved to local disk (permissions 0600) and reused by every other tool. Takes no arguments. Not available under --dry-run.

NameRequiredDescriptionDefault

No parameters

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already indicate readOnlyHint=false and idempotentHint=true, but the description adds valuable context: it opens a visible browser, waits for human input, never types credentials, saves the session to local disk with permissions 0600, and is interactive. This goes beyond annotations and provides security-relevant details that affect agent behavior.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with the key action (opens browser, waits for human) and then covers important caveats (never types, session saved, not under dry-run) in a compact set of sentences. Every sentence contributes value, and the structure is scannable.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's interactive nature and reliance on human input, the description covers the essential flow: what it does, how it completes (when feed appears, session saved), and constraints (no credential handling, file permissions). It doesn't specify failure handling, but for a login tool this is acceptable and the description is complete for an agent to invoke it correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool takes no arguments, and the schema confirms an empty properties object. The description redundantly states 'Takes no arguments.' Since there are no parameters to document, the baseline of 4 applies, and the description adds no misleading information.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: opens a visible Chromium window on LinkedIn's login page and waits for the human to sign in. It specifies the verb (opens, waits), resource (LinkedIn login page), and explicitly distinguishes itself from siblings by emphasizing it requires human interaction and never types credentials.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description communicates when to use it: when a human must authenticate interactively, including 2FA/CAPTCHA. It also notes the session is saved and reused by other tools, implying usage before authenticated operations, and states it's unavailable under --dry-run. However, it doesn't explicitly name alternatives like linkedin_session_status for checking login status, or mention when not to use it.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

linkedin_scrape_feedScrape the LinkedIn feedA
Read-onlyIdempotent

Reads the signed-in member's home feed and returns the posts as structured data: author name and headline, post text, permalink, reaction and comment counts, and a timestamp. count is how many posts to return (default 10, max 50). The feed is lazy-loaded, so reaching a high count means scrolling, and this tool pauses a randomized human-like interval between scrolls — a large count can therefore take a minute or more. Read-only: takes no confirm, consumes no daily quota, and never likes, comments or reposts; the only clicks are "…see more" expanders so post text is captured at full length. Posts are de-duplicated by permalink (LinkedIn recycles cards while scrolling) and sponsored or suggestion cards with no readable author are skipped. Fewer posts than requested is a normal result, not an error.

NameRequiredDescriptionDefault
countNo

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description substantially exceeds the annotations' coverage. It discloses the read-only nature (never likes/comments/reposts, no confirm, no quota), explains the lazy-loading with human-like pauses and the time implication for large counts, de-duplication by permalink, skipping of sponsored cards, and that fewer-than-requested results is normal. This rich behavioral detail helps the agent anticipate side effects and performance without contradicting 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.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Though longer than average, every sentence earns its place. The opening sentence states the core purpose, then parameter semantics, behavioral safety, de-dup/skipping logic, and the normal-result caveat are each addressed succinctly. The structure is front-loaded and contains no filler or repetition.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool with one parameter and no output schema, the description covers everything an agent needs: what it returns, how it behaves, potential pitfalls (time, fewer results), and safety guarantees. There are no meaningful gaps that would prevent correct invocation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must fully compensate. It does: it explains that count is how many posts to return, defaults to 10, has a max of 50, and that a larger count involves scrolling and can take a minute or more. This goes far beyond the schema's bare integer min/max, giving the agent the context needed to choose an appropriate value.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb ('reads') and resource ('signed-in member's home feed'), and enumerates the exact output fields (author name/headline, post text, permalink, reaction/comment counts, timestamp). This clearly distinguishes it from sibling tools like linkedin_scrape_profile, which targets profiles, and linkedin_create_post, which writes.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description unambiguously communicates that this tool is for reading the home feed, which sets it apart from siblings (profiles, jobs, messaging). However, it does not explicitly name alternative tools or state conditions when this tool should be avoided (e.g., 'if you need profile data, use linkedin_scrape_profile'), leaving that inference to the agent.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

linkedin_scrape_profileScrape a LinkedIn profileA
Read-onlyIdempotent

Reads a LinkedIn member profile and returns it as structured data: name, headline, about, location, connection degree, experience, education and skills. profileUrl accepts a full URL ("https://www.linkedin.com/in/john-doe"), a path ("/in/john-doe") or a bare slug ("john-doe"); omit it entirely to read the signed-in member's own profile. Read-only: takes no confirm, consumes no daily quota, and the only thing it clicks is the "…see more" expander, which reveals text already on the page. Profile sections LinkedIn hides from the viewer come back as null or [] rather than as an error — only a profile with no readable name fails (selector_not_found), because that means the page is not a profile. connectionDegree is 1, 2 or 3, and null when no badge is shown (which includes your own profile). skills is capped at 50 entries, experience at 25, education at 15.

NameRequiredDescriptionDefault
profileUrlNo

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Even though the annotations already declare readOnlyHint and idempotentHint, the description adds substantial behavioral detail: it consumes no daily quota, only clicks the '…see more' expander, returns null/[] for hidden sections, and specifies the sole error condition (selector_not_found). It also clarifies connectionDegree semantics and result caps, going well beyond the annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long but densely informative, with the core purpose and return fields front-loaded followed by parameter formats, behavioral notes, and edge cases. Every sentence adds operational value; no filler or tautology is present.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With one optional parameter, strong annotations, no output schema, and no nested objects, this description covers all necessary invocation details: input formats, return fields, error behavior, and limits. An agent can correctly select and call this tool without needing additional documentation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema only defines profileUrl as a string with no description, giving 0% coverage. The description fully compensates by explaining all accepted input formats, the default when omitted, and what the tool reads from that URL. This gives an agent everything needed to pass the parameter correctly.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource: 'Reads a LinkedIn member profile and returns it as structured data,' and enumerates the exact fields returned. It is clearly distinguishable from sibling tools like linkedin_scrape_feed and linkedin_search_jobs, which target different data sources.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives concrete usage context: accepted profileUrl formats (full URL, path, bare slug) and the behavior when omitted (reads the signed-in member's own profile). It does not explicitly name alternatives or when not to use this tool, but the profile-scoped purpose and sibling names make the choice reasonably clear.

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 jobsA
Read-onlyIdempotent

Searches LinkedIn job postings and returns them as structured data: job id, title, company, location, whether the posting supports Easy Apply, and a URL. keywords is required; location is a free-text place name ("London", "Remote", "New York, NY"). Four optional facets narrow the search — easyApplyOnly, datePosted ("past24h" | "pastWeek" | "pastMonth"), experienceLevel ("internship" | "entry" | "associate" | "midSenior" | "director" | "executive") and remote — each accepted either at the top level or nested inside a filters object; filters wins if you somehow pass both. count is how many postings to return (default 25, max 100); results are lazy-loaded, so a high count means scrolling with a randomized human-like pause between scrolls and can take a while. Read-only: takes no confirm and consumes no daily quota. Cards LinkedIn renders without a usable job id are skipped and counted in skipped. Pass a returned jobId to linkedin_apply_to_job, which handles Easy Apply postings only — so filter on easyApply before trying to apply.

NameRequiredDescriptionDefault
countNo
remoteNo
filtersNo
keywordsYes
locationNo
datePostedNo
easyApplyOnlyNo
experienceLevelNo

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already mark read-only and idempotent, but description adds substantial behavior: lazy-loading with randomized scroll pauses, scoring and skipping of cards without job IDs (counted in `skipped`), no confirm or daily quota, and the filter precedence rule (filters wins if both present). This exceeds annotation coverage and is genuinely useful.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Although lengthy, every sentence adds value: purpose, parameters, behavior, and downstream workflow. Front-loads the core action and returns, then details. No filler; structure is logical.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a complex tool with 8 params, nested objects, and no output schema, the description covers return fields, skipped count, lazy-loading performance, and the apply workflow. It also clarifies default/max count and filter precedence. No critical missing information for an agent to call it correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, yet the description compensates by explaining each major parameter: required keywords, free-text location examples, all four facets with exact enum values (datePosted and experienceLevel), count default/max, and the nested-versus-top-level filter hierarchy. This is more than the schema provides.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Clear verb+resource: 'Searches LinkedIn job postings' and explicitly lists returned fields (job id, title, company, location, Easy Apply support, URL). Distinguishes from siblings because no other sibling handles job search; it's the obvious entry point for discovery.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides clear context: explains how to use filters and count, and explicitly routes results to linkedin_apply_to_job for Easy Apply postings. Does not explicitly state exclusions (e.g., when not to use) but the workflow guidance is strong. Lacks a direct 'use instead of X' but that's not needed given sibling separation.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

linkedin_send_connection_requestSend a LinkedIn connection requestA
Destructive

Sends one connection invitation to a member, optionally with a note. Two-call handshake: without confirm: true this only reads the profile (name, headline, connection degree, and whether a Connect control exists at all) and returns a preview plus a previewToken — nothing is clicked and no invitation is sent. Re-issue the same call with confirm: true to send. profileUrl accepts a slug ("john-doe") or any LinkedIn profile URL. note is capped at 300 characters; free accounts are limited to 200 and LinkedIn silently truncates past its own limit, so notes over 200 characters are flagged. The request is REFUSED (with invalid_input, before any quota is spent) when the member is already a 1st-degree connection, when an invitation is already pending, or when LinkedIn offers no Connect control for that profile; the preview says so up front in that case. Counts against the daily connectionRequests cap. Under --dry-run the invitation dialog is driven against a local HTML fixture and the final Send click is skipped.

NameRequiredDescriptionDefault
noteNo
confirmNo
profileUrlYes
previewTokenNo

TDQS

A5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description goes far beyond the annotations (readOnlyHint=false, destructiveHint=true) by detailing concrete side effects: no click or send without confirm, quota consumption, note truncation limits per account type, and refusal before quota is spent. This level of transparency is exemplary.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is dense but every sentence adds critical information. It is front-loaded with the core purpose and handshake, then layers on parameter details, refusal conditions, and quotas. No redundancy or filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity (two-call handshake, refusals, dry-run, quotas, note limits), the description covers every operational aspect. Without an output schema, it explains what the preview returns and what confirm does. An agent can safely and correctly invoke this tool without further context.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0% (no descriptions in the schema), so the description carries full burden. It explains profileUrl accepts slug or URL, note has 300-char cap (200 for free accounts with silent truncation), confirm triggers the actual send, and previewToken is part of the handshake. All four parameters are clarified.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool sends a connection invitation to a member, optionally with a note, and explicitly describes the two-call handshake. It distinguishes itself from siblings like send_message or create_post by focusing on the connection request action and its preview/confirm flow.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides explicit usage instructions: the two-call pattern (preview then confirm), when requests are refused (already 1st-degree, pending invite, no Connect control), and the dry-run behavior. It clarifies when not to call with confirm:true immediately, covering usage conditions thoroughly.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

linkedin_send_messageSend a LinkedIn direct messageA
Destructive

Sends one direct message, either to a 1st-degree connection (pass a profile URL or slug) or into a conversation that already exists (pass the id from its /messaging/thread// URL). Two-call handshake: without confirm: true this only reads the profile or thread and returns a preview plus a previewToken, and nothing is typed or sent. Messaging is limited to 1st-degree connections — a profile at 2nd/3rd degree (or one whose degree cannot be read) is refused with not_connected, and InMail is never attempted. Counts against the daily messages cap. Under --dry-run everything runs against local fixtures and the final Send click is skipped.

NameRequiredDescriptionDefault
textYes
confirmNo
previewTokenNo
profileUrlOrConversationIdYes

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond annotations (readOnlyHint false, destructiveHint true), the description discloses the two-call handshake, that confirm:true is required to actually send, that it counts against a daily messages cap, the dry-run behavior, and that InMail is never attempted. It also mentions the refusal condition. This is rich, actionable behavioral context that adds real value over 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.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is dense yet efficient, front-loading the core action and then packing critical details (handshake, restrictions, dry-run) without fluff. Every sentence adds necessary information and the structure guides the reader through the flow logically.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity (two-call handshake, degree restrictions, dry-run mode), the description covers all essential aspects an agent needs to call it correctly: return of previewToken, refusal with not_connected, daily cap effect, and dry-run behavior. Without an output schema, it adequately explains the expected response shape.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With 0% schema description coverage, the description carries the burden of explaining parameters. It explains profileUrlOrConversationId (URL/slug or thread ID), confirm (triggers the send in the second call), and previewToken (returned from the first call). It does not explicitly describe the text parameter, but its meaning is obvious as the message body. This nearly compensates for the lack of schema descriptions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: 'Sends one direct message' and specifies the two distinct target types (1st-degree connection via profile URL/slug, or an existing conversation via thread ID). This unambiguously distinguishes it from siblings like linkedin_create_post or linkedin_send_connection_request.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides explicit usage conditions: only 1st-degree connections, refusal with not_connected, and the two-call handshake required for sending. It explains when the tool will refuse and how confirmation works, though it does not explicitly name an alternative tool for other messaging scenarios. Still, the context is strong enough for an agent to decide when to use it.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

linkedin_session_statusCheck the saved LinkedIn sessionA
Read-onlyIdempotent

Reports whether the locally saved LinkedIn session still works, when it was saved, and when it was last verified. Loads the feed once to check; no cookie or session content is ever returned. Under --dry-run this always reports valid. Takes no arguments.

NameRequiredDescriptionDefault

No parameters

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the readOnlyHint annotation, the description reveals that it 'loads the feed once to check', which implies a network call despite being read-only. It also assures that 'no cookie or session content is ever returned' and clarifies the --dry-run behavior. These details give the agent important expectations beyond annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two sentences with no waste. The core purpose is front-loaded, followed by behavioral notes and the no-arguments statement. Every clause adds necessary information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a zero-parameter, no-output-schema tool, the description fully covers what the tool does, how it checks (loads feed), what it returns (status, save time, verification time), and edge case behavior (dry-run). Nothing an agent needs to invoke it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool has zero parameters, and the description explicitly states 'Takes no arguments.' This meets the baseline for no-parameter tools, and the statement removes any ambiguity, so a 4 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb ('reports') and resource ('saved LinkedIn session'), and specifies the exact outputs: whether it still works, when saved, last verified. This clearly differentiates it from action-oriented siblings like linkedin_login or linkedin_create_post.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description makes it clear this is a status check tool by stating what it reports. While no explicit alternatives or when-not-to-use are mentioned, the context of sibling tool names (all performing actions) makes the intended usage obvious. A slight deduction for not explicitly saying 'use before other LinkedIn operations'.

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.

  1. 11 tool updatesv0.1.0
    • First observedlinkedin_apply_to_job
    • First observedlinkedin_create_post
    • First observedlinkedin_list_connections
    • First observedlinkedin_list_pending_invites
    • First observedlinkedin_login
    • First observedlinkedin_scrape_feed
    • First observedlinkedin_scrape_profile
    • First observedlinkedin_search_jobs
    • First observedlinkedin_send_connection_request
    • First observedlinkedin_send_message
    • First observedlinkedin_session_status

TDQS

A4.7/5.0

Scored across 11 tools

Disambiguation5/5

Each tool targets a distinct LinkedIn action (login, session check, create post, send connection, send message, scrape profile, scrape feed, search jobs, apply to job, list invites, list connections). Even similar functions like scraping profile vs. sending connection request are clearly separated by purpose, with no two tools overlapping in behavior.

Naming Consistency5/5

All tools follow the 'linkedin_' prefix plus a snake_case verb_noun pattern (e.g., linkedin_create_post, linkedin_list_connections). Even the outlier 'linkedin_session_status' uses a consistent noun-like phrase, but the naming is uniform and predictable across the entire set.

Tool Count5/5

With 11 tools, the server covers a broad set of LinkedIn capabilities without being bloated. Each tool serves a clear, distinct purpose, from authentication and session management to content creation, messaging, scraping, job search, and application. The count is well within the ideal 3-15 range and feels appropriately scoped for a LinkedIn assistant.

Completeness4/5

The surface covers core workflows: posting, messaging, connection requests, profile/feed reading, job search, and Easy Apply. Minor gaps exist such as accepting/rejecting invitations, editing/deleting posts, or reacting to content, but the available tools handle the most common personal LinkedIn tasks without dead ends.

Related MCP Connectors

Related MCP Servers