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_detailapply_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_scriptreadonly.py에 두 번 나타나는데, 둘 다 그러한 호출을 탐지하기 위한 스캐너eigen pattern입니다. 스캐너는 자기가 금지하는 것을 이름으로 검열하며, 그래서 스캐너 소스에는 그런 코드 조각이 존재하고 나머지 패키지에는 없는 것입니다. partition_mutation_hits는 그 사실을 독립적으로 확인합니다. 허용된 변경 호출 하나, 허용되지 않은 변경 호출은 0건입니다.

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

  • 타이밍은 고정되어 있고 인간처럼 조작되지 않습니다. 모든 지연은 상수입니다. import random0번 나타납니다. 페이지 로드 사이의 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이 탭별로 보여주는 카운트가 있는, 여러분의 저장 목록에서 말입니다. performedtrue, 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이 탭별로 추산해 주는 내 저장 목록에서 말입니다. performedtrue, 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_serverscripts/_probe_apply_flow.py는 LinkedIn이 호스트하는 플로우를 캡처하고 기존 캡처마다 빠져 있는 그 바로 그 컨트롤들 — form, file input, 다이얼로그, screening 질문, 제출하는 컨트롤 — 을 정확하게 인벤토리로 만든다. 이 플로우에 도달하는 방식은 클릭이 아니라 내비게이션이다(LinkedIn은 apply 컨트롤을 링크로 그린다). 그리고 이 패키지 자체의 mutation 스캐너는 이 스크립트에서 변이 호출이 0개임을 발견하며, job id는필수 인자로 받아서 어떤 기본값도 사용자 대신 어떤 채용 공고를 골라 주는 일을 없다. 왜냐하면 Easy Apply 평가 시 문서 초안(draft)이 생성될 수 있기 때문이다 — 이는 가설이며, 아직 어무도이 검증하지 못했고, 그렇게 라벨되어 있고 가정이 아니라 측정된 것으로 취급합니다.

그것은 실행되지 않았다. 누군가 지켜보는 누군가 있는 상태에서, apply_pathlinkedin_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_urlpage.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.setItemfetch(를 끼워 아주 기존의 호출부를 통해 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_infoli_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/meli_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

오류: 이름을 읽을 수 없음

h10개인 페이지에서 이름, 부제목, 위치, 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) 행의 모든 위치에 주입하고, 결과가 움직이지 않기를 요구합니다. 그리고 대조군을 통해 앵커를 제거하면 같은 주입이 필드를 깨뜨리는 것을 보여줍니다.

Install Server
F
license - not found
A
quality
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

View all related MCP servers

Related MCP Connectors

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Sundeepg98/linkedin-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server