Skip to main content
Glama
Sundeepg98

linkedin-mcp

by Sundeepg98

linkedin

자신의 LinkedIn 계정 데이터를 페이지를 하나씩 클릭해 살펴봐야 하는 대신 구조화된 도구 결과로 보여 주는 MCP 서버입니다.

열일곱 개 도구 중 열네 개는 읽기만 하며 아무것도 바꾸지 않습니다. 세 개는 쓰기 작업입니다.

2026-08-23까지는 이 문단에 *“읽기만 합니다. 그것이 전부입니다. 이 저장소에는 쓰기 경로가 없습니다. — 비활성화된 것도, 스텁(stub)인 것도, 플래그 뒤에 숨겨진 것도 아니었습니다.”*라고 쓰여 있었습니다. 그 말은 사실이었고, 단순한 주장이 아니라 실제로 강제되어 있었습니다. 그러다 linkedin_save_job이 도구로 공개되면서 그 말은 더 이상 사실이 아니게 되었습니다. 편한 문장을 그대로 담은 README는 독자가 가장 먼저 신뢰하는 대상이면서 동시에 가장 먼저 오해를 만드는 대상이 됩니다.

지금 사실은 다음과 같습니다.

  • 이 패키지에는 LinkedIn에서 무엇이든 바꿀 수 있는 호출이 정확히 하나 들어 있습니다. writes.perform 안의 단일한 지점(anchored click)입니다. 소스 스캐너는 여전히 그 호출을 보고합니다 — 살펴보는 것을 멈추도록 가르친 적이 없습니다. 그리고 그 호출은 경로(path), 함수(function), 종류(kind)가 허용 목록 한 줄에 명시되어 있으며, 그 허용 목록이 넓어지면 테스트가 실패합니다.

  • 쓰기는 직접 켜지 않는 한 꺼져 있습니다. 프로세스마다 LINKEDIN_ENABLE_WRITES=1이 필요합니다. 새로 복제한 저장소는 아예 LinkedIn에 쓰을 수 없습니다.

  • 모든 쓰기는 호출이 두 번입니다. 첫 번째 호출은 아무것도 수행하지 않으면서 읽을 블록을 건네주고, 두 번째 호출에서 그 블록의 일회용 토큰을 사용합니다. 이 토큰은 어떤 대상에 대한 하나의 작업 하나에만 묶여 있으며 120초 안에 소멸합니다. 따라서 쓰기를 예약하거나무인 상태로 실행하는 것은 권장되지 않는 차원을 넘어 구조적으로 불가능합니다.

  • linkedin_unsave_job은 구현되어 있고 게이트가 걸려 있으며 작동을 거부합니다. 거부하는 그 도구를 참고하십시오.

  • 지원서를 제출하지 않습니다. 그리고 이것은 어깨를 으쓱하는 회피가 아닙니다. linkedin_job_detail은 apply_path를 보고합니다. 즉 어떤 채용 공고가 LinkedIn 안에서 지원을 받는지, 아니면 외부의 지원자 추적(applicant-tracking) 시스템으로 넘겨보는지를 나타내며 그 시스템의 이름도 알려 줍니다. 식별과 안내에 해당하는 절반은 읽기 기능으로 제공됩니다. 제출을 담당하는 절반은 분명한 근거를 가지고 distanced refused합니다. — Applicat: 제공되는 절반과 그 절반은 제공되지 않은 것을 참고하십시오.


무엇보다 먼저 이 부분을 읽으십시오

LinkedIn 사용자 약관은 사이트에 대한 자동화된 접근를 제한합니다. 이 서버가 어떤 방식으로 만들어졌든지 간에 이것은 사실이며, 아래 어떤 내용도 이 사실을 바꾸지 않습니다.

의도된 것이다 때문에 "없는 일"이라 여기지 않고 노출을 최소화하는 것이 이 디자인의 방식입니다.

선택

위험을 낮추는 이유

사람이 직접 지시하는 경우에만

모든 호출은 그 순간 당신이 직접 요청한 것입니다. 타이머로 실행되는 것은 없고, 잠자는 동안 실행되는 수도 없습니다.

한 번에 하나의 작업

도구 호출마다 페이지 로드는 한 번뿐입니다. 무한 스크롤 반복, 자동 페이지, 다중 실행(fan-out)이 없습니다.

당신 만의 세션, 당신 만의 기기, 당신 만의 IP

어떤 쿠키도 제3자에게 전달되지 않습니다. 프록시도, 데이터센터 IP도, 헤드리스 팜(headless farm)도 없습니다.

평범한 브라우저, 플래그 하나

스텔스 플러그인, user-agent나 플랫폼의 위장, 핑거프린트 변조, 프록시, 사람을 흉내내기 위한 타이밍 조작이 전혀 없습니다. 단 하나의 Chromium 플래그만 전달됩니다 — --disable-blink-features=AutomationControlled는 Blink가 navigator.webdriver를 설정하지 못하게 합니다. LinkedIn은 로그인 시 이 값을 확인하므로 없으면 자동화된 브라우저 사용자를 거부합니다. 이것이 전부입니다. 이 플래그는 실행 시 readonly.assert_launch_flags_permitted에 의해 강제되며, 세 번째 플래그가 추가되면 tests/test_launch_boundary.py가 빌드를 실패로 처리합니다.

당신의 데이터만

당신의 프로필 열람, 당신의 입사지원, 당신이 저장한 채용공고, 당신의 프로필, 당신의 알림만 다룹니다. 다른 회원의 데이터를 열거하거나 수집하는 일은 없습니다.

세 가지 명시적 쓰기를 제외한 읽기 전용

아무것도 지원하거나, 보내거나, 게시하거나, 추천하거나, 초대하거나, 편집하지 않습니다. 저장, 저장 취소, 언팔로우가 예외입니다. 이 작업들은 기본적으로 꺼져 있고, 한 번에 하나씩만 이루어지며, 활성 상태의 읽기로 만든 블록을 당신이 직접 보고 확인한 뒤 수행됩니다. 해당 토큰은 한 번만 유효하고 2분이 지나면 소멸합니다. 이 행은 2026-08-23까지 “읽기 전용”이라고 표기되어 있었으며, 그 문장은 조용히 넓어진 것이 아니라 바로 그 사실을 반영하도록 수정되었습니다.

이것은 노출을 낮춥니다. 없애주지는 않습니다. 자동화된 접근은 여전히 속도 제한(rate limit), 챌린지, 또는 계정 제재로 이어질 수 있으며, 그 반드는는 당신이 부담해야 하는 리스크입니다. 서버를 등록하기 전에 의도적으로 그 선택을 내리십시오.

이것은 탐지 방지 도구가 아닙니다 — 그리고 여기 장담이 아니라 증거가 있습니다

앞에서 말한 그 Chromium 플래그 하나 때문에 이 저장소가 회피용(evasion) 프로젝트처럼 보일 수 있습니다. 여기서 측정된 사실을 명확히 하는 것이 중요한데, 주장은 검증이 가능하고 독자가 그냥 어조만 믿지 않도록 하기 위한 것입니다.

2026-08-24에 추적 중인 모든 파일 105개를 감사했습니다.

  • 핑거프린트 변형: 0건. user-agent, platform, locale, timezone, geolocation, viewport, device-scale, WebGL, canvas 패칭이 전혀 없습니다. page.route 개입도 없고, 려ют 초기 코드 주입도, 추가 헤더도, 프록시도 없습니다. 각 항목은 패키지 전체에서 이름별로 검색했으며 모두 호출 0건을 돌려보았습니다. 직접 grep으로 확인하면 수 있는 독자가 혼동하지 않도록 한 가지만 말하자면 add_init_script는 readonly.py에 두 번 나타나는데, 둘 다 그러한 호출을 탐지하기 위한 스캐너eigen pattern입니다. 스캐너는 자기가 금지하는 것을 이름으로 검열하며, 그래서 스캐너 소스에는 그런 코드 조각이 존재하고 나머지 패키지에는 없는 것입니다. partition_mutation_hits는 그 사실을 독립적으로 확인합니다. 허용된 변경 호출 하나, 허용되지 않은 변경 호출은 0건입니다.

  • 스텔스 의존성이 없습니다. 의존성은 네 개이고 어느 것도 탐지 방지 라이브러리가 아닙니다. readonly.scan_source_for_evasion은 패키지 전체에서 일치 0건, 즉 테스트에 일부러 심어둔 대조군 하나를 제외하면 어디서도 나타나지 않습니다.

  • 타이밍은 고정되어 있고 인간처럼 조작되지 않습니다. 모든 지연은 상수입니다. import random은 0번 나타납니다. 페이지 로드 사이의 3초 간격은 MIN_INTERVAL - elapsed이며 그만큼 정확히 대기합니다 — 기계처럼 일정합니다. 무작위 지터(randomised jitter)는 사람 행동을 퉁 내는 도구가 쓰는 방식이고, 고정된 간격은 스로틀링(throttling)입니다.

  • 플래그 그 자체는 선의가 아니라 게이트에 의해 통제됩니다. readonly.assert_launch_flags_permitted는 모든 실행에서 검사하며, 만약 세 번째 플래그까 들어오면 tests/test_launch_boundary.py가 빌드를 실패시킵니다.

그 플래그는 Blink가 navigator.webdriver를 설정하지 못하게 만들며, LinkedIn은 로그인 시 이 패치를 확인합니다. 따라서 그 값이 진실이면 아바운 자동 브라우저가 실제 계정 소유자에게조차 사용 불능 인된다는 뜻입니다. 그것이 전부입니다. 자동화된 브라우저를 작동시키는 것과 탐지를 끌피하는 것은 서로 다른 활동이며, 여기 있는 것은 첫 번째뿐입니다.

라이선스도 그로부터 정해집니다. 그리고 일부러 거슈에 엄격합니다

이 저장소는 독점(proprietary) material입니다. 모든 권리가 보유됩니다. 참고용으로 제공되며, 사용, 복제, 수정 또는 배포에 관한 어떤 권한도 부여하지 않습니다.

이것은 실수나 임시 표기가 아닙니다. 이 서버는 자동화를 금지하는 사용자 약관 아래에서 인증된 LinkedIn 세션을 구동합니다. permissive한 라이선스는 이 서버를 자기 계정 — 또는 남의 계정 — 에 돌리도록 낯선에게 뒀고, 그 방법을 알려준 저장소 위에 저자의 이름이 남게 됩니다.

이것은 포트폴리오 산출물입니다. 배포 대상이 아니라 읽히기 위한 것유입니다. 설계, 경계 설정, 게이트와 감사 내역을 읽으십시오. 이것이 바로 이 저장소의 질적입니다.


Related MCP server: LinkedIn MCP Server

할 수 있는 무엇인가

도구

읽는 내용

linkedin_who_viewed_me

프로필을 조회한 사람이 누구인지 알려 줍니다. 계정에 Premium Career가 있으면 조회 기록이 365일까지 돌아갑니다. 구직 활동에서 가장 의도가 분명한 신호입니다.

linkedin_my_applications

지원한 채용공고와 LinkedIn이 표시하는 상태를 보여줍니다.

linkedin_saved_jobs

북마크한 채용공고입니다.

linkedin_search_jobs

키워드, 지역, 원격 근무, 게시일, 경력 수준으로 채용공고를 검색합니다.

linkedin_job_detail

채용공고 한 건 전체를 보여줍니다. 급여 범위, LinkedIn의 지원자 수, 근무 방식과 고용 형태, 채용 상태, 그리고 설명까지 포함합니다. 이 중 어떤 것도 검색 결과나 저장된-job 카드에는 나오지 않습니다. 또한 apply_path: 이 공고가 두 지원 경로 중 어떤 것을 쓰는지, 그리고 사외(off-site) 경로라면 어느 기업의 지원자 추적 시스템(ATS)으로 보내지는지 알려줍니다.

linkedin_followed_companies

팔로우하고 있는 기업 페이지를 숫자 id와 함께 보여줍니다. 그 id가 linkedin_unfollow_company가 참조하는 대상입니다. LinkedIn은 팔로우하는 기업이 얼마든 약 20행만 렌더링하고 그 나머지를 페이지로 넘길 수 없기 때문에, 이 도구는 전체를 다뤘다고 단정하지 않고 실제로 살펴본 범위만 보고합니다.

linkedin_my_profile

내 프로필 자체입니다. 소개, 프로필 문구, 기술(skills)와 어떤 섹션이렌더링을 보여줍니다. LinkedIn은 페이지를 스크롤하기 전까지 Experience/Education/Skills를 제공하지 않으므로 이 항목들은 0이 아닌 UNKNOWN으로 읽힙니다.

linkedin_notifications

알림 목록입니다.

linkedin_auth_status

인증된 요청으로 확인하는, 라이브 세션이 있는지의 여부입니다.

linkedin_login_browser

직접 로그인할 수 있는 브라우저 창을 엽니다.

linkedin_session_info

세션이 살아 있는지와 언제 만료되는지를 브라우저의 쿠키 보관함에서 바로 읽습니다. 크레덴셜(credential), 크레덴셜을 뒷받침하는 csrf 쿠키, 지속성, 그리고 왜 여기에는 조용한(silent) 재인증이 없는지 보고합니다. renewal.session_lapses_at은 그 시점이 지나면 어떤 재인증도 도움이 안 때문에 직접 로그인해야 하는 날짜입니다. 여러 서버에서 무엇을 비교할 때 쓰는 필드이며, LinkedIn에서는 이 곳에서 세션을 그 다음 시점까지 이어 주지 못하므로 쿠키의 자체 만료일과 같습니다.

linkedin_logout

이 기기의 쿠키 보관함을 지워 로컬 로그인을 종료합니다. 이 서버에서 유일하게 파괴적인 도구입니다. confirm=False(기본값)는 아무것도 하지 않고, 실행할 내용을 미리 보여만 줍니다. 요청을 발행하지 않으므로 LinkedIn에 알릴 일이 없습니다.

linkedin_cdp_status

복구 진단 도구입니다. 이 서버가 붙을 수 있는 Chrome이 있는지 확인합니다. LinkedIn에는 어떤 것도 건들지 않습니다.

linkedin_server_info

소스 코드를 읽지 않고도 서버의 경계(boundary), 속도 제한 설정, 실행 플래그를 알려줍니다.

쓰기(to write) 도구 세 개

도구

하는 일

linkedin_save_job

공고 하나를 북마크합니다. confirm_token 없이 호출하면 아무것도 실행하지 않습니다. 그 대신, 지원 공고와 저장 목록을 현재 라이브로 읽고, 공명과 회사의 원본 제목과 회사 이름으로 공고를 식별하고, 토글이 어느 방향으로 움직일지, 각 정보가 어떤 근거에서 나왔는지, 그리고 되돌리는 방법이 담긴 블록을 반환합니다. 그 블록의 토큰으로 다시 호출하면 실제로 실시합니다.

linkedin_unsave_job

형태도 같고, 안전장치도 갖고 있지만 거부합니다. 아래를 참조하세요.

linkedin_unfollow_company

기업 하나 팔로우를 중단합니다. 형태와 다섯 개의 안전장치 모두 동일합니다. 대상은 숫자 company id로만 지정하며, 이름으로는 하지 않습니다. 이름은 겹치고 바뀐 수 있고 신뢰할 수 있는 것이 아니며, 클릭은 id가 있는 행에 박혀 있으므로, 이름으로 지명한 것과 실제로 눌리는 행이 구조적으로 항상 같은 행이라는 것입니다.

클릭 후의 결과는 방금 누른 그 버튼이 아니라 다른 면에서 확인됩니다. LinkedIn이 탭별로 보여주는 카운트가 있는, 여러분의 저장 목록에서 말입니다. performed는 true, false, 또는 "unknown"으로 돌아옵니다. "unknown"이 나오면 재시도하지 마세요. 실제로 이미 토글이 적용된 상태에서 재시도하면 반대 동작을 수행하게 됩니다.

거부하는 도구

LinkedIn는이 저장 컨트롤를 접근 가능한 이름(accessible name)으로 식별합니다. 이 리포지토리가 가진 모든 캡처 — 공고 4개, 두 가지 hydration 상태, 서로다른 날짜 2일 — 가 전부 aria-label="Save the job" 즉 저장되지 않은 상태를 보여 줍니다. 그 대가 공고가 저장된 상태에서 붙게 되는 이름은 아이번에 관측하지 못했습니다. 그리고 관측할 방법도 없습니다: 관찰할 대상으로 계정에 이미 저장된 것이 없기 때문입니다.

그래서 linkedin_unsave_job에는 기준선(anchor)이 없고, 이 서버는 그걸 야래with야 하지 않습니다. "Saved"와 "Unsave the job" 모두 그럴듯하며, 어느 한쪽도 보지 못했습니다. 거부를 하는 이유다 “안 만들었다”가 아닌 그 이유를 알려줍니다. "이미 구현되지 않았다"라고 하면, 어떤 사람이 아무 문자열이나 골라서 구현해 버릴 수 있게 되기 때문입니다.

해결팁은 측정으로 나온 정확한 한 줄입니다. 처음으로 저장(supervized)이 실행하면 그것을 만듭니다. perform 그 컨트롤이 막바뀐 라벨을 읽어내고 보고합니다. 그 값을 shape.SAVE_LABELS에 기록해 넣으면 unsave_job은 앵커를 얻게 됩니다. 이것은 표에서 한 칸의 문제이지, 빠진 코드 경로가 아닙니다.

지원하기: 동작하는 절반과, 동작하지 않는 절반

linkedin_job_detail은 공고가 어떻게 지원되는지 알려줍니다. LinkedIn은 지원 컨트롤을 링크(버튼이 아니라 링크)로 그리기 때문에 건드리지 않고도 목적지가 보이며, apply_path는 세 가지 답 중 하나를 보고합니다:

  • linkedin_apply — 지원 접수가 LinkedIn 안에서 완성되고 제출됩니다.

  • offsite — 여러분을 기업 자체의 지원자 trace 시스템( ATS)으로 건네줍니다. 목적지는 LinkedIn의 외부 전달 래퍼에서 문자열만으로: 리디렉션을 따라가지도, 제3자 호스트와 접속하지도 않습니다. host만 알아도, 어느 기업의 from을 작성해야 하는 알게 됩니다.

  • unknown — 좋아요. 그런 답을 아니. 이것은 실재하는 팍스이자 중요한 답입니다. 아래 See.

그것이 유용한 절반입니다. 추가 페이지 로드는 게 하나 없으며 순수 조회입니다.

제출은 하지 않습니다. 서버가 지원하기를 요구하지 않아서가 아니라, 실제로 측정된 이유가 있습니다:| 도구 | 읽는 내용 | | --- | --- | | linkedin_who_viewed_me | 프로필을 조회한 사람. 계정에 Premium Career가 있다면 이력이 365일까지 소급됩니다 — 구직 활동에서 의도가 가장 뚜렷한 신호입니다. | | linkedin_my_applications | 지원한 채용공고와 LinkedIn이 표시하는 상태. | | linkedin_saved_jobs | 북마크한 채용공고. | | linkedin_search_jobs | 키워드, 지역, 원격 근무, 게시일, 경력 수준으로 채용공고를 검색합니다. | | linkedin_job_detail | 채용공고 하나 전체를 보여줍니다 — 급여 범위, LinkedIn의 지원자 수, 근무지 및 고용 형태, 채용 상태, 설명까지. 이 중 어느 것도 검색 결과나 저장 공고 카드에는 없습니다. 또한 apply_path: 이 공고가 두 가지 지원 경로 중 어느 쪽을 쓰는지, 외부(off-site) 경로일 때는 어느 기업의 지원자 추적 시스템으로 보내지는지 알려줍니다. | | linkedin_followed_companies | 팔로우하는 기업 페이지와 각각의 숫자 id — 이 id가 바로 linkedin_unfollow_company가 참조하는 값입니다. LinkedIn은 팔로우한 기업이 아무리 많아도 약 20행만 렌더링할 뿐, 그 나머지를 페이지로 넘기는 방법이 없습니다. 그래서 이 도구는 “전부 덮었다”가 아니라 실제로 확인한 범위만 보고합니다. | | linkedin_my_profile | 내 프로필 자체: 헤드라인, 소개, 스킬, 그리고 어떤 섹션이 렌더링되었는지. LinkedIn은 스크롤하기 전까지 Experience/Education/Skills를 미뤄두기 때문에, 이 항목들은 0이 아니라 UNKNOWN으로 읽힙니다. | | linkedin_notifications | 알림 목록. | | linkedin_auth_status | 라이브 세션이 있는지 — 인증 요청으로 측정한 결과. | | linkedin_login_browser | 직접 로그인할 수 있는 창을 열어 줍니다. | | linkedin_session_info | 세션이 활성 상태인지와 언제 만료되는지를 브라우저 자체의 쿠키 보관함에서 읽어 옵니다. 세션을 뒷받치는 자격 증명(credential), 그것을 지지하는 csrf 쿠키, 지속성, 그리고 왜 여기에 무성(자동) 재인증이 없는지 보고합니다. renewal.session_lapses_at은 갱신이 더 이상 도움이 되지 않는 날짜, 즉 손으로 직접 로그인해야 하는 시점입니다. 서버 간에 비교하기 위한 필드이며, LinkedIn에서는 아무것도 이 시점을 넘겨 세션을 이어줄 수 없으므로 쿠키 자체의 만료 시각과 같습니다. | | lookup | 이 기기의 쿠키 보관함을 지워 로컬 로그인을 끝냅니다. 여기서 유일하게 파괴적인 도구입니다: confirm=False(기본값)는 아무것도 실행하지 않고 무엇이 일어날지를 미리만 보여줍니다. 요청을 하나도 발행하지 않으므로 LinkedIn에는 알지 못합니다. | | linkedin_cdp_status | 복구 진단합니다: 이 서버에 붙을 수 있는 Chrome이 있는가. LinkedIn에는 무엇도 건드리지 않는다. | | linkedin_server_info | 소스 코드를 읽지 않고, 경계(boundary), 속도 설정, 실행 플래그를 알려줍니다. |

쓰기 도구 세 가지

도구

하는 일

linkedin_save_job

하나를 저장합니다. confirm_token 없이 호출하면 아무것도 하지 않습니다: 공고와 저장 목록을 라이브로 읽고, 제목과 기업 이름으로 공고를 특정하는 블록을 대신 반환합니다. 그 블록에는 토글이 어느 방향이 이동될지, 각 정보의 출처, 그리고 되돌리는 방법이 있습니다. 그 블록에 담긴 토큰으로 다시 호출하면 실제로 작동합니다.

linkedin_unsave_job

형태도 같고, 게이트도 같지만 거부합니다. 아래 참고.

linkedin_unfollow_company

특정 기업 차단의 팔로우를 중단합니다. 형태와 5개의 안전장치가 동일합니다. 지칭하는 대상은 숫자 기업 id이지 이름이 아닙니다. 이름은 충돌하고, 바뀌고, 여러분이 신뢰에 의지할 수 없는 것이기 때문입니다. 그리고 클릭은 id가 있는 행에도 부여되어 있으므로, 이름으로 지시한 것과 실제 눌리는 행이 구조상 항상 같은 것입니다.

클릭 후 결과는 방금 눌린 버튼이 아니라 다른 것으로에서 확인합니다 — LinkedIn이 탭별로 추산해 주는 내 저장 목록에서 말입니다. performed는 true, false, "unknown" 중 하나로 돌아옵니다. "unknown"이라면 재시도하지 마십시오: 이미 실제로 켜진 토글에 재시도하면 반대 동작을 하게 됩니다.

거부하는 하나

LinkedIn은 저장 컨트롤을 접근 가능한 이름(accessible name)으로 식별합니다. 이 저장소가 가진 모든 캡처 — 공고 4건, 두 가지 로딩 상태, 다른 날짜 이틀 — 는 흔히 aria-label="Save the job", 즉 저장되지 않음 상태를 보여 줍니다. 공고가 저장된 상태에서 이 컨트롤이 갖는 이름은 한 번도 목격된 적이 없고, 읽기만으로는 관측할 수도 없습니다: 계정에 저장된 것이 하나도 없어 그것을 관측할 대상이 없기 때문입니다.

그래서 linkedin_unsave_job에는 그리에 붙을 수 있는 앵커가 없고, 이 서버는 어느 것으로 잘 못 변명하지 않습니다. "Saved"와 "Unsave the job" 둘 다 그럴듯하지만 어느 쪽도 sighted 없습니다. 거부는 “아직 이렇게 되지 않았습니다”가 아니라 그 이유를 명시합니다. “아직 미지원”이라는 이유는 어떤 문자열을 골라 “지원해 보겠다”라고 하는 사람을 부를 수 있기 때문입니다.

해결책은 실제로 측정된 한 줄입니다. 첫 번의 supervised save가 그것을 만들어 줍니다: perform이 컨트롤이 바뀐 라벨을 읽어서 reporting 합니다. 그 값을 shape.SAVE_LABELS에 적어 넣으며 unsave_job은 그 때의 앵커를 얻게 됩니다. unsave_job은 빠진 코드 경로가 하나의 표의 행입니다.

지원하기: 동작하는 절반, 그리고 동작하지 않는 절반

linkedin_job_detail은 공고가 어떻게 지원되는지 알려 줍니다. LinkedIn은 지원 객체를 버튼이 아니라 링크로 그리므로 클릭하지 않고도 목적지가 보이며, apply_path는 세 가지 답 중 하나를 보고합니다:

  • linkedin_apply — 지원이 LinkedIn 내에서 완성되어 제출됩니다.

  • offsite — 고용주의 자체 지원자 추적 시스템으로 건네 줍니다. 목적지는 LinkedIn의 전송용 래퍼에서 문자열만으로 해독됩니다: 리다렉트를 따라가지 않아 제삼자 호스트에 접속하지 않습니다. 그 호스트를 얻이라는 것은 — 누구의 양식을 whether 들어갈지의.

  • unknown — 아무것도 말하지 않습니다. 이 경우 진짜 답이며 중요한 것입니다. 아래를 참조하세요.

그게 유용한 절반이며, 페이지를 추가로 로드하지 않고, 일방적으로 읽기입니다.

제출은 하지 않습니다. 지원 도구가 이 서버가 맡는 범위에 없는 것이 아니라, 실제로 측정된 이유가 있습니다:

  1. 지원(apply) 플로우는 지금까지 한 번도 캡처된 적이 없다. 열세 건의 job 캡처 어디에도 form은 0개, file input은 0개, 다이얼로그는 0개, screening 질문은 0개, 무엇이든 제출하는 control도 0개다. 여기 있는 그 어떤 것도 무엇이 채워지고 무엇이 눌리는지 본 적이 없다. 이것은 unsave_job에 적용되는 것과 동일한 기준이며, 그 기준이 가장 마땅히 적용되어야 할 액션에 그대로 적용한 것이다.

  2. 지원(application)은 이곳에서 되돌릴 수 없다. 어떤 confirm 단계에서든, 어떤 상황에서든. 지원 철회는 영구적으로 금지되어 있다.

  3. 오프사이트(off-site)에 해당하는 절반은 이 서버의 몫이 전혀 아니다. 캡처가 아무리 좋게 이루어졌어도, 남의 도메인에 놓여 있고 남의 조건으로 움직이는 남의 폼을 조종하는 것은 서로 다른 소프트웨어에 속한 일이다.

apply_job은 그런 이유로 writes.py에서 완전하게 스펙으로 정의되고 게이트되어 있으며, 도구 등록도 url 보유도 하지 않는다. 따라서 이에 대한 grant는 사용 시점이 아니라 발급(issue) 시점에 거부된다.

그리고 그 빈틈(gap)에는 주소가 있다. 그 사실이 그 빈틈을 '영구 결함'이 아니라 **'측정되지 않은 것'**으로 남게 한다. linkedin_server 의 scripts/_probe_apply_flow.py는 LinkedIn이 호스트하는 플로우를 캡처하고 기존 캡처마다 빠져 있는 그 바로 그 컨트롤들 — form, file input, 다이얼로그, screening 질문, 제출하는 컨트롤 — 을 정확하게 인벤토리로 만든다. 이 플로우에 도달하는 방식은 클릭이 아니라 내비게이션이다(LinkedIn은 apply 컨트롤을 링크로 그린다). 그리고 이 패키지 자체의 mutation 스캐너는 이 스크립트에서 변이 호출이 0개임을 발견하며, job id는필수 인자로 받아서 어떤 기본값도 사용자 대신 어떤 채용 공고를 골라 주는 일을 없다. 왜냐하면 Easy Apply 평가 시 문서 초안(draft)이 생성될 수 있기 때문이다 — 이는 가설이며, 아직 어무도이 검증하지 못했고, 그렇게 라벨되어 있고 가정이 아니라 측정된 것으로 취급합니다.

그것은 실행되지 않았다. 누군가 지켜보는 누군가 있는 상태에서, apply_path가 linkedin_apply인 채용 공고를 대상으로 실행하십시오.

분류기가 왜 여러 필드가 일치하는 것을 요구하는가, 그것 하나로 분명하고 보이는 유일한 필드가 있을 때도. 각각의 후보를 측정했고, 각 후보만으로는 실패한다. data-view-name="job-apply-button"은 열세 개중 한 개의 캡처에서만 존재하고, 온전하게 하이드레이션된 오프사이트 게시면에는 아예 없기 때문에, 이 필드가 없다는 것은 전혀 정보를 갖게 하지 않는다. 나가는 래퍼(outbound wrapper)는 범용적이다 — 어느 한 캡처에는 그런 것이 둘 있으며 단 하나 어기만 apply 컨트롤이다. 접근 가능한 이름(accessible name)은 가장 강력한 단일 필드이면서 동시에 LinkedIn이 이미 바꾸어 버린 필드다. "Easy Apply"라는 문자열은 접근 가능한 이름에서는 0번 나타나고, 같은 페이지의 본문 산문에는 2번 나타나므로, 그 어떤 기능을 모두가 아는 이름으로 인덱스하는 파서는 아무것도 매치할 수 없다. 그리고 하이드레이션 전(payload) 페이로드는 무용지물보다 나쁘-- 오프사이트 채용 공고가 같은 job id로 온사이트 플로우의 자체적인 표시(marker)를 운반하고 있음이 측정되었는데,LinkedIn이 apply 상태 풀 세트 스테이트 머신을채용 게시물별 템플릿으로 제공하기 때문이다.

의도적으로 할 수 없는 것

메시징, InMail, 연결 초대. 프로필 편집, Open To Work, 회사를 팔로우하기, 게시, 좋아요, 댓글, 기능 추천. 알림 읽음 표시. 다른 멤버 데이터 수집. 애플리케이션 제출 — 위의 section에서 설명한 대로.

이것들은 '빠누락 기능'이 아니며, 모든 것이 같은 종류의 "아니요"인 것도 아니다. linkedin_server_info는 각 항목에 POLICY, MEASURED 또는 UNMEASURED로 라벨을 붙이다. 왜나하면 "원칙적으로 거부합니다", 이유로는 "진행하고 살펴보니 동작하지 않는다", 그리고 "어무도 살펴보지 않았다"는 세 가지 서로 다른 진술이고, 그것들을 평면로 눌러섞은 하나의 목록으로 만들면 진단되지 않은 빈 틈까지도 설계 결정처럼 읽힐 수 있기 때문이다.

**팔로우가 흥미로운 그 하나다. ** 그 '할 수 없음'에 사유는 2026-08-24에 바뀌었지만, 결판 자체는 안 바뀌었다. 이전에는 어차피(별 코드) 존재가 없기 때문에 차단되어 있었다 — 이 서버는 자신이 소거하지 못할 상태를 만들 수 있었다. 지금은 unfollow가 있다. 하지만 여전히 실행 불가능한 이유는 되돌리기(undo가) 대상을 겨누어 정할 수 없기 때문에: 게시 기업공고의 공고 쪽 employer는 slug로 표시하고, unfollow 화면unfollow는 행을 숫자company id로 주소를 삼으며, 이 repo에는 두 action 어느 쪽이 가 관한 surface에서 그러한 두 값을 한 회다. 이 가지고 있는 캡처가 없는 것이 없다. 그 화면은 같은 list의 전체에는 58개중 대략 20개 행을 페이지네이션 없이 보여 평가는, 한 번의 페이지 로드로 대부분의 목록은 도달 불가능이다. 그 거부 사유은 당시 두 가지를 다 밝히면서 그걸 어떻게 바라보면 그것을 해제할 수 있는지이를 밝힌다.

자신의 받은 편지함을 읽는 것은 REFUSE(거부)가 아니라 UNMEASURED(측정 안 됨) 다. 읽기 경계boundary은 /messaging 차단하고, 그 차단을 문서로 만드는 모든 사유가 왜 그게 차단된 이유도 '보내기(sending)'을 사실로 진술되어 있다. 읽기가 자체가 가능이라도 된 적이 없는지도 테스트된 적이 없다. scripts/_probe_messaging.py은 이를 테스트하기 위해 존재하여, 다음 질문에서 여기서 보통 지나가는 어떤 것을 아울러 테스트한다. 이 가설은 — 지금까지 아무도 이것을 검증하지 않고, nig의 그 포인트는 —LinkedIn의 데스크톱 메시징 화면이 도착 즉시 대화 하나를 열어버리기 때문에 때문에, 받은 편지함에 대한 "읽기"가 대화 스레드 하나에 join중 "읽음" 상태를 만들어버리며, 그것은 알림에 관한 알림에 관한 앞선 objection을 "읽기이라고 뜨는 함태인 도구 그것이 될 것이다. 그 프로브는 내비 배지를 /feed/feed 출구에서 before /after을 두 번 읽어 그를 측정한다. load가 그 surface 화면 was not touch the surface를 돌지 않다. 아직 실행은 되지 않았다, 그리고 그 금지는 것이 그것이 될 때전까지 조금도 변하지 않는다: 경계(boundary)는 측정되지 않은 진술에 따라 움직이지 않는 것이다.

LinkedIn의 어떠한 것이라도 서버에 무엇서 변경하는 그 밖의 모든 것은 scope 밖이며, tests/test_readonly.py 는 모 등의 두 번째 불기 같은mutating call이 어떤 패키지 어디에 나타나면 빌드를 실패로 만든다.

한 가지 도구는 이 머신 위에 상태를 변경엄을 준다: linkedin_logout(confirm=True) 그 로컬 cookie jar를 지운다.한 건의 요청 커녕 발행되지 않으므로 it 없어 LinkedIn에 알려지지 않으며, linkedin_server_info는 이것을 변환 read_only 필드에 감추어놓는 대신 local_state_writes로 목에 있다.

그 부작용들: 숨겨서가 아닌, 밝힐 수 있는것

무엇인가를 바꾸는 읽기라면 그래서 언급을 해야 할 것:

  1. Notifications 페이지를 여는 것은 LinkedIn의 unread 배지를 사라지게 한다. — 페이지를 사용자 스스로가스를 새로인 것과 정확히 똑같이. 이론이 아니라 측정된 값이다; 하나의 호출(2026-08-21)에서 만들지가 1에 0으로 갔고 그렇게 다시 돌아오지 않았다. 회피 불가능한다. 페이지가 제공될 때 LinkedIn가 이 목록과 보았다고 한 서버에 표시하므로, 이 surface가를 걷는 모든 것은 배지를 그대로 남기는 우회급이 전혀 없다. 그리고 여기서 어떤 클릭이나 스크롤 혹은 항목별 열기도 들어있지도 않고, 패키지 어디에도 مما "mark-as-read" caller 가 없다. 배지를 지워지지 않게 위한 유일한 방법은 linkedin_notifications을 호출하지 않는 것이다. 필요한 괴 will happen는 어느쪽 그래도지상이기 때문에, 각 행에는 읽었을 순간 LinkedIn의 원래 상태가 유지된 unread가 들어있다 — 페이지 로드가 유일한 그 사실 을 잃는다. 그 하나입니다.

  2. job 검색(채용 공고 검색)을 실행하면 직접 사이트에 검색어를 입력할 때처럼 통해 자신의최근-검색( recent-search) history에 추가됩니다.

그런 비용과 관리가 모두 in tool docstrings in linkedin_server_info 로 공개되어있다. 그렇습니다.


Set up

cd D:\Sundeep\projects\job-hunting\mcp-servers\linkedin
pip install -r requirements.txt
playwright install chromium
python -m pytest            # 986 passed

그 다음, 서버가 즉시 클라이언트에게 등록된 후에는 먼저linkedin_login_browser을 호출하십시오. linkedin.com/login에서 창 열립니다. 로그인은사용자가 직접 수행한다 — 이 서버가 비밀번호를 보거나 입력하거나 저장하거나 전송하는 일이 없다. 이후 세션은 영구 Chrome 프로필에 보존되어, LinkedIn이 만료하기 전까지단 한 번만 되는 단계다.

아무 읽기 결과를 신뢰하여을 사용하기 전에 linkedin_auth_status과 상태를 확인.

이 스토리지 등록

stdio transport, entry point linkedin.py:

{
  "mcpServers": {
    "linkedin": {
      "command": "python",
      "args": ["D:\\Sundeep\\projects\\job-hunting\\mcp-servers\\linkedin\\linkedin.py"]
    }
  }
}

"read-only"가 아니라 단문이 아니라 강제성을 어떻게 하는 (enforce)

linkedin_server/readonly.py 로 네 가지 기제가 들어 있으며, 실제 패키지를 만지 은 신뢰 앞에서 각각을 고의로 위반시키는 실패하는 테스트로 확인한다. 실패가 있을 수 없는 가정은 아무것도 입증하지password이기 까닭이다.

  1. 네비게*샨 allowlist. assert_read_url가 page.goto 도어를 여는 유일한 문이며, 허락되는 모든 URL는 마징표 앵커된 pattern이다. 입력한 검색어가 우리 K 링크의 네비게이션하고 어떤 action용 URL로 물타가는될은 없다. 차단 출발에는 /jobs/application/, /messaging/, 초대, /edit/, open-to-work, action=, 이 있는, 그리고 www.linkedin.com 이외의 모든 도메인이. 공고 목표로 채용리스트에 대한 URL의 패턴이 그 목록 중 가장 빈틈없을 뿐이다 오직 분 갯숫자 id 만 지니고 쿼리 문자열도 허용하지 않는다 왜냐하면 그 URL을 integer 로 조립되는 관계로쿼리 스트링이 그것을 가지고 있지 않기 때문이다. LinkedIn이 제 서비스 류웹에서 slug 표시한 것을 포스터 또는 /jobs/view/senior-engi... 등이 가능한 같은 이유로 거부되는데, slug 이직 title이고 title은 string이기 때문이다.

  2. Source scanner. 페키지는 상태를 바꿀 수 있는 호출 가능 — click, fill, type, press, select_option, set_files, 양식 제출, Any non-GET request — 를 목표로 그제에서 grep한다. 검색 결과 정확적 하나: 있다, writes.perform 안의 click이기. 하지만 스캐너를 이 호출을맞추기 위해 완화하지 않았다. 스캐너는 여전히 변이 호출을 응조적으로 조건 보도하고, 그 언어 하나만 컨트롤되어 허용하는 것은 별도 한 줄의 허용 목록 readonly.SANCTIONED_MUTATIONS이며 키는 (path, function, kind)입니다. 이 세 가지가 각각 몇 가지를 거부하기 때문에 dom.py의 click, writes.py의 다른 함수 자체 내의 click, perform 내부의 fill — 모두수 못한다. 그리고 그 밖에 한단계 아래 클로저 안에 묻힌 click 도 거부되는데, why because 귀속은 닫혀은 outermost 함수로 분류되기 때문이다. 이 다섯의 실패 후보 각각통해 유효하게 실패로 나타내 보입니다. 암, 하나 another 또 click 것이 perform 때문에 이 triple에서 더 가지고 첫 컨트롤 수 있지만, 별도로 allowlist안의 항 수 이상의 muating 호출이 없는지 패키지 전체를 검증하기 때문에 후속 호출이 들어와 도라게 잡힘 of catches.

evaluate도 무엇처럼 표기된다: 세개의 read-only DOM 하베스터는 trailing # readonly-ok가 걸린 면제되고, 어떤 새 introduce evaluate가 빌드를 실패하게 유지된다 — 누군가 리뷰하기 항상 있는 diff로 그 면제를 통과하기 전까지는 도구 surface check. 어떤 tool의 이름에 쓰기 동만(동사)이 없고, 어떤 docstring도 부정적인 "어떤 변경을 만든다"는 주장 이런하는 것 없다. docstring이 "가 지워지거나 추가 진다"와 같은 문구 하는 것은 무제. 따라서 이 검사는 단순히 문구를 금지하는 대신 부정 표현을 찾는다.

  1. Launch representing. assert_launch_flags_permitted는 두 가지 허용이 아니라 지난번에 그 사이다; 그 flags의 밖인 Chromium my flags를 다 씹기하고, --disable-blink-features 이외 값으로 AutomationControlled 불가 아닌 다양한인 것은 벤다. 그 flag는 브레이브제 Blink의 어떠한 동작이라도 꺼내다시기하에, the name 여부로만 허용 안되 маловероятно므로. browser.py는 **매번 실행마다 이함수를 실행해 실행 시간 시 그것이 결합되고, CI뿐 아니라 아래서.

동반 스캐너가 anti-detect 라이브러리의 출현 — playwright_stealth이, undetect_chromedriver, captcha 해결 모듈, TLS-spoofing 클라이언트 — 을 의존으로 감지하며, import 줄에만 일치시킨다. 따라서 이 파일은 여전히 경계를 산문으로 기술할 것이다.

주입된 스크립트는 또 페이지를 변이할 수 있는 것 (.click(, .value =, dispatchEvent, fetch(, ...) 을 별도의 검사한다. 그 스크립트는 DOM에서 질의고고 텍스트를 읽습니다.

그 검사는 특정 이름으로 정의된 것이 아니라 실제로 실행되는 것에 바인딩된다. 테스트 개통 package를 parse하여 모든 page.evaluate(...)의 첫 번째 인자를면 식끝내 모듈 레벨 상수로 resolve해서 그 값을 스캔된다. 그래서 스크립트를 읽힘 없이 주입할 수는 없고, 이 문자열이 resolve 못하는 것(예를 들어 런타임중 어셈블되는 것)은 곧바로 다음 빌드를 실패로 만든다. 이전 버전은 손수 _JS로 끝나는 세 개의 이름을 목록으로 스캔했는데, vax cold review reading 시점에서 어떤 리뷰어가 상수 하나 EVIL_INLINE을 해고 그것에 localStorage.setItem와 fetch(를 끼워 아주 기존의 호출부를 통해 with delivery final을 모든 테스 green으로 보는 것가 통과해버렸다. 그 구멍은 닫혔고 그 이제 그 공격은 테스트 중에 하나로 존재한다.

형제 하드웨어 서버(sibling server)가 이 서비스가 생멘 전전 오는데 반대 방향의 을 탐재하여 출발했다. 세션 쿠키가 나타났다 즉시 그것은 성공음을 보고한다. LinkedIn은 로그인하지 않은 방문자에도 쿠키를 지급하고, 이 그런지 성공의 신호는 아닌 것이었던.

여기서의 판결 VO는 GET /voyager/api/me에서 나온다. LinkedIn's web이 어플 being에서 로딩에 호출하는 아이덴티티 API이다. li_at 쿠키가 나타나는 것은 단지 그게 항상 endpoint에 재요청 할 reason 을 주는 사인일 뿐이다.

총 세 개의 결과이며 두 가지가 아니네요:

  • 연결 세 개 접근 at: authenticated: true — 엔드포인트가 한 명의 아이덴을 반환했 Amire.

  • authenticated: false — 엔드포인트가을 거부했거나 피드가 로그아웃 담방 벽으로 리다이렉트된다.

  • authenticated: null — 어느 쪽까지도 판정이 아니였다. Untitled호(알 수 없음)는 '로그아웃'으로 합축하지 않는다, 유효시 그걸 하면 다시 login 해달라고 하는데, 세션이 분현결 몇 미들 정상 되는 때에 comment에는 판독 lines 불가추 called** So stuff nothing — 그

쿠키 값은 자격 증명입니다. 이 서버는 쿠키 값을 절대 로그로 남기지 않고, 절대 저장하지 않으며, 도구 결과에도 절대 나타내지 않습니다. 그 존재 여부만 보고되며, 그 점을 두 개의 테스트가 확인합니다.

로그인과 그 유효 시간

linkedin_login_browser를 호출하세요. LinkedIn 로그인 페이지에서 Chrome 창이 열리고, 여러분이 직접 입력합니다. 이 서버는 비밀번호를 보거나, 입력하거나, 저장하거나, 전송하지 않습니다. 그렇게 할 수 있는 코드 경로 자체가 없습니다. 창은 신원(identity) 엔드포인트가 실제 세션을 확인하거나, 여러분이 닫거나, wait_seconds가 경과할 때까지 열려 있습니다(기본값 300초; 더 필요하면 더 큰 값을 전달하세요).

이것은 세션마다 하는 단계가 아니라 1회성 단계입니다. 세션은 디스크에 있는 Chrome 프로필 _state/chrome-profile/ 아래에 저장되므로, 이 세션은 다음 사항에도 survive합니다:

이벤트

세션이 유지됩니까?

이유

이 서버가 재시작할 때

그렇다

세션은 프로세스에 있는 것이 아니라 디스크에 있습니다.

컴퓨터가 재부팅될 때

그렇다

동일합니다.

프로필 디렉토리가 삭제될 때

아니다

그 디렉토리 자체가 세션입니다.

창 안에서 로그아웃할 때

아니다

LinkedIn이 그 세션을 폐기합니다.

LinkedIn이 쿠키를 만료시킬 때

아니다

아래 참조.

LinkedIn이 여러분에게 주는 시간. linkedin_session_info는 li_at 쿠키의 만료 날짜와 남은 일 수를 브라우저 자체의 쿠키 저장소에서 직접 읽어 실시간으로 보고합니다. 그래서 가만히 어림짐작할 필요가 없고, README의 주장이 아니라 측정값입니다. 참고로, 이 프로필에서 LinkedIn의 자체 장기 쿠키(bcookie, bscookie)는 365일 만료로 발급되었습니다. 로그인을 좌우하는 것은 li_at 값이며, 실제 로그인을 통해서만 생성될 수 있습니다.

쿠키 값은 자격증명입니다. 이 서버는 값은 절대 로그로 남기지 않고, 절대 저장하지 않으며, 도구 결과에 절대 나타내지 않습니다. 이름, 존재 여부, 만료 날짜만입니다.

세션이 만료되면, 모든 읽기 도구가 그렇게 알려줍니다. {"error": "not_authenticated", "message": "..."}와 함께 linkedin_login_browser를 복귀 경로로 지정합니다. 대신 빈 목록을 반환하지 않습니다. 만료된 세션에서 얻은 빈 목록은 실제로 데이터가 전혀 없어서 생긴 빈 목록과 구별할 수 없기 때문입니다.

콜드 스타트와 그 안의 함정

li_at은 지속성(영구) 쿠키입니다. JSESSIONID는 LinkedIn의 자체 웹앱이 csrf-token 헤더에 복사하는 것으로, 신원 엔드포인트가 인증된 요청에 응답하지 않는다는 그 값은 세션 쿠키입니다 (이 프로필의 쿠키 저장소에서 is_persistent=0). 그래서 브라우저가 시작할 때마다 쿠키 저장소에는 정상적인 로그인은 있는데 csrf 토큰은 없습니다.

서버가 즉시 신원 엔드포인트를 호출하면 토큰 없이 요청을 보내고, 거절당하고, 세션이 멀쩡한데도 다시 로그인하라고 안내하게 될 것입니다. 그래서 콜드 쿠키 저장소에서는 check_auth가 먼저 LinkedIn 페이지를 하나 불러와서 LinkedIn이 쿠키를 발급하게 만든 다음에야 엔드포인트를 호출합니다. 그 페이지 로드는 일거양득으로 확인 읽기도 대신하므로 추가 요청이 들지 않습니다.

복구 경로: 여러분의 Chrome에 연결하기

일상적인 경로가 아닙니다. 위의 지속성 프로필이 정답입니다. 이것은 그 프로필의 세션이 죽고 새 로그인이 거부되는 날을 위한 대비책입니다. LINKEDIN_CDP_ATTACH=1로 활성화하면 이 서버는 아무것도 시작하지 않습니다. 여러분이 직접 시작한 Chrome에 CDP로 연결하기만 합니다.

이를 조용히 무너뜨리는 두 가지가 있으며, 이는 모두 이 장비에서 측정되었습니다:

  1. 작업표시줄에서 연 Chrome에는 DevTools 포트가 없습니다. "브라우저가 켜져 있다"는 충분하지 않습니다. --remote-debugging-port 플래그로 시작되어야 합니다.

  2. Chrome의 싱글턴 때문에 플래그가 사라집니다. Chrome이 이미 실행 중이면 그 플래그로 두 번째를 시작해도 첫 번째 인스턴스에 인수가 넘겨진 뒤 종료됩니다. 포트도, 오류도, 그냥 종료 코드 0만 남습니다.

따라서 먼저 Chrome을 완전히 종료하세요(창 및 백그라운드 인스턴스 모두). 그러면 실제 프로필과 그에 따른 실제 LinkedIn 세션이 유지됩니다:

"C:\Program Files\Google\Chrome\Application\chrome.exe" --remote-debugging-port=9224

아니면 Chrome에 별도의 프로필을 주면, 이미 켜져 있는 Chrome과 함께 쓸 수 있지만 아무 데도 로그인되어 있지 않으므로, 그 창 안에서 한 번 LinkedIn에 로그인하면 됩니다:

"C:\Program Files\Google\Chrome\Application\chrome.exe" --remote-debugging-port=9224 --user-data-dir="%LOCALAPPDATA%\linkedin-cdp"

http://127.0.0.1:9224/json/version을 열어서 제대로 되었는지 확인하세요. JSON이면 포트가 살아 있는 것입니다. 또는 linkedin_cdp_status를 호출하면 서버가 직접 포트를 확인하고, 응답이 없으면 명령어를 함께 방식없이 알려줍니다. 주소는 127.0.0.1이지 localhost가 아닙니다. Chrome은 포트를 IPv4에만 바인딩하므로, 이름을 풀면 먼저 [::1]로 변환되어 타임아웃을 소모합니다(35 ms가 아니라, 2085 ms로 측정되었습니다).

포트는 9224로, 형제 서버인 Naukri의 9223을 사용하지 않습니다.

연결 모드에서 이 서버는 프로필 잠금을 걸지 않고(소유한 프로필이 없습니다), 여러분의 탭을 조작하는 대신 자체 탭 안에서 작업하며, 종료 시 여러분의 브라우저를 닫지 않고 연결을 끊습니다. 즉, close()는 클라이언트 연결만 끊는 셈인데, 그 점은 실제 Chrome으로 횟수 측정되어 신뢰하게 되었습니다. 읽기 전용 화이트리스트는 두 모드 모두 동일합니다.

요청 제어

  • 페이지 로드 사이에 **기본 3초 이상 ** 전역적으로 적용됩니다. 위장이 아니라 가속 제어입니다. 의도적으로 헤이불로 만들어진 것입니다.

  • 도구 호출당 페이지 로드는 한 번. 유일한 예외는 linkedin_my_profile(include_skills=True)로, 두 번째 페이지가 로드되고 pages_loaded: 2로 보고됩니다.

  • 자동 페이지네이션 없음. 검색의 다음 페이지를 원하면 의도적으로 start=25로 요청하세요. 모든 목록 결과에는 capped, page_had, limit이 있으므로, "결과 25개"가 "실제로 25개가 존재한다"로 오해받는 일은 없습니다.

  • 한 번에 호출 하나, 프로세스 내에서 직렬화되고, 한 번에 프로세스 하나는 잠금으로 직렬화됩니다. 두 프로세스가 같은 Chromium user-data 디렉터리를 사용하면 그 디렉터리가 손상되어 세션을 잃게 되는데, 그런 일로 형제 서버의 37분이었습니다.

  • 창이 남아 있지 않습니다. 5분간 유휴 상태이면 브라우저가닫히면서 잠금을 해제합니다.

읽을 수 없는 상황

서버가 빈 목록을 반환하는 대신 오류를 일으킵니다. 렌더링에 실패한 페이지에서 생긴 빈 목록은 실제로 데이터가 없는 빈 목록과 구별할 수 없는데, 이 두 가지를 절대 혼동할 수 없게 해야 합니다. 읽기 실패는 {"error": "extraction_failed", "url": ..., "hint": ...} 형태로 돌아오므로, 직접 같은 페이지를 열어 서버가 본 것을 확인할 수 있습니다.

유일한 예외는 linkedin_search_jobs입니다. 그 도구에서 결과가 0개는 실제 답변이므로 note와 함께 results: []를 반환합니다.

페이지를 읽는 방식

LinkedIn 클래스 이름은 생성되며, GraphQL 쿼리 ID는 배포될 때마다 회전(순환)되므로, 두 항목 모두 깨지기 쉬운 기준입니다. 회전되지 않는 것은 주소 형태입니다. 즉, 개인은 /in/<slug> 뒤에, 채용은 /jobs/view/<id> 뒤에 있습니다. 모든 목록 화면은 해당 링크를 찾아 주변 카드의 상태. 텍스트를 읽어 파싱한 다음, shape.py의 순수 함수로 분석합니다. 그래서 파싱은 브라우저나 네트워크 계정 없이ũ.

알림 표면은 각 항목에 대해 그럴 듯한 링크가 없는 유일한 위치인데, 그래서 구조에 기준을 두고 전개합니다. 업데이트가 가장 필요한 곳이며, 감지하지 못할 때는 빈 목록 대신 오류를 일으킵니다.

구조

linkedin.py              entry point (stdio)
linkedin_server/
  config.py                  paths, timeouts, caps, the rate floor,
                             the two launch flags
  readonly.py                the allowlist, the scanners, the verb list,
                             the launch boundary
  profile_lock.py            cross-process lock on the Chrome profile
  browser.py                 persistent context, single-flight, idle close
  auth.py                    the login gate, session lifetime, cold start
  cdp_bridge.py              the recovery path: attach to a running Chrome
  dom.py                     the read-only harvesters and the control readers
  shape.py                   pure parsers and the result envelope
  server.py                  the seventeen tools
  errors.py
tests/                       1393 tests, no network, no account
  fixtures/                  frozen LinkedIn markup, scrubbed

상태

빌드 및 테스트: 1393테스트, 네트워크 없음, 계정 없음. 대부분은 브라우저도 없이 실행되고, 픽스처 기반 모듈은 로컬 헤드리스 Chromium을 런칭하여 고정 마크업에 가져온 실제 파서를 실행하지만 머신 외부로 나가지 않습니다.

이 숫자는 파일의 가장 부정확하게 기재된 수치입니다. 스위트가 1000을 넘어서형 986개라고 세 차례 표시되었는데, 그 자체로는 문제가 없지만 동일한 습관 때문에 네 문끄가 이 서버가 쓸 수 없다고 표시되었습니다. 숫자는 이제 이전 값을 이어가는 대신 각 사이클에서 다시 측정합니다.

첫 라이브 실행: 2026-08-21. 로그인이 성공하고 세션이 유지되었으므로, 위의 플래그는 이 머신에서 이제 충분함이 검증되었습니다. /voyager/api/me와 li_at 수명(365일)도 확인되었습니다. 그 후 모든 읽기 도구를 실제 계정에 대해 한 번씩 실행했습니다. 11개 도구 중 4개가 제대로 동작했습니다. 계획 조치는 ../_audit/2026-08-21-linkedin-parse-fix.md에 정적되어 있으며, 그 결과는 이렇습니다.

linkedin_who_viewed_me는 이름이 아닌 값을 이름으로 반환하고 있었습니다. 모든 행은 페이지 제목, "Who's viewed your profile", 실제 인물의 프로필 주소에 달려 있었습니다. 4행, 반복되는 이름 1개, 그런데 4개 주소는 모두 진짜였습니다. 이후 당일에 수정되었습니다: 행 경계, 이제 LinkedIn이 하이드레이션 후 붙여 넣는 속성에 의존하지 않으며, 검색 결과의 개인정보 보호 사용자는 더 이상 조용히 제거되지 않고(10명 중 6명이 그런 경우였습니다), 타임스탬프를 정확히 읽습니다. 라이브로 검증: 10행, 10개의 고유 이름, 누락된 필드 없음.

두 번째 라운드, 2026-08-22. 첫 라운드에서 깨진 상태로 남은 세 화면이 수리되고 실시간 검증되었습니다. 네 가지 오류는 모두 같은 특성입니다. LinkedIn이 더 이상 생략하는 마크업 기반 그 읽기가, 또는 페이지 렌더링의 어느 정도에 따라 존재 여부가 바뀌는 마크업에 뿌리를 보고 있는 경우.

도구

이전

현재

linkedin_my_profile

오류: 이름을 읽을 수 없음

h1이 0개인 페이지에서 이름, 부제목, 위치, About, 프로필 사진을 읽습니다. 항목이었습니다. 이제 한 섹션은 자기가 있는 정확히 하나의 제목을 포함하는 가장 큰 조상으로 정의합니다. 행 안에서 쓰는 규칙과 같은데, 하이드레이션 전후 동일한 결과가 나옵니다. 실시간 검증됨.

linkedin_saved_jobs, linkedin_my_applications

리다이렉트에서 오류

/jobs-tracker/?stage=saved와 ?stage=applied를 읽습니다. 두 목록 모두 실제로 비어 있으며, 비어 있는 결과는 이제 명시적으로 표시되고 LinkedIn 자체 탭 개수와 빈 상태 문구를 함께 포함합니다. 페이지가 뒷받침하지 않는 0은 여전히 오류입니다. 실시간 검증됨.

linkedin_notifications

행이 많고 노이즈 포함

화면 리더 텍스트가 구문이 아니라 개수로 차감됩니다. when은 카드 자체의 시간 요소를 읽습니다. 각 행은 읽을 당시 상태의 unread 정보구도 포함합니다. 라이브 페이지의 동결 캡처로 검증됨.

my_profile 내 skills

All, Industry Knowledge, Tools & Technologies를 반환

실제 목록을 반환합니다. 실제 계정에서 20개 기술이며, 페이지가 제공하는 유일한 기술별 앵커로 구분하키는 대상의결실.

프로필 리더가 하지 않는 일 하나: 경력, 교육, 기술은 프로필 페이지에 전혀 없습니다. LinkedIn은 스크롤해야 그들을 생기게 만들으며 이 서버는 자체 스크롤 하지 않습니다. 그들은 빈 값이 아니라 UNKNOWN으로 보고되며, 자세한 정보가 필요하면 각 페이지로 가는 앵커가 details_urls로 제공됩니다.

세 번째 라운드, 2026-08-22. linkedin_search_jobs가 마지막으로 남은 오류였습니다. 어떤 검증된 회사의 행에 LinkedIn은 "<title> with verification"이라는 화면 리더 전용 줄을 넣었는데, 이를 위치 기반으로 읽으면 그 줄이 company가 되고 실제 회사가 location으로 밀려나는 사례인 두 라이브 검색을 통해 14개 중 5개에서 발생했습니다. 이제는 존중 구조가 재작동되어, 위치 기반 필드가 수정되었습니다.

이 수정은 특정 문자열에 대한 규칙이 아닙니다. 필드는 더 이상 “1번 줄, 2번 줄, 3번 줄”처럼 읽히지 않습니다. LinkedIn이 집어넣은 줄이 하나라도 추가되면 그 뒤의 모든 필드가 밀리기 때문입니다. 실제로 같은 두 페이지에는 “Promoted", "Apply”, “Viewed”, “Actively reviewing applicants”라는 표시와, 연봉 칩(salary chip), 그리고 동문 정보 줄까지 모두 있었습니다. 이제 각 필드는 그 필드를 식별해 주는 대상에 고정됩니다. title은 행을 채용 공고 행으로 만드는 링크의 텍스트에 고정되며, 페이지 자체가 가진 스크린 리더용 복사 텍스트는 개수만큼 차감합니다. company는 LinkedIn이 고용주 로고에 부여한 접근 가능한 이름(accessible name)에 고정됩니다. 로고는 이미지이므로 줄이 추가되어도 밀릴 수 없습니다. location은 entity lockup 내부의 메타데이터 목록에 고정됩니다. 여기서 lockup은 클래스 이름을 전혀 쓰지 않고, 그 로고도 함께 감싸는 링크의 가장 작은 조상 요소로 찾습니다. 이 중 어떤 앵커도 제공하지 않는 화면 — job tracker가 그런 경우 — 은 이전처럼 줄을 순서대로 읽는 방식으로 대체합니다.

같은 쿼리로 실제 환경에서 검증했습니다. 7개 행 중 7개가 LinkedIn 자체의 artdeco-entity-lockup 요소와 일치했습니다. 이번 수정은 그 요소를 의도적으로 사용하지 않습니다. 그 7개 중 3개 행에는 검증용 데코레이션이 붙어 있었습니다. 테스트는 LinkedIn이 아직 출시하지 않은 데코레이션을 모든 고정(frozen) 행의 모든 위치에 주입하고, 결과가 움직이지 않기를 요구합니다. 그리고 대조군을 통해 앵커를 제거하면 같은 주입이 필드를 깨뜨리는 것을 보여줍니다.

Available Tools

17 tools
linkedin_auth_statusA

Report whether there is a live LinkedIn session, measured not guessed.

The verdict comes from an authenticated request: GET /voyager/api/me, the same identity call LinkedIn's own web app makes on page load. A session cookie sitting in the profile proves nothing and is never treated as an answer -- LinkedIn hands cookies to signed-out visitors too.

Three outcomes, deliberately:

  • authenticated true -- the endpoint returned an identity.

  • authenticated false -- the endpoint refused, or the feed redirected to LinkedIn's signed-out wall.

  • authenticated null -- neither could be established. Unknown is reported as unknown rather than collapsed into "signed out".

Costs up to two requests: the identity call, plus one feed load used only to turn an inconclusive answer into a definite "false".

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.7/5.0
Behavior5/5

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

With no annotations provided, the description carries the full burden and does so excellently. It discloses the internal behavior: makes up to two requests, uses the identity endpoint, treats cookies as unreliable, and explicitly defines the null case as 'unknown' rather than 'signed out'. This level of detail is transparent and goes well beyond basic expectations.

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 structured with a clear opening statement, followed by detail and bullet-point outcomes. Every sentence adds value: it explains the measurement method, the meaning of each outcome, and the cost. It is thorough without being verbose, and the front-loaded core statement makes it easy to grasp quickly.

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 that the tool has no parameters, an output schema exists, and the description thoroughly explains the semantics of each outcome, nothing is missing. It also covers edge cases (e.g., cookies from signed-out users) and costs, making it complete for an agent to use correctly.

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

Parameters4/5

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

The tool has zero parameters, so the description correctly does not discuss any. Per the calibration rules, a baseline of 4 is appropriate since there are no parameters to explain and the schema coverage is 100% (empty schema). The description doesn't need to add anything beyond that.

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: to report whether there is a live LinkedIn session, and it specifies the exact method (authenticated GET /voyager/api/me). It distinguishes itself from siblings like linkedin_session_info by emphasizing it measures actual authentication status, not guessed from cookies.

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 effectively communicates when to use this tool—whenever an agent needs to determine authentication status. It explains the methodology and the three possible outcomes, making it obvious how to interpret results. However, it does not explicitly name alternative tools or state when not to use it, so it misses the highest bar for usage guidance.

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

linkedin_cdp_statusA

Is there a browser this server could attach to? A recovery diagnostic.

NOT the normal way to run this server, and not something to reach for first. The daily path is the persistent Chrome profile: sign in once and it holds for as long as LinkedIn honours it. This exists for the day that profile's session dies and an automated sign-in is being refused -- then the operator can run his own Chrome with a DevTools port open and let this server read through that instead.

It needs a Chrome that is ALREADY RUNNING and that was started with --remote-debugging-port. A browser opened from the taskbar has no such port, so "my browser is open" is not enough. Worse, when a Chrome is already running, a second one started with the flag silently hands its arguments to the first and no port opens at all -- so either quit Chrome completely first, or give the new one its own --user-data-dir.

This touches nothing on LinkedIn. It asks the local port what is there and reports the answer, or the exact command to run when nothing answered.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.9/5.0
Behavior5/5

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

With no annotations provided, the description carries the full burden of behavioral disclosure. It states the tool touches nothing on LinkedIn, only queries the local port, and reports the answer or the exact command to run when nothing answers. This fully discloses side effects (none) and expected outcomes, making behavior transparent and safe to invoke.

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 the description is relatively long, every sentence earns its place. It front-loads the purpose and the 'not the normal way' nuance, then details prerequisites and pitfalls, and finishes with the safety guarantee and output behavior. The structure is logical and efficient, with no 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?

The description is complete for a no-parameter diagnostic tool with an output schema. It explains the tool's role, when to use it, prerequisites, common failure modes, side effects, and how the result is reported. Nothing an agent needs to decide whether and how to call it 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 schema confirms an empty object. The description does not need to explain parameters, and the baseline for no parameters is 4. It adds nothing beyond that, but nothing is needed—the schema already fully covers the parameter surface.

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: checking for an attachable browser, explicitly framing it as a recovery diagnostic. It distinguishes this from the normal persistent-Chrome-profile path and names the alternative clearly, so an agent can tell this tool apart from its siblings like linkedin_auth_status or linkedin_server_info.

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?

The description explicitly explains when to use this tool (when the profile session dies and automated sign-in is refused) and when not to use it (not the first choice, not the daily path). It also provides operational prerequisites (Chrome already running with --remote-debugging-port) and warns about the second-instance pitfall, giving concrete guidance that leaves no ambiguity.

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

linkedin_followed_companiesA

The company Pages LinkedIn records you as following.

A read, and the exact counterpart of linkedin_saved_jobs: it answers "is this Page already on the list?". This server cannot follow or unfollow anything and ships no tool that could.

THE ONE THING TO READ BEFORE TRUSTING AN ANSWER. LinkedIn draws only the first rows of this list and fetches the rest on scroll; this server opens one page and reads whatever had drawn. So complete is normally false -- measured 2026-08-23, twenty rows under a heading saying 58 Pages -- and a Page missing from pages comes back as UNKNOWN, never as not-followed. Three-valued on purpose: "absent from the rows I was shown" and "you are not following them" are different facts, and reporting the first as the second is how a confirm gate ends up pointing the wrong way.

For a SINGLE employer whose posting you already have, linkedin_job_detail is the better read: it reports company_follow_state off the posting page itself, at no extra page load, and that answer is never partial.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNohow many rows to return.
companyNoa Page name or numeric Page id to ask about. Leave it out to get the whole rendered list.

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.9/5.0
Behavior5/5

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

With no annotations provided, the description fully carries the burden and does so excellently. It discloses the incomplete list due to scrolling, that 'complete' is normally false, and explains the three-valued semantics (UNKNOWN vs not followed) to avoid misinterpretation. It also notes the server's read-only nature.

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 long, every section earns its place. The critical caveat is front-loaded in a bolded warning, and the structure guides the reader from core purpose to alternatives. Nothing is redundant; it's dense but efficient.

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?

The description covers purpose, limitations, alternatives, and parameter usage thoroughly. Since an output schema exists to explain the return shape, nothing essential is missing. An agent can confidently decide when and how to use this tool.

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?

Both parameters are already well-documented in the schema (100% coverage). The description adds a slight extra: 'Leave it out to get the whole rendered list' for the company parameter, which provides context beyond the schema. Given the high schema coverage, this is adequate but not extensive.

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 resource (company Pages you follow) and the action (list/read). It explicitly identifies itself as the counterpart of linkedin_saved_jobs and answers 'is this Page already on the list?', which precisely distinguishes it from mutation siblings like linkedin_unfollow_company.

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?

It explicitly names linkedin_job_detail as the better alternative for a single employer, explains why (reports company_follow_state directly from the posting page, never partial), and clarifies that this server cannot follow or unfollow anything. This gives clear when-to-use guidance and prevents misuse.

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

linkedin_job_detailA

Read one job posting in full, including the description and the pay.

Every list tool here returns CARDS -- title, company, location, link. This reads the posting behind one of them, which is where the facts that settle a decision actually live: the pay range, LinkedIn's own applicant count, the workplace and employment type, the hiring status, and the description itself. None of those is on any card.

One page load, one posting. There is no sweep and no paging.

A read in both directions: this does not apply to the job, does not save it, and has no way to change anything about the posting. Nobody else on the page is collected either -- LinkedIn draws a hiring team and a "people also viewed" rail beside a job, and neither is read here.

A field the page did not carry comes back null rather than blank. If the page did not render the posting at all the call FAILS instead of returning an empty one: LinkedIn serves the document title before the body, so a title with nothing behind it is never treated as an answer.

ParametersJSON Schema
NameRequiredDescriptionDefault
job_idYesthe numeric job id, or a job url to take it from -- linkedin_search_jobs and linkedin_saved_jobs both return each.

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.6/5.0
Behavior4/5

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

There are no annotations provided (readOnlyHint, destructiveHint are absent), so the description carries the full burden of disclosing behavioral traits. It does this well: it states that this tool does not apply to the job, does not save it, and has no way to change anything about the posting; it also clarifies it does not collect other people on the page. It describes null vs. FAIL behavior for missing fields and unrenderable pages. The only small gap is that it doesn't explicitly mention auth requirements or rate limits, but for a read-only tool with one page load, the disclosure is thorough and covers the key side effects and failure modes.

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 well-structured and front-loaded: the first sentence states the core purpose, then it builds context about what cards lack, confirms the one-page behavior, clarifies non-effects, and explains null/fail semantics. It is a bit longer than strictly necessary (around 150 words) but every paragraph earns its place by addressing a distinct aspect: what this gives, what it does not give, and the specific edge case. The structure is clear and uses paragraphs to separate logical blocks. Slightly verbose in places (repeats 'this does not apply...' phrasing), but overall effective.

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 complexity is simple (one parameter, no nested objects) and there is an output schema (has_output_schema=true) that presumably describes the returned fields, the description covers the critical runtime behaviors an agent needs: what data is available, the distinction from sibling list tools, the null vs. FAIL semantics, and the explicit side-effect-free nature. The description fully equips an agent to decide when to call this tool and what to expect. The only potential missing detail is the exact output fields, but that is covered by the output schema, so the description is complete.

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 schema has a single parameter, job_id, with 100% description coverage (the schema already describes it as the numeric job id or a job URL from two sibling tools). The description reinforces this by mentioning that both linkedin_search_jobs and linkedin_saved_jobs return it, but does not add new meaning beyond what the schema gives. Since coverage is 100% and the description adds a small hint about which tools return the id, a score of 4 is appropriate—slightly above baseline because the description confirms the source of the parameter value, which is useful for an agent.

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 direct, specific statement of what the tool does: 'Read one job posting in full, including the description and the pay.' It clearly identifies the resource (one job posting) and the verb (read). It goes further to distinguish itself from sibling list tools, explaining that this tool retrieves the full posting behind a card, and explicitly lists the facts available only here (pay range, applicant count, etc.). This fully separates it from the list/collection siblings (linkedin_search_jobs, linkedin_saved_jobs) and from action tools (linkedin_save_job, linkedin_unsave_job).

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?

The description is explicit about when to use this tool vs. the sibling list tools: it explains that list tools return only cards with title, company, location, link, while this tool is where the decision-settling facts live. It states there is one page load, no pagination, and explicitly mentions it does not read the 'people also viewed' rail or hiring team. It also provides clear behavioral conditions: the call FAILS if the page did not render the posting, and it treats a title-only page as an error. This gives an agent unambiguous selection criteria.

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

linkedin_login_browserA

Open LinkedIn's sign-in page and wait for you to sign in yourself.

This server never sees, types, stores or transmits a password. It opens a browser window at linkedin.com/login; you type into that window; the persistent Chrome profile keeps the session afterwards, so this is a one-time step until LinkedIn expires it.

The window stays open until the identity endpoint confirms a real session, the window is closed, or wait_seconds runs out. A cookie appearing does not end the wait -- it only causes the endpoint to be asked again. On timeout the result is authenticated false with a reason, never an optimistic success.

ParametersJSON Schema
NameRequiredDescriptionDefault
wait_secondsNohow long to leave the window open for you. Default 300.

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.7/5.0
Behavior5/5

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

With no annotations provided, the description carries the full burden — and it excels. It discloses that the server 'never sees, types, stores or transmits a password,' that the persistent Chrome profile retains the session, and crucially the non-obvious cookie nuance: 'A cookie appearing does not end the wait — it only causes the endpoint to be asked again.' It also states the no-optimistic-success guarantee on timeout. This prevents a common agent failure mode of assuming cookie presence means logged in.

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 purpose in the first sentence, and every subsequent sentence earns its place: the password privacy guarantee, the persistent-profile behavior, the termination conditions, and the cookie/timeout nuance. Nothing is filler; the multi-paragraph length is justified by the density of non-obvious behavioral 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?

Given the tool's interactive-browser complexity and the presence of an output schema, the description covers everything an agent needs: how the interactive flow works, the three termination conditions, the meaning of wait_seconds on timeout, and the return semantics. The one-time-step framing sets expectations correctly. Nothing essential 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?

Schema coverage is 100% and the schema already documents wait_seconds with a default. The description adds value beyond the schema by tying the parameter to observable behavior: 'On timeout the result is authenticated false with a reason, never an optimistic success.' This connects the parameter to the wait-termination and result semantics, which is genuine value added over the schema text.

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+resource+behavior: 'Open LinkedIn's sign-in page and wait for you to sign in yourself.' This clearly distinguishes it from siblings like linkedin_auth_status (checking status) and linkedin_session_info (session info). An agent immediately knows this tool initiates interactive login rather than inspecting or acting on the session.

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 conveys the key usage fact that this is 'a one-time step until LinkedIn expires it,' which tells the agent not to expect repeated calls. However, it does not explicitly route to alternatives such as checking linkedin_auth_status first to determine whether login is even needed. The context is clear but lacks explicit when-not/alternative guidance.

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

linkedin_logoutA

End the local LinkedIn session by erasing this profile's cookie jar.

THE ONE DESTRUCTIVE TOOL IN THIS SERVER, and the most expensive thing it can do to you. The sign-in it throws away took a full day to establish, and there is no automated way to put it back: linkedin_login_browser opens a window and you type into it yourself, exactly as you did the first time.

So confirm is False by default and an unconfirmed call performs NOTHING AT ALL -- no file is opened, no file is even stat-ed, no browser starts, the profile is not read. It hands back a preview naming the exact files a confirmed call would erase, what the sign-in cost, and how you get back in. Read that first; nothing about this is reversible afterwards.

Nothing here reaches LinkedIn. This server stays read-only towards the platform: no request goes out, no session is ended on LinkedIn's side, your account is untouched, and any other browser signed in to it stays signed in. What lapses is purely local -- the cookie jar on this machine.

A profile another process is using is never touched. If the cross-process lock is held, the answer is cleared false naming the holder's PID, because erasing a jar out from under a live Chromium is how a profile gets corrupted -- which costs the same day this tool is asking about.

ParametersJSON Schema
NameRequiredDescriptionDefault
confirmNoFalse -- the default -- previews and performs nothing. True erases the jar.

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.6/5.0
Behavior5/5

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

With no annotations provided, the description carries the full behavioral burden. It thoroughly discloses the destructive nature, the confirm-gated behavior (including that unconfirmed calls do nothing), the local-only scope (no remote impact), and the cross-process lock protection. It is transparent about irreversibility and side effects.

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 lengthy but every paragraph serves a safety-critical purpose: purpose, consequences, confirm behavior, local-only scope, and lock protection. It is front-loaded with the core action, and structure is logical. While it could be trimmed, the verbosity is justified for a destructive tool.

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 destructive complexity and absence of annotations, the description covers all essential aspects: the action, the confirm gate, preview behavior, irreversibility, recovery path, remote non-effect, and cross-process lock. The output schema exists separately, so return value details are not required here.

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

Parameters3/5

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

The input schema already provides a description for 'confirm' that exactly matches the text in the tool description ('False -- the default -- previews and performs nothing. True erases the jar.'). Since schema coverage is 100%, the description adds no additional semantic value beyond reiteration; the baseline of 3 applies.

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: 'End the local LinkedIn session by erasing this profile's cookie jar.' It clearly distinguishes itself as the sole destructive tool among siblings, making selection unambiguous.

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?

It explicitly warns that this is the one destructive tool and explains the cost of recovery, pointing to linkedin_login_browser as the alternative for re-establishing a session. The description implies when to use it (when you intend to end session) and strongly cautions against casual use, providing clear usage context.

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

linkedin_my_applicationsA

List the jobs you have applied to on LinkedIn, with their status.

Reads the Applied tab of your own job tracker (the page My Items > Applied became). Each row carries title, company, location, the status LinkedIn shows (applied, application viewed, resume downloaded, no longer accepting applications) and how long ago, plus the job id and link.

Status is whatever LinkedIn displays; this server does not infer, score or chase anything, and it cannot see applications you made anywhere else.

An empty result says so explicitly and carries LinkedIn's own count for the tab, so "you have applied to nothing" and "this could not be read" are never the same answer. If the two disagree -- no rows, but a non-zero count -- you get an error rather than an empty list.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNomaximum rows to return (default 25, max 100).

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.4/5.0
Behavior5/5

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

No annotations are provided, so the description carries the full burden. It discloses that status is whatever LinkedIn displays, that the server does not infer or chase, and explicitly explains edge cases: empty results carry LinkedIn's count, and a mismatch between rows and count yields an error. This is exceptionally transparent about behavior.

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 multi-sentence and a bit longer than strictly needed, but every sentence adds value: scope, row contents, status behavior, and edge cases. It could be tightened slightly, but it is well-structured and front-loads the primary action.

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?

The description explains row contents (title, company, location, status, how long ago, job id, link), clarifies that statuses mirror LinkedIn's display, and covers empty-result and error disambiguation. Given the simple schema and the presence of an output schema, this is complete enough for an agent to call correctly.

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

Parameters3/5

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

Schema description coverage is 100% for the only parameter (limit), and the schema already documents its behavior fully. The description adds no further parameter details, so the baseline score of 3 is appropriate per the scoring rules.

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 exactly what the tool does: 'List the jobs you have applied to on LinkedIn, with their status.' It names a specific resource (own job tracker's Applied tab) and the action (list), and clarifies it reads only your own applications. This clearly distinguishes it from siblings like saved_jobs or search_jobs.

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?

While it doesn't explicitly name alternative tools or say when not to use it, the description makes the scope obvious: it reads only the Applied tab and cannot see applications made elsewhere. This is sufficient guidance for an agent to know when to invoke it versus other job-related tools.

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

linkedin_my_profileA

Read your own LinkedIn profile as LinkedIn currently stores it.

Returns name, headline, location, the About text, and which sections were on the page when it was read.

On completeness: LinkedIn's own profile-strength meter is not exposed here, so this server does not report one. What it reports is derived and labelled as such.

One honest limitation, stated because its absence would otherwise read as data: LinkedIn now defers Experience, Education and Skills until the page is SCROLLED, and this server does not scroll. Those sections are therefore usually absent from the render, and absent means UNKNOWN here, never zero. sections_not_rendered names them, and details_urls gives you the page for each one if you want to look yourself.

ParametersJSON Schema
NameRequiredDescriptionDefault
include_skillsNoalso load the full skills page, which is where a real skills list can be read. That is a second page load, reported as pages_loaded: 2. Pass false to stay at one.

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.7/5.0
Behavior5/5

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

With no annotations provided, the description carries full responsibility for behavioral disclosure. It is exceptionally transparent: it explains that LinkedIn defers certain sections until scrolling and that this server does not scroll, so those sections are usually absent and reported as UNKNOWN, not zero. It also admits the profile-strength meter is not exposed and that derived data is labelled as such.

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 longer than average but every sentence adds information: it states the purpose, enumerates outputs, discloses limitations, and clarifies how to interpret absence. It is well-paragraphed and front-loads the core purpose before diving into nuance. No filler exists.

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 read-only profile tool with an output schema, the description covers all necessary aspects: what is returned, the caveat about missing sections, how to interpret missing data, and a hint about the details_urls for further investigation. The 'include_skills' parameter behavior is also disclosed through the schema. Nothing an agent needs to call and interpret the tool correctly is omitted.

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?

There is a single parameter, include_skills, and its schema description is already very informative, explaining the effect (additional page load, pages_loaded: 2) and the trade-off. The description itself does not elaborate on the parameter, but because the schema coverage is 100% and the schema text is detailed, the parameter semantics are clear. A score of 4 reflects the extra value of the schema description beyond a bare type.

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 uses a specific verb and resource ('Read your own LinkedIn profile') and immediately lists the concrete output fields (name, headline, location, About text, sections). It clearly distinguishes itself from siblings by focusing on the user's own profile and explicitly notes what is not included (profile-strength meter).

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 the tool is appropriate (reading your own profile) and provides guidance on handling missing sections by pointing to details_urls. It does not explicitly contrast with sibling tools, but the tool name and the content give enough context for correct selection. The explanation of the 'include_skills' parameter also adds usage context.

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

linkedin_notificationsA

List your LinkedIn notifications as they appear on the notifications page.

====================== SIDE EFFECT -- READ FIRST ====================== THIS TOOL CHANGES SOMETHING ON LINKEDIN. Loading the notifications page CLEARS YOUR UNREAD BADGE -- every notification LinkedIn was still counting as unread stops being counted, exactly as if you had opened the page yourself. MEASURED, not theorised: one call on 2026-08-21 took the badge from 1 to 0, and it does not come back.

It cannot be avoided. LinkedIn marks the list seen on the server when the page is served, so there is no read of this surface that leaves the badge alone: no click, no scroll and no per-item open is involved, and there is no mark-as-read call anywhere in this package. The only way not to clear the badge is not to call this tool.

It is the ONE server-side change any tool here causes. Everything else in this package leaves LinkedIn exactly as it found it.

Partial compensation, since the badge is going either way: each row carries "unread": true/false as LinkedIn had it AT THE MOMENT OF READING -- which is the fact the page load is about to destroy. Read it here or lose it.

Rows carry the notification text, how long ago it arrived, whether it was unread, and the link LinkedIn attaches. Screen-reader-only text ("Unread notification.", "Status is reachable") is stripped, so the body is what you would read on screen.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNomaximum notifications to return (default 20, max 50).

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.4/5.0
Behavior5/5

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

Annotations are absent, so the description carries the full burden — and it delivers exceptionally. It discloses the badge-clearing side effect with a dated measured example (badge 1→0), states it is unavoidable, confirms this is the only server-side change in the package, and describes the partial compensation (unread flags captured at read time) plus input stripping behavior. This is model behavioral disclosure.

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 long (~300 words), but the length is earned: the side-effect disclosure requires specific, verifiable detail. The purpose is front-loaded before the warning block, and the block formatting makes the critical information visually distinct. Minor deduction for verbosity in the compensation paragraph, which could be tightened.

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 single-parameter list tool with full schema coverage, an output schema, and a thoroughly disclosed side-effect profile, nothing an agent needs to call it correctly is missing. Return format is described in prose (notification text, time, unread flag, link), complementing the output schema.

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

Parameters3/5

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

Schema description coverage is 100% — the single 'limit' parameter (default 20, max 50) is fully documented in the schema. The description adds no parameter-specific meaning beyond the schema, which the rubric sets as the baseline 3. Nothing beyond the baseline, but nothing missing either.

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 first sentence states a specific verb (List) and resource (your LinkedIn notifications) with a clear frame of reference ('as they appear on the notifications page'). It is unmistakably distinct from the sibling tools, all of which cover auth, sessions, jobs, or profile surfaces — no other tool claims the notifications surface.

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 does not explicitly name an alternative, but none of the siblings overlap with notifications, so exclusion guidance is unnecessary. It instead provides strong context on when NOT to call: the side-effect block makes the cost explicit ('The only way not to clear the badge is not to call this tool'), which functions as clear usage guidance. Slight deduction for not naming an alternative, though no natural one exists.

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

linkedin_saved_jobsA

List the jobs you have bookmarked on LinkedIn.

Reads the Saved tab of your own job tracker: title, company, location, when it was posted where LinkedIn shows it, and the job link.

Read-only in both directions -- this lists what you saved and has no way to add to or remove from the list.

An empty result says so explicitly and carries LinkedIn's own count for the tab, so an empty list can never be mistaken for a read that failed.

The tracker also holds In Progress, Interview and Archived tabs. They are not exposed as tools: this reads the two lists it names and nothing else.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNomaximum rows to return (default 25, max 100).

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.3/5.0
Behavior5/5

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

With no annotations at all, the description carries the full disclosure burden and handles it well. It explicitly declares read-only behavior in both directions (covering what destructiveHint would normally signal) and gives a strong edge-case guarantee: an empty result is stated explicitly and carries LinkedIn's own count, so an empty list can never be misread as a failed call. This is the kind of behavioral detail agents genuinely need.

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

Conciseness3/5

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

The opening line is profitably front-loaded, but the body is somewhat verbose for a single-parameter list tool and contains an internal inconsistency: the last sentence says 'this reads the two lists it names' when only one list (the Saved tab) is actually named. Phrases like 'when it was posted where LinkedIn shows it' are clunky. The redundancy around read-only scope could be tightened.

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 simplicity (one optional parameter, no required inputs, an output schema present), the description is more than sufficient. It covers the safety profile, the exact scope, and the empty-result edge case, leaving no gap an agent needs to fill before calling it. The 'two lists' wording is a minor editorial slip, not a completeness failure.

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

Parameters3/5

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

The schema covers 100% of the single parameter (limit) with a complete description ('maximum rows to return (default 25, max 100)'). The tool description adds no parameter-level detail, which is acceptable since the schema already documents the limit fully and there are no other parameters to explain. Baseline 3 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 first line uses a specific verb and resource ('List the jobs you have bookmarked on LinkedIn') that clearly identifies this as the read counterpart to the save/unsave siblings. It names exactly what is returned (title, company, location, post date, link) and scopes itself to the Saved tab, distinguishing it from search and job-detail tools.

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 states the tool reads only the Saved tab and explicitly notes that In Progress, Interview and Archived tabs are 'not exposed as tools.' The read-only framing ('no way to add to or remove from the list') implicitly routes agents away from linkedin_save_job/linkedin_unsave_job, though it does not name those alternatives directly or give an explicit when-to-use statement.

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

linkedin_save_jobA

Bookmark one job posting on LinkedIn. Two steps, and the first is free.

THIS TOOL CHANGES SOMETHING ON LINKEDIN, which no other tool in this server does. It is the reason linkedin_server_info no longer reports read_only: true.

CALL IT WITHOUT confirm_token FIRST. Nothing is done: the posting and your own saved list are read live, and you get back a block naming the job by title and employer, saying which way the toggle would move, where each fact was read from, and how the action can be undone. Read it, then call again with the confirm_token it hands you.

The token works ONCE, only for this posting, only for this verb, and it expires in two minutes -- so a scheduled or unattended caller can never hold a live one. That is the intended consequence and not a side effect.

After the click the result is confirmed from a DIFFERENT surface: your saved list, with LinkedIn's own per-tab count, rather than from the button that was just pressed. performed comes back true, false, or "unknown"; on "unknown" do not retry, because a retry on a toggle that did land performs the opposite action -- look at your saved jobs instead.

ParametersJSON Schema
NameRequiredDescriptionDefault
job_idYesthe numeric LinkedIn job id, as it appears in /jobs/view/<id>.
confirm_tokenNoleave empty to preview. Pass the token from that preview to actually save.

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.8/5.0
Behavior5/5

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

With no annotations provided, the description carries the full burden of behavioral disclosure, and it excels: it states the tool mutates LinkedIn state, explains the preview/confirm two-step mechanism, describes the token's single-use, per-post, per-verb, two-minute expiry, and discloses that confirmation is read from a different surface (the saved list) rather than the button. It also explains the 'performed' result values and the risk of opposite actions on retry. This is exhaustive and transparent.

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 longer than typical, but it is well-structured and front-loaded: the opening sentence captures the core action and two-step nature. Subsequent paragraphs each add necessary detail (token lifecycle, confirmation source, retry warning) without redundancy. Every sentence serves a purpose; while verbose, it respects the complexity of the operation.

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 state-changing complexity and two-step flow, the description is exceptionally complete. It covers the preview, confirmation, token expiry, result semantics, retry avoidance, and even a fallback suggestion ('look at your saved jobs instead'). An output schema exists, but the description still clarifies the meaning of 'performed' and the confirmation pathway, leaving no critical operational detail 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?

Schema coverage is 100%, so the schema already documents both parameters. The description adds meaningful context for confirm_token—its lifecycle, the preview/confirm pattern, and that an empty value means preview—which goes beyond the schema. For job_id, it mostly repeats the schema's description but confirms it as a numeric ID. The added semantic depth for confirm_token raises the score above the baseline of 3.

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 explicitly states the verb 'Bookmark' and the resource 'job posting on LinkedIn', and immediately distinguishes this tool from all read-only siblings by noting it is the only one that changes something on LinkedIn. It also implicitly contrasts with the sibling 'linkedin_unsave_job' by describing the save action. 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 Guidelines5/5

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

The description gives a precise two-step procedure: call without confirm_token to preview, then call again with the token to save. It clearly states when to use the tool (when you want to save a job) and provides explicit warnings about token expiration, single-use, and when not to retry ('on "unknown" do not retry'). It also mentions the intended consequence of preventing unattended callers, further clarifying appropriate usage.

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

linkedin_search_jobsA

Search LinkedIn jobs with filters, returning one page of results.

Runs the search LinkedIn's own jobs page runs and reads the rendered results: title, company, location, job id and link.

One page load per call, no scrolling and no auto-paging -- LinkedIn puts roughly 25 results on a page, so ask for the next page deliberately with start=25, start=50 and so on. capped in the result tells you the limit trimmed the rows, and page_had tells you how many the page actually held.

Note that LinkedIn records searches in your own recent-search history, exactly as it would if you typed the query on the site. That is the only trace a search leaves, and it is on your account, not anyone else's.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNomaximum rows to return (default 25, max 50).
startNoresult offset for manual paging (0, 25, 50 ...).
remoteNoany | on_site | remote | hybrid.any
sort_byNorelevance | date.relevance
keywordsYeswhat to search for, e.g. "senior node.js engineer".
locationNocity, region or country. Empty means LinkedIn's default.
date_postedNoany | past_24h | past_week | past_month.any
experience_levelNocomma-separated from internship, entry, associate, mid_senior, director, executive. Empty means no filter.

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.4/5.0
Behavior5/5

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

With no annotations provided, the description carries the full disclosure burden. It exceeds expectations by detailing that each call is a single page load with no scrolling or auto-paging, that results are roughly 25 per page, and that capped/page_had indicate row trimming. Most notably, it discloses a non-obvious side effect: searches are recorded in the user's own LinkedIn recent-search history, leaving a trace on the account. This level of transparency about side effects and pagination mechanics 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.

Conciseness4/5

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

The description is structured into three focused paragraphs: purpose, pagination mechanics, and privacy note. It is front-loaded with the core purpose in the first sentence. Every sentence contributes either operational details (single page, start offsets) or behavioral transparency (capped, page_had, search history). It is concise relative to the amount of critical information it conveys, though slightly longer than the bare minimum. The structure makes the key usage guidance easy to find.

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 search tool with 8 parameters and an output schema, the description covers all necessary aspects: what the tool does, how it works, how to page for more results, what the returned fields include (title, company, location, job id, link), and the side effect on search history. The presence of an output schema means the return format needn't be detailed, and the description still names the key fields. Nothing essential is missing for an agent to call this correctly.

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

Parameters3/5

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

Schema description coverage is 100%, so the baseline is 3. The description does not add significant meaning beyond the schema: it mentions start for paging and limit indirectly through the pagination discussion, but the schema already documents defaults, enums, and descriptions for every parameter (keywords, start, limit, remote, etc.). The description's pagination explanation adds marginal value over the schema's 'result offset for manual paging' line, but not enough to raise the score above baseline.

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 crisp statement of function: 'Search LinkedIn jobs with filters, returning one page of results.' It then specifics the returned fields (title, company, location, job id, link) and indicates the search runs on LinkedIn's own jobs page. This clearly distinguishes it from sibling tools like linkedin_job_detail (which likely retrieves a single job) and linkedin_saved_jobs (which lists saved jobs). The purpose is unambiguous and specific.

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 provides explicit operational guidance: it explains the single-page behavior and instructs the agent to page deliberately using start=25, start=50, etc. It also explains the meaning of capped and page_had fields for interpreting results. However, it does not explicitly say 'use this instead of X' for any sibling tool, though the distinct search function is implied. The guidance is clear on how to use the tool for repeated paginated searches, which is the core usage pattern.

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

linkedin_server_infoA

Describe this server: what it can do, what it deliberately cannot.

Useful for confirming the read-only boundary and the rate settings without reading the source.

version and build.code.commit are two different facts and both are reported. version is a HAND-MAINTAINED label: it says what this server calls itself, and it keeps saying it whether or not anybody remembered to bump it. build.code.commit is MEASURED -- it is the commit this process was imported from, read once at import and frozen.

WHAT TO DO WITH IT. A fix committed to disk changes nothing for a server that is already running. To tell "the fix is not loaded" from "the fix is wrong", compare build.code.commit against git rev-parse HEAD in the checkout::

git -C <this checkout> rev-parse --short=12 HEAD

They MATCH -> the running process holds that commit, so a bug you can still reproduce is a real bug. They DIFFER -> the process is STALE and no further committing will change its behaviour until the MCP client restarts it. build.code.dirty says whether the working tree had uncommitted changes when this process started, so a matching commit with dirty: true means the commit is necessary but not sufficient to describe what is loaded. build.process.started_at dates the answer.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.6/5.0
Behavior5/5

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

With no annotations, the description fully carries the burden. It discloses that version is a hand-maintained label while build.code.commit is measured and frozen at import, distinguishes between stale and current processes, explains how to compare against git rev-parse HEAD, and covers the dirty flag and started_at field. This goes well beyond what annotations would provide and gives the agent complete behavioral expectations.

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 front-loaded with the main purpose and then dives into detailed interpretation of version/build fields. It is well-structured with a clear flow, but it is somewhat verbose, especially the code block and extended explanation of staleness. Every sentence adds value, so it is not padded, but it could be tightened without losing essential meaning.

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?

The description is extraordinarily complete for a no-parameter tool. It explains the meaning of each output field (version, build.code.commit, dirty, started_at), how to interpret them together, and even provides a practical procedure (git rev-parse HEAD) for diagnosing stale servers. Nothing an agent needs to call or interpret this tool 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, so the schema is trivially complete (100% coverage). Per the rubric, a baseline of 4 applies, and the description adds no parameter detail (none is needed). No deduction is warranted.

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 "Describe this server: what it can do, what it deliberately cannot," a specific verb/resource pair that clearly distinguishes this tool from siblings like linkedin_auth_status or linkedin_session_info. It states the exact scope (server capabilities, read-only boundary, rate settings) and the primary output (version and build information). No ambiguity about what this 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 explicitly states when to use it ("Useful for confirming the read-only boundary and the rate settings without reading the source"). It stops short of naming alternatives or explicit when-not-to-use conditions, but the context of the sibling list makes the intended use clear. A 5 would require an explicit comparison to alternatives, which is missing.

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

linkedin_session_infoA

Report whether the session is live and how long it has left.

This is the question that comes up after a week away: is the sign-in still good, and when does it lapse? The verdict is the same measured one linkedin_auth_status gives -- an authenticated call to the identity endpoint, never a cookie's presence -- and alongside it comes the expiry date read from the browser profile's own cookie jar.

The sign-in lives in an on-disk Chrome profile rather than in this process, so it survives this server restarting and the machine rebooting. What ends it is LinkedIn expiring it, a sign-out, or the profile directory going away.

When no browser can be started at all -- Chromium missing, another process holding the profile -- this tool does not die with it. It falls back to reading the expiry dates out of that profile's cookie jar on disk, which is precisely the moment you most want to know whether the login survived. Then 'authenticated' is null and the live_check block says why in plain words: a cookie in the jar is not a session, and reporting one as the other is a lie this server refuses to tell. Two labelled fields, never one blurred one.

What comes back, block by block, so a caller is never guessing:

  • credential -- li_at, the one cookie that authenticates here. Its name, whether it is there, its expiry, and expiry_source naming which route produced that date: the live browser's jar, or the on-disk jar read with no browser.

  • supporting -- JSESSIONID, role csrf. Not a second credential: it cannot sign anything in, it only governs whether the identity call can be made at all. It dies with the browser and a fresh one arrives on the next page load, so it having lapsed means nothing on its own.

  • renewal -- silent_renew_available is false here, and why says what the four servers in this family were ruled on: there is one credential layer, so a linkedin_reauth would be linkedin_login_browser wearing a different name and it is deliberately not shipped. It also carries session_lapses_at / _in_days: the date past which no silent renew can help and you sign in by hand. THAT is the number to compare against a sibling server, not credential.expires_at -- a server that re-mints its own credential shows hours there while its session holds for months. On LinkedIn the two coincide, because nothing here can carry the session past the cookie, and session_lapses_source says so. uses_browser is null rather than false -- there is no renewal here to characterise, and a false would claim one exists and happens to need no browser -- while mechanism spells out what recovery actually costs: a real window and your own hands, never a background refresh.

  • durability -- where the sign-in is kept and what it survives.

Cookie values are never returned. Only the name, whether it is there, and when it lapses. When it has lapsed every read tool says so with a reason rather than handing back nothing, and linkedin_login_browser is the way back.

(Everything below this point is dropped from the description a caller sees: FastMCP cuts a docstring at Args: and renders the rest into the argument schema. Prose that has to reach a caller goes ABOVE it.)

ParametersJSON Schema
NameRequiredDescriptionDefault
verify_liveNoput the question to the identity endpoint for a real verdict, which requires a working browser. Pass False for the free, browserless answer -- jar facts only, 'authenticated' null. Default True.

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.6/5.0
Behavior5/5

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

With no annotations, the description carries the full burden and delivers heavily: it discloses the measurement method (identity endpoint, never cookie presence), the persistence of the session in an on-disk Chrome profile, the fallback to reading the jar without a browser and the resulting null 'authenticated', the fact that cookie values are never returned, and the semantic of renewal (silent_renew_available false, costs manual sign-in). This is extensive behavioral disclosure beyond the basics.

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

Conciseness2/5

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

The description is extremely verbose. It includes narrative prose ('This is the question that comes up after a week away...'), extensive block-by-block explanations, and a meta-note about FastMCP cutting the docstring. While structured with headings, the length is excessive for an AI caller; many sentences could be trimmed without losing essential information. The front-loaded purpose is clear, but the overall bulk detracts from usability.

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 no annotations and a fairly complex behavior (fallback, multiple output fields, nuanced semantics), the description is extremely thorough. It explains each output block, the meaning of null, the reasons behind renewal limitations, and the durability of the session. An agent would have all necessary context to call it correctly, even without seeing the output schema (which exists but is not needed to understand the tool).

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 schema already covers the only parameter (verify_live) with a clear description, so the baseline is 3. The description adds value by explaining the trade-offs in context: it details what True vs False means for the live_check block, the fallback when no browser is available, and the null semantics. This enriches the parameter's meaning beyond the schema's concise text.

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 opening sentence clearly states the tool's purpose: 'Report whether the session is live and how long it has left.' It goes further to differentiate from linkedin_auth_status by specifying that it uses the same identity-endpoint verdict but also includes the expiry date from the cookie jar, and it names linkedin_login_browser as the recovery path. This makes the resource and scope unambiguous.

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?

The description explicitly references alternatives and when to choose them: it says the verdict is the same as linkedin_auth_status, points out that linkedin_login_browser is the way back after lapse, and explains the fallback behavior when no browser can be started. It also tells when to set verify_live True vs False. This gives clear decision rules for an agent.

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

linkedin_unfollow_companyA

Stop following one company Page. Two steps, and the first is free.

Same two-step shape and the same five gates as linkedin_save_job. What differs is worth reading before you use it, because two of the differences change what an answer from this tool means.

IT IS ADDRESSED BY THE NUMERIC COMPANY ID, NOT BY NAME. Call linkedin_followed_companies first: it prints the id beside each Page. A name is refused outright -- names collide, they change, and they belong to somebody else -- and the click is anchored to the row carrying the id, so the thing you name and the thing that gets pressed are the same row by construction. The preview still prints the NAME, because an id is not something a person can check.

THE LIST IS NEVER COMPLETE, AND THAT IS THE IMPORTANT ONE. LinkedIn renders about twenty rows of however many you follow, and offers no way to page through the rest. So a Page that is not in the rendered rows comes back "unknown" rather than "not followed", and the preview refuses rather than guessing. If the company you want is not reachable, this tool will say so instead of doing nothing quietly.

Confirmation after the click is read by RELOADING the same list -- there is no second surface that lists followed Pages -- and the verdict rests on LinkedIn's own stated total dropping by one, not on the row having vanished. On a partial list an absent row is not evidence.

THE PAIR IS ASYMMETRIC ON PURPOSE: this server can stop a follow and cannot start one. linkedin_follow_company is specced and is not performed.

ParametersJSON Schema
NameRequiredDescriptionDefault
company_idYesthe numeric LinkedIn company id, as printed by ``linkedin_followed_companies``.
confirm_tokenNoleave empty to preview. Pass the token from that preview to actually unfollow.

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.9/5.0
Behavior5/5

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

With no annotations provided, the description carries the full burden and excels at it. It discloses the two-step confirmation flow, the lack of pagination in the followed-companies list and its consequences, the confirmation method (reloading the list and checking the total count drop), and the 'unknown' vs 'not followed' distinction. It even explains why a partial list makes absent rows unreliable evidence. This is exemplary behavioral disclosure.

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 long, the description is tightly structured and every sentence earns its place. The key warnings are front-loaded and emphasized with caps, and the paragraphs each address a distinct behavioral aspect. It is verbose only where necessary; the complexity of the tool justifies the length, and the structure makes it 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?

For a tool with an output schema and non-trivial behavioral caveats, this description is complete. It covers prerequisites (get the ID), limitations (list is partial), verification (count drop), and side-effect asymmetry. Nothing an agent needs to call it correctly or interpret results 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?

Schema coverage is 100%, so the parameters are already documented. The description adds contextual meaning: company_id is clarified as the numeric id printed by the followed list (not the name), and confirm_token is explained as the preview vs final confirmation flow, with an empty value meaning preview. This goes beyond the schema's basic 'leave empty to preview' by tying it into the overall two-step process, justifying a 4 over the baseline 3.

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 precise verb+resource ('Stop following one company Page') and immediately differentiates from siblings by referencing the shared two-step shape and five gates, and by naming the asymmetry with linkedin_follow_company. It leaves no ambiguity about what the tool does and how it differs from related tools.

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?

The description gives explicit when-to-use guidance: call linkedin_followed_companies first to obtain the numeric ID, explains that names are refused, and details the list incompleteness that makes a 'unknown' verdict possible. It also states the asymmetry (can unfollow but not follow) and directs to the unimplemented follow tool for the inverse, leaving no room for misuse.

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

linkedin_unsave_jobA

Remove one job posting from your saved list. Built, gated, and refusing.

Same two-step shape as linkedin_save_job and the same gates, with ONE honest difference that this docstring will not bury: THIS TOOL CANNOT PERFORM ANYTHING TODAY, and it is not because the code is missing.

LinkedIn labels the save control by its accessible name, and the name it wears when a posting IS saved has never been observed on this account -- there has been nothing saved on it to observe. Every capture this repo holds shows the unsaved state. So the selector an unsave would click is unknown, and this server will not guess one: "Saved" and "Unsave the job" are both plausible spellings and it has seen neither.

THE FIX IS ONE MEASURED LINE, and the first supervised save produces it -- linkedin_save_job reads back the label the control changes into and reports it. Until that label is written down, this refuses with that explanation rather than clicking something it hopes is the right button.

A preview may also be unrenderable for a second and unrelated reason: an unsave is only valid on a posting that is currently saved, so with an empty saved list there is nothing to preview it against. The preview says so.

ParametersJSON Schema
NameRequiredDescriptionDefault
job_idYesthe numeric LinkedIn job id, as it appears in /jobs/view/<id>.
confirm_tokenNoleave empty to preview. A token is accepted but the action behind it will refuse until its anchor has been measured.

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A3.8/5.0
Behavior5/5

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

With no annotations supplied, the description carries the full burden of behavioral disclosure. It is exceptionally transparent: it confesses 'THIS TOOL CANNOT PERFORM ANYTHING TODAY,' explains the technical reason (unknown accessible name), details the preview failure condition, and states it will refuse rather than guess. This level of honest disclosure 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.

Conciseness2/5

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

The description is a multi-paragraph essay covering the tool's broken state, future fix, and preview behavior. While informative, it is verbose and could be condensed into a few sentences. An MCP description should be concise; this one isn't.

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 has an output schema and no annotations, the description must convey behavioral context. It thoroughly covers why the tool refuses, how preview behaves, and the condition for it to work, leaving no ambiguity about what will happen when called. It is complete for an agent to handle correctly.

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

Parameters3/5

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

Schema coverage is 100% – both parameters (job_id and confirm_token) are fully described in the input schema, including the preview vs. token behavior. The description adds no additional parameter-level information, which is acceptable given the schema already covers semantics. Baseline 3 applies.

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 opens with 'Remove one job posting from your saved list,' which is a clear verb+resource statement. It implicitly differentiates from linkedin_save_job by being the inverse operation, though it doesn't explicitly compare with siblings. The core purpose is unambiguous despite the lengthy caveats about the tool being non-functional.

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 implies usage when a user wants to unsave a job and explicitly states the tool cannot currently perform the action and will refuse until a label is measured. It also mentions the preview limitation based on having a saved job. However, it doesn't explicitly state when not to use it or mention alternatives, though the refusal condition is clear.

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

linkedin_who_viewed_meA

List the people who viewed your profile, most recent first.

The highest-intent signal in a job search: someone who opened your profile has already spent attention on you. Where the account has Premium Career, this list reaches back 365 days rather than the free tier's five viewers.

Rows carry name, headline, when the view happened, and a profile link.

Viewers browsing with limited visibility appear exactly as LinkedIn shows them to you and no more: "Someone at Acme", "Recruiter at Acme", with a date and no link, flagged "anonymous": true. They are the majority of a typical list and often the most useful part of it -- a recruiter's view is a recruiter's view whether or not it comes with a name. This server makes no attempt to work out who they are, and there is no code here that could: nothing is fetched about any viewer, and no viewer's profile is ever opened. What you get is the row LinkedIn already put on your screen.

Reads the Premium analytics page. The older /me/profile-views/ address now redirects to that same page, so the second attempt is a re-load for a page that had not finished rendering rather than a different surface; it still reports pages_loaded: 2 when it happens.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNomaximum rows to return (default 25, max 100).

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.4/5.0
Behavior5/5

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

With no annotations provided, the description takes full responsibility for behavior. It discloses that some viewers appear as anonymous with limited info, states the server makes no attempt to identify them and fetches nothing about viewers, mentions the data source (Premium analytics page), and explains the redirect behavior and resulting pages_loaded count. This is exceptionally transparent.

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 longer than average, but it is front-loaded with the core purpose and organized into informative paragraphs. Each segment adds value: the high-intent context, anonymous viewer behavior, technical data source, and the redirect edge case. It is structured logically and avoids redundancy, though it could be tightened without losing clarity.

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 simplicity (one parameter, no nested objects) and the presence of an output schema, the description still covers all essential behavioral context: the kind of rows returned (name, headline, when, link), anonymous flagging, the data source, and the redirect quirk. It even explains what the server does not do. This is complete for an agent to use correctly.

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

Parameters3/5

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

Schema description coverage is 100%, so the baseline is 3. The sole parameter 'limit' is already fully described in the schema with default and max values; the tool description adds no additional semantic meaning or usage nuance beyond that. Thus it meets the baseline without needing extra explanation.

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 'List the people who viewed your profile, most recent first' — a precise verb and resource. It clearly distinguishes from sibling tools like linkedin_search_jobs or linkedin_my_applications by specifying the unique action of listing profile viewers, and provides additional context about the 'highest-intent signal in a job search.'

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 provides strong contextual guidance on when this tool is valuable ('the highest-intent signal in a job search' and notes that anonymous viewers are often the most useful part), implying usage in recruiter outreach scenarios. However, it does not explicitly exclude or compare against sibling tools, so it falls short of a 5 but is above a 3 due to the clear situational context.

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. 17 tool updatesv0.1.0
    • First observedlinkedin_auth_status
    • First observedlinkedin_cdp_status
    • First observedlinkedin_followed_companies
    • First observedlinkedin_job_detail
    • First observedlinkedin_login_browser
    • First observedlinkedin_logout
    • First observedlinkedin_my_applications
    • First observedlinkedin_my_profile
    • First observedlinkedin_notifications
    • First observedlinkedin_save_job
    • First observedlinkedin_saved_jobs
    • First observedlinkedin_search_jobs
    • First observedlinkedin_server_info
    • First observedlinkedin_session_info
    • First observedlinkedin_unfollow_company
    • First observedlinkedin_unsave_job
    • First observedlinkedin_who_viewed_me

TDQS

A4.2/5.0

Scored across 17 tools

Disambiguation5/5

Each tool targets a distinct action or query: auth status, session details, login, logout, CDP diagnostics, profile viewers, applications, saved jobs, job search, job detail, followed companies, own profile, notifications, save job, unsave job, unfollow company, server info. Even related tools like auth_status and session_info are clearly differentiated by scope.

Naming Consistency4/5

All tools begin with 'linkedin_' and use snake_case, but the pattern mixes noun phrases (linkedin_my_profile, linkedin_notifications) with verb-noun (linkedin_search_jobs, linkedin_save_job) and state queries (linkedin_auth_status). While consistent in prefix, the verb usage is not uniform, though still readable.

Tool Count4/5

17 tools is slightly above the ideal 3-15 range but each serves a distinct purpose, from session management to job and company interactions. The count is justified by the breadth of LinkedIn functionality covered, though a few are diagnostic or advisory.

Completeness3/5

The server covers the main job-search workflow (search, detail, saved, applications) and profile viewing, but has notable gaps: no way to follow a company (only unfollow), and the unsave_job tool is explicitly non-functional. Additionally, there is no tool for applying to jobs or editing profile, leaving some expected actions absent.

Maintenance

ActivityActive
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    B
    quality
    C
    maintenance
    Enables AI agents to manage LinkedIn profiles, posts, connections, skills, education, and certifications through the LinkedIn API.
    18
    176 npm
    64
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables AI assistants to connect to LinkedIn, accessing profiles and companies, searching for jobs and people, managing saved jobs, updating job-search profile settings, and inspecting analytics.
    1
    Apache 2.0
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables natural language interaction with LinkedIn, including profile retrieval, job searching, messaging, and post engagement.
    3
    Apache 2.0