linkedin-mcp
linkedin-mcp
Сервер Model Context Protocol, который управляет вашим собственным вошедшим в систему сеансом LinkedIn в реальном браузере Chromium, с явным шагом подтверждения перед каждой операцией записи.
⚠️ Сначала прочтите это
Это не обычный API-клиент. Пожалуйста, прочитайте всё это до установки.
Он управляет реальным, вошедшим в систему браузерным сеансом от вашего имени. Сервер запускает Chromium, восстанавливает сохранённые cookie-файлы LinkedIn и нажимает те же кнопки, что нажал бы человек.
Он не использует официальный API LinkedIn. За ним нет OAuth-приложения, партнёрского соглашения и поддерживаемой интеграции. Это браузерная автоматизация публичного сайта.
Автоматизация LinkedIn таким способом действует за пределами вашего аккаунта с пользовательским соглашением LinkedIn. Условия LinkedIn запрещают скрейпинг и автоматический доступ к сайту.
Ваш аккаунт может получить rate-limit, быть ограниченным или заблокированным навсегда. Этот риск реален и не является гипотетическим. LinkedIn обнаруживает автоматизацию, и принудительные меры могут прийти без предупреждения и без возможности обжаловать.
Cookie-файлы вашего сеанса хранятся на этой машине. После успешного входа Playwright записывает файл
storageStateна локальный диск. Любой, кто может прочитать этот файл, сможет действовать на LinkedIn от вашего имени.Использование происходит полностью на риски владельца аккаунта. Здесь нет никаких гарантий, явных или подразумеваемых, и нет возможности восстановить аккаунт после блокировки.
Из вышеперечисленного следует три проектных решения, которые невозможно настроить:
Один аккаунт, один пользователь. Сервер имеет один каталог состояния с одним сохранённым сеансом. Нет поддержки нескольких аккаунтов и нет понятия «пользователи» — это локальный инструмент для человека, сидящего за клавиатурой.
Каждое действие записи требует явного подтверждения. Публикация поста, отправка запроса на соединение, сообщения и отклики на вакансии — всё это процесс из двух вызовов. Один вызов инструмента никогда не может ничего отправить в LinkedIn. См. следующий разделу.
Сервер никогда не будет решать CAP-КА. Сервер не будет вводить ваш пароль, не будет проходить 2FA-запрос и не будет пытаться обойти контроль безопасности. Когда LinkedIn вызывает защитную проверку, сервер останавливается и просит вас пройти её в обычном браузере.
Если что-то из этого неприемлмо для вашего аккаунта, не устанавливайте этот сервер.
Related MCP server: LinkedIn MCP Server
Как поддерживать функ "подтверждение перед выполнением"
Каждый инструмент записи (linkedin_create_post, linkedin_send_connection_request, linkedin_send_message, linkedin_apply_to_job) — это ракопожатие из двух вызовов:
Вызов 1 — предпросмотр. Вызовите инструмент с реальными аргументами и без confirm. Сервер проверяет страницу, выясняет, что именно произошло бы, и возвращает обработчик предпросмотра. Ничего не отправляется, и ваша дневная квота не тратится.
{
"profileUrl": "https://www.linkedin.com/in/dana-whitfield-example/",
"note": "Hi Dana — we both worked on the OpenTelemetry collector. Would like to connect."
}Вызов 2 — подтверждение. Повторяте тот же вызов с указанием confirm: true. При желании укажите previewToken, который вы получили; если сделать это, сервер проверяет, что аргументы не изменились с момента предпросмотра, и при изменении возвращает confirmation_mismatch.
{
"profileUrl": "https://www.linkedin.com/in/dana-whitfield-example/",
"note": "Hi Dana — we both worked on the OpenTelemetry collector. Would like to connect.",
"previewToken": "9f2c41ab77e05d13",
"confirm": true
}Обёртка предпросмотра выглядит так (реальный предпросмотр linkedin_send_connection_request):
{
"status": "preview",
"action": "linkedin_send_connection_request",
"executed": false,
"confirmationRequired": true,
"summary": {
"profileUrl": "https://www.linkedin.com/in/dana-whitfield-example/",
"name": "Dana Whitfield",
"headline": "Staff Engineer, Observability",
"note": "Hi Dana — we both worked on the OpenTelemetry collector. Would like to connect.",
"noteLength": 88,
"notePreview": "Hi Dana — we both worked on the OpenTelemetry collector. Would like to connect.",
"connectionDegree": 2,
"connectionDegreeLabel": "2nd",
"connectPathway": "direct",
"alreadyPending": false,
"wouldSucceed": true
},
"previewToken": "9f2c41ab77e05d13",
"quota": {
"action": "connectionRequests",
"used": 3,
"cap": 20,
"remaining": 17,
"resetsAt": "2026-08-25T07:00:00.000Z",
"allowed": true
},
"warnings": [],
"howToConfirm": "Nothing has been sent to LinkedIn yet. To execute, re-issue the exact same `linkedin_send_connection_request` call with `confirm: true` (optionally echoing `previewToken: \"9f2c41ab77e05d13\"` so the arguments are verified as unchanged)."
}Замечания по чтению предпросмотра:
executed: falseиconfirmationRequired: trueприсутствуют всегда в предпросмотре. Результат выполнения вместо них содержит"status": "executed","executed": trueи объектresult.В
warningsсервер сообщает о всех волнующих его событиях — усечённый пост, слишком длинное примечание к запросу на соединение, исчерпанная дневная квота, режим dry-run.Если предпросмотр говорит, что вызов не может быть успешным (пользователь уже в контактах, приглашение уже было отправлено, нет Controls для Connect, вы уже 1-й степени связи),
howToConfirmпрямо это указывает, и подтверждение будут отклонено сinvalid_inputбез расходования квоты.previewToken— это короткий дайджест имени действия и канонизированной копии полезной нагрузки. Это защита от расхождений, а не защитный токен.
Требования
Node.js 20 или новее (
"engines".{ "node": ">=20" }).Desktop-окружение с возможностью открыть видимое окно браузера — интерактивный вход без него невозможен.
Собранная для Playwright сборка Chromium (устанetype ниже).
Установка
Установите зависимости.
npm installСкачайте Chromium-сборку, которую использует Playwright:
npx playwright install chromiumСоздайте файл окружежра (кажда переменная в нёмля, и он не хдранит учётные аднные):
cp .env.example .envСоздайте файл конфигурации (только дневные лимиты и таймаууты):
cp config.example.json config.jsonСкомпилируйте TypeScript в папку dist/:
npm run buildПервый запуск, по порядку
Ничто здесь не изменяет ваш аккаунт LinkedIn до самого последнего шага.
npm installnpx playwright install chromiumcp .env.example .envandcp config.example.json config.json— оба необязательны, и ни один из них не содержит учётные данныеnpm run buildnpm test— 75 герметичных модульных тестов; без браузера, без сетиnpm run verify:dry— проверBuyет все 11 инструментов на локальных тестовых данных, чтобы вы знать, что связка работает исправно после подключения к своему аккаунту (подробности—linkedin))Зарегистрируйте сервер в своём MCP-клиенте, первым с
--dry-runвargs(подробности). Убедитесь, что клиент показывает 11 инструментов и что инструмент записи возвращает предпросмотр.Удуйте
--dry-runизargs, перезапустите клиент и вызовитеlinkedin_login(подробности). Этот шаг уже обращается к linkedin.com. Открывается видимое окно Chromium и вы входите вручную.Вызовите
linkedin_session_status, чтобы убедиться, что сохранённый сеанс работает.
Шаг 8 — это граница. Всё до него может одатить удаление каталога.
Вход в систему
В этом проекте нет конфигурации учётных данных — это сделано намеренно. Вы входите вручную, один раз, в реальном браузере.
Вызовите инструмент
linkedin_login. Он не принимает аргументов.Открывается реальное видимое окно Chromium со страницей входа LinkedIn. Оно видно даже при конфигурации
headless: true; интеракитивный вход всегда принудительно открывает окно браузера.Вы вводизв email и пароль, и вы проходитепроверки LinkedIn — SMS или код из аутентификатора, PIN-код из электронной почты, подтверждение устройства, CAPTCHA. Сервер не вводичит учётные данные, не читает поле пароля и не трогает элементы проверки. Он только каждые две секундыone poll, ожидая обнаружения входа в элемента навигации LinkedIn.
Не спешите. Ожидание ограничено таймаутом
loginTimeoutMs, по умолчанию300000(пять минут). Если нужно больше, повысьте его вconfig.json.Как только LinkedIn покажет ленту пользователя, браузерный сеанс будет сохранён в файл
storageState.jsonв каталоге состояния, с правачном файла0600(доступ только на чтение и запись владельцу). Этот файл игнорируется Git.Каждый последующий вызов инструмента использует этот сохранённый сеанс заново. Вам, скорее всего, не понадобится входить несколько недель.
Проверить состояние сеанса вызовом linkedin_session_status (без аргументов). Он один раз загружает ленту и отдаёт отчёт:
{
"valid": true,
"lastVerified": "2026-08-24T18:42:10.114Z",
"sessionSavedAt": "2026-08-11T09:03:55.002Z"
}When he reports valid: false, in the result there is reason (for example, that LinkedIn redirected the feed to login page itself). No cookie or session data is ever returned neither by this tool nor any other.
Сеансы истекают. Когда он истекает, инструменты начинают завершаться с session_expired, и решение всегда одинаково: снова запустите linkedin_login и войдите вручную.
Rate limits и скорость работы
Сервер ограничивает свой собственный дневной потолок и вставляет рандомизированную задержку перед действиями в браузере. Это самая важная защита вашего аккаунт.
скопируйте config.example.jsonвconfig.json` и отредактируйте:
{
"dailyCaps": {
"connectionRequests": 20,
"messages": 30,
"posts": 5,
"jobApplications": 10
},
"delayRangeMs": {
"min": 1500,
"max": 6000
},
"headless": false,
"navigationTimeoutMs": 30000,
"actionTimeoutMs": 15000,
"loginTimeoutMs": 300000
}Четыре дневных лимита
Cap | Что ограничивает | По умолчанию |
| Приглашения отправлены через | 20 / день |
messages | сообointmentsОтправлено mens through через | 30 / день |
posts | Posts published through | 5 / день |
jobApplications | Applications submitted by linkedlinkedin_apply−to−job | 10 / day |
Their behavior:
Лимит расходуется только при подтверждённом действии. Предпросмотрбащает текущая квота, но его не тратит; вызов, который сервер отклонил outright, также не расходует квоту.
Счётчики хранятся в
counters.jsonв каталоге состояния, поэтому перезапуск сервера не обнуляет их.Когда лимит исчерпан, инструмент завершается с ошибкой
rate_limitedвместо выполения действия. Установка лимита в0полностью отключает это действие.Каждая обёртка предпросмотра и выполнения содержит блок
quotaс полямиused,cap,remaining,resetsAtиallowed.
Случайная задержка
MS — это диапазон, сколько сервер спит равномерно рандомным образом перед действиями браузера — по умолчанию 1500 ms to6000 ms. Если случайность не нарушается: фиксированный интервал — это почерк машины. min может быть равно max (фиксированная задержка), однако min не должен превыстать max или загрузка конфигурации завершится с config_invalid.
Лимиты сбрасываются в локальной полуночь
Счётчики привязаы к локальной календарной дате, так что все четы лимита сбрасьваются в полуночь в собственном временном поясе — не в UTC полуночь и не по 24-часовому окотда. resetsAt в обёртке quota указывает эту следущую лоcalную полуночь, представленную как временная метка UTC ISO.
Начание с значения же, чем по-умочанию
Поставляемые по умчанют постановки — это потолок, а не рекомендация. Если ваш аккаунт новый, с мало контакми или никоlда не автоматиззовался, начинайте с намного более низких значных — например connectionRequests: 5, messages: 5, posts: 1, jobApplications: 2 — с повышинем их медленно в течение недель, слушая предупреждения LinkedIn. Всплеск активности, которая вовсе не похожа на ваш обычный паттов, — это как раз то, с чего начинаются ограничения на аккааунт.
Справочник по инструментам
11 инструментов, в порядк, в котором их регистрирует сервер.
Четыре инструмента сбора данных/выкансиях ниже (
linkedin_scrape_profile,linkedin_scrape_feed,linkedin_search_searchjobs, присfrom a common сontract insrc/types.tsandsrc/selectors.ts. Если output your client'stools/listis disagree with an argument name here,tools/listis authoritive — the server truly в provided its real schema.
linkedin_login
Opening a visible Chrome window and waits until you sign in, include any 2FA or CAPTCHA. On succss, save the session to disk. **takes no args. Not available in --dry-run **.
{}linkedin_session_status
Сообщает, действует ли сохранённый сеанс, когда он был сохранён и когда была проведена последняя проверка. Загружает ленту один раз. Только чтение. В режиме --dry-run всегда valid: true. Не принимает аргументов.
{}linkedin_create_post (запись — требуется подтверждение)
Публикует текстовый пост в ваш feed. Расходует дневной лимит posts.
Предпросмотр:
{
"text": "Spent the week reading Playwright's tracing internals. Notes soon.",
"visibility": "connections"
}Подтверждение:
{
"text": "Spent the week reading Playwright's tracing internals. Notes soon.",
"visibility": "connections",
"confirm": true
}text— обязательно, от 1 до 3000 символов. Если длина превышает ~1300 симвволов, LinkedIn сворачивает пост под кнопку «see more»; предпросмотр покажет предупреждение.visibility— необязательно,"public"или"connections". По умолчанию"public".mediaUrl— необязательный URL. Сервер никогда не загружает удалённые мультифайлы из интернета. ПередачаmediaUrlвызывает предупреждение при предпросмотре и будет отклонено сinvalid_inputпри подтверждении.previewToken— необязательно; передайте его обратно, чтобы сервер свернул, что аргументы не изменились.confirm— необязательный boolean; должен быть ровноtrueдля выполнения.
linkedin_send_connection_request (запись — требуется подтверждение)
Отправляет приглашение соединиться, optionally with note. Расходует дневной лимит connectionRequests.
Предпросмотр:
{
"profileUrl": "https://www.linkedin.com/in/dana-whitfield-example/",
"note": "Hi Dana — we both worked on the OpenTelemetry collector. Would like to connect."
}Подтверждение:
{
"profileUrl": "https://www.linkedin.com/in/dana-whitfield-example/",
"note": "Hi Dana — we both worked on the OpenTelemetry collector. Would like to connect.",
"previewToken": "9f2c41ab77e05d13",
"confirm": true
}profileUrl— обязательное непустое поле. Полный URL профиля или короткий vanityдомен.note— необязательно, не более 300 символов. Примечания длиннее 200 символов обычно max. Longnoteтребует Premiumли плохого аккаунта, поэтому предпросмотр предупреждает при > 200.previewToken,confirm— поэтому.
Отказ с invalid_input перед то, как стратить квоту, если пользовать уже является контактом первого уровня, у вас уже есть приглашение, либо страница не содержит вообще elementа For Connect.
linkedin_send_message (запись — требуется подтверждение)
Отправляет личное сообщение. Расходует квоту messages.
Предпросмотр:
{
"profileUrlOrConversationId": "https://www.linkedin.com/in/dana-whitfield-example/",
"text": "Thanks for the pointer to the collector RFC — that answered my question."
}Подтверждение:
{
"profileUrlOrConversationId": "https://www.linkedin.com/in/dana-whitfield-example/",
"text": "Thanks for the pointer to the collector RFC — that answered my question.",
"confirm": true
}profileUrlOrConversationId— обязательный, непустой. Может быть URL/слагом профиля либо идентификатором существующей ветки переписки (как в/messaging/thread/<id>/).text— обязательный, от 1 до 8000 символов.previewToken,confirm— как выше.
В режиме профиля получатель обязан быть контактом первой степени; всем остальным будет отказано с кодом not_connected, и только после этого квота не тратится. Этот инструмент никогда не отправляет InMail и никогда не нажимает Enter в редакторе сообщения — он нажимает кнопку Send.
linkedin_scrape_profile
Читает один профиль и возвращает структурированный объект Profile: имя, заголовок, раздел «О себе», местоположение, степень связи, признак того, является ли это ваш собственный профиль, записи об опыте, записи об образовании и навыки. Только чтение.
{
"profileUrl": "https://www.linkedin.com/in/dana-whitfield-example/"
}linkedin_scrape_feed
Читает свежие публикации из вашей ленты и возвращает элементы FeedPost: имя автора и его заголовок, текст, URL публикации, количество лайков и комментариев, время публикации. Только чтение.
{
"count": 20
}linkedin_search_jobs
Выполняет поиск вакансий в LinkedIn и возвращает элементы JobListing: jobId, название, компанию, местоположение, является ли вакансия Easy Apply, и URL вакансии. Только чтение.
{
"keywords": "site reliability engineer",
"location": "Berlin, Germany",
"easyApplyOnly": true,
"count": 25
}Параметр keywords обязателен. Всё остальное — по желанию:
location— свободно вводимое название места, сопоставляется с собственным параметромlocationв LinkedIn.easyApplyOnly— ограничивает результаты вакансиями с функции Easy Apply (фасет Twitter). Это стоит включать каждый раз, когда вы планируете откликаться через этот сервер, посколькуlinkedin_apply_to_jobработает только с вариантами Easy Option.datePosted—"past24h","pastWeek"or"pastMonth".experienceLevel—"intern","entry","associate","midSenior","director"or"executive".remote— приtrueбработка только удалённых вакансий.count— сколько вакансий вернуть (по умолчанию 25, максимум 100). Результаты подгружаются отложено, поэтому большое значениеcountозначает multiple прокрутки с рандомизированной паузой между подгружениями и может занять некоторое время.
Эти четыре фасета можно передавать либо на верхнем уровне, как показано выше, либо сгруппированным в объекте filters — принимаются оба варианта, и filters автоматически выигрывает, если вы передаёте один и тот же ключ дважды:
{
"keywords": "site reliability engineer",
"filters": { "easyApplyOnly": true, "datePosted": "pastWeek", "remote": true },
"count": 50
}Карточки, которые LinkedIn отображает без пригодной вакансии id (под рекламированные места, placeholders), сразу пропускаются, а не возвращаются половинчающими, и учитываются в поле skipped ответа. Ответ также включает searchUrl, который является тем же запросом, который вы открываете и в своём сервере.
linkedin_apply_to_job (запись — требуется подтверждение)
Подаёт заявку LinkedIn Easy Apply. Расходует квоту jobApplications.
Предпросмотр:
{
"jobId": "3912847561",
"resumePath": "/Users/parthbansal/Documents/resume.pdf"
}Подтверждение:
{
"jobId": "3912847561",
"resumePath": "/Users/parthbansal/Documents/resume.pdf",
"confirm": true
}jobId— обязательный. Обычный идентификатор вакансии, URL/jobs/view/<id>/, URL поиска с?currentJobId=или job urn — все разрешаются в один и тот же идентификатор.resumePath— необязательный абсолютный путь к файлу резюме этой машины. Отсутствующий файл вызывает ошибкуfile_not_found. Содержимое не логируется.Вакансии, сколько передают приём внешней системе отслеживания кандидатов, завершаются ошибкой
external_ravelication; вакансии без контрола Easy Apply — ошибкойnot_easy_apply. Прочтите предпросмотр перед подтверждением — это единственный способ узнать заранее, на какие вопросы форма будет отвечать от вашего имени.previewToken,confirm— как выше.
linkedin_list_pending_invites
Возвращает список ожидающих приглашений в виде элементов PendingInvite: имя, заголовок, URL профиля, время отправки и направление. Только чтение.
{
"direction": "received",
"count": 25
}direction— необязательно:"received"(если вы приглашены) или"sent"(вы приглашаете). По имени операции"received".count— необязательное целое число от 1 до 100. По умолчанию 25.
linkedin_list_connections
Возвращает ваши контакты в виде элементов ConnectionSummary: имя, заголовок, URL профиля и время включения в контакты. Только чтение.
{
"count": 50,
"query": "observability"
}networkпорядок—count— необязательное целое число от 1 до 200. По умолчанию 50.query— необязательно. Локальный регистронезависимый фильтр по подстроке, применяется к контактам, которые уже прочитал сервер, и не отправляется в LinkedIn как поисковый запрос.
Конфигурация MCP-клиента
Сервер говорит на JSON-RPC через STD util и должен быть запусщен вашим MCP-клиентом, а не вручную. Сначала соберите (npm run build), затем укажите клинту на dist/server.js.
Из-за того, что сервер резолвит config.json, каталог состник и fixture/ **относительно своей рабчей диретори ** — а рабойчая диретория вашего MCP-клиента обычно не этот про — стоит явно установить абсолютные путие в блок env.
Claude Code / Claude Desktop
В claude_desktop_config.json:
{
"mcpServers": {
"linkedin": {
"command": "node",
"args": ["/Users/parthbansal/Desktop/claude code/linkedin-mcp/dist/server.js"],
"env": {
"LINKEDIN_MCP_CONFIG": "/Users/parthbansal/Desktop/claude code/linkedin-mcp/config.json",
"LINKEDIN_MCP_STATE_DIR": "/Users/parthbansal/Desktop/claude code/linkedin-mcp/.linkedin-mcp",
"LINKEDIN_MCP_LOG_LEVEL": "info"
}
}
}
}Cursor
В .cursor/mcp.json:
{
"mcpServers": {
"linkedin": {
"command": "node",
"args": ["/Users/parthbansal/Desktop/claude code/linkedin-mcp/dist/server.js"],
"env": {
"LINKEDIN_MCP_CONFIG": "/Users/parthbansal/Desktop/claude code/linkedin-mcp/config.json",
"LINKEDIN_MCP_STATE_DIR": "/Users/parthbansal/Desktop/claude code/linkedin-mcp/.linkedin-mcp",
"LINKEDIN_MCP_LOG_LEVEL": "info"
}
}
}
}Generic stdio client (например, Codex CLI)
Любый клент, который запускает stdio MCP-сервер, требует три вещи: команду, аргументы и окружение:
{
"name": "linkedin",
"command": "node",
"args": ["/Users/parthbansal/Desktop/claude code/linkedin-mcp/dist/server.js"],
"env": {
"LINKEDIN_MCP_CONFIG": "/Users/parthbansal/Desktop/claude code/linkedin-mcp/config.json",
"LINKEDIN_MCP_STATE_DIR": "/Users/parthbansal/Desktop/claude code/linkedin-mcp/.linkedin-mcp",
"LINKEDIN_MCP_LOG_LEVEL": "info"
}
}Codex CLI использует TOML в ожрсаннии JSON, но соответствие один к одному: command = "node", args = ["/Users/parthbansal/Desktop/claude code/linkedin-mcp/dist/server.js"], плюс таблица [env].
Безопасное экспериментирование
Добавьте "--dry-run" в args, чтобы зарегистрировать полностью рабочий сервер, который не может касаться вашего аккаунта:
"args": [
"/Users/parthbansal/Desktop/claude code/linkedin-mcp/dist/server.js",
"--dry-run"
]В режиме сухого пуска ни один запрос не достигает linkedin.com, и не публикуется ни запись, ни приглашение, ни сообщение, ни заявка. Это правильный способ, позволяя агенту исследовать инструментальный экран в первый раз.
Другие флаги: --headless, --config <path>, --log-level <debug|infor|warning|error> и -h / --help (результат печатается в stderr, потому что stdout работает протокол). Неверные флаги — критическая ошибка: опечатка --dry-runn никогда не должна оставить у вас чувство, что вы находитесь в dry-run, когда это не так. Приоритет всюду: флаги командной строки > переменные окружения > config.json > значения по умолчанию.
Все переменные окружения документированы в .env.example; все они необязательны, и ни в одной не хранутся никакие учётные данные.
Разработка
Проверка типов без инициализации:
npm run typecheckЗапуск модульных тестов:
npm testПересборка и сохранение:
npm run dev--dry-run
npm run dry-run запускает собранный сервер с --dry-run. В этом режиме браузер направлен на локальные HTML-фикстуры в fixtures/, а не на linkedin.com, и финальный клик отправки каждого действия не происходит. Если действие подтвердить, полный формат пройдёт весь путь кода — валидацию, проверку квоты, взаимодействие с диалогом — и останавливается перед выполнением. Результаты содержат dryRun: true, и предпросмотры предупреждают, что dry-run включён. Поиск linkedin_login недоступен, linkedin_session_status всегда сообщает валидность.
Встроенные фикстуры охватывают страницу профиля (в трёх вариантах: профиль второй степени с прямой кнопкой «Connect», профиль второй степени, у которого Connect находится за меню «More», и контакт первой степени с рабочим компо связью), ленту, поиск вакансий, страницу вакансии и для Easy Apply, и для внешней системы приема кандидатов, сообщения, приглашения и контактов. Разрабатывайте здесь. Нет никакой необходимости, чтобы ежедневная работа с этим сервером касалась вашего реального аккаунта.
Какой фикстурой будет разрешаться URL — решает упорядоченный список правил подстрок в src/fixtures.ts. Порядок имеет значение: /mynetwork/invite-connect/connections/ содержит /mynetwork/invite-connect и /in/ содержить /in/, поэтому конкретные правила стоят выше общин, и tests/fixtures.test.ts закрепляет этот порядок.
Проверка без LinkedIn
npm run verify:dryЭто загружает скомпилированный бинарный файл --dry-run, общается с ним по JSON-RPC по протоколу stdio так же, как реальный MCP-клиент, и прогоняет все 11 инструментов в 23 случаях — каждого читающего инструмента против своей фикстуры, каждый пишущий инструмент через полный предпросмотр → подтверждение, и важные запреты (устаревший previewToken, слишком длинное сообщение, сообщение профилю второй степени, внешняя вакансия, linkedin_login в dry-run). Проверяются также протокольные контракты: предпросмотр должен сообщать executed:false, подтверждение — executed:true, и stdout должен содержать только JSON-RPC.
Он вообще не будет выполняться, если сервер в своей строке запуска не сообщает dryRun:true, поэтому случайно сделать что-либо с вашим аккаунтом невозможно. Ему нужен рабочий Chromium: всё, кроме session_status, открывает страницу браузера. В случае любой ошибки он возвращает ненулевой код и сортирует сбои на selector/logic (сервер неверный) и harness (обвязка неверная).
Тесты
Suite запускается Vitest (tests/**/*.test.ts) и в настоящее время покрывает rate limiter (src/rateLimiter.ts), config loader (src/config.ts) и маршрутизацию фикстur (src/fixtures.ts) — 75 случаев в трёх файлах. Все три по построению «герметичны»: каждый случай запускается в новой временной директории mkdtemp, окружение передаётся явно, а не читается из process.env, внутрь инжектируются задержки через RateLimiterDeps. Они не делают сетевых запросов и не запускают браузер.
Всё, что требует браузер, находится в обзоре dry-run выше, а не в модуле, поэтому npm test выполняется меньше чем за секунду и не требует ничего, кроме node_modules.
Соглашения, о которых стоит знать перед редактированием
Ничто не должно писать в stdout. В stdout — это канал JSON-RPC; один зателочный байт портит фрейминг, и клиент разрывает соединение. Все диагностические сведения идут в stderr через
Logger.Проект построен на ESM с
moduleResolution: "NodeNext", поэтому относительные импорты должны заканчиваться на.js, даже в TypeScript-исходниках.src/types.tsиsrc/errors.ts— это общий контракт.src/selectors.tsсодержит только строки и чистые функции.
Устранение неисправностей
«Я запустил и ничего не происходит» — это успех
Запустите npm start вручную и вы увидите одну строку в stderr, а затем очевидное молчание:
{"ts":"...","level":"info","msg":"linkedin-mcp ready on stdio","version":"0.1.0","tools":11,"dryRun":false,"headless":false}Это здоровый сервер, а не зависание. MCP-studio-сервер — это не демон с портом и не командная строка, которая печатает результат и завершается. Он читает запросы из stdin, а записывает ответы stdout — после приветствия он блокируется в ожидании сообщений от клиента. Когда клиент не подключен, говорить нечего, и корректный сервер молчит, а не пишет что-то в stdout — один лишний байт там нарушил бы протокольную цепочку.
Так что npm start — это не способ использования. Зарегирируйте сервер в MCP-клиенте (ниже) и пусть клиент его запускает. Если вы хотите провериться, что он отвечает, вставьте прямо в его stdin запрос и нажмите Enter — ответ tools/list должен прийти немедленно:
printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | node dist/server.js --dry-runНажмите Ctrl-C, чтобы остановить запущенный вручную сервер. Он завершается по SIGINT/SIGTERM и пишет shutting down.
Два связанных симптома:
В вашем клиенте 0 инструментов или на сервер не появляется. Почти всегда причина — неверный путь в
argsслучаях. Используйте абсолютный путьdist/server.js, выполнитеnpm run buildи проверьте свой клиентский лог MCP — там будет stderr сервера, а в нём —config_invalidили ошибка разрешения модулей Node.Вы видите строку готовности, но все инструменты падают. Проверьте, что говорит эта строка.
dryRun: trueозначает только фикстуры, и ничего что вы сделаете, не дойдёт до LinkedIn.not_authenticatedв каждом вызове означает, что сохранённой сессии нет, поэтому запуститеlinkedin_login.
Коды ошибок
Сбои приходят как ошибки инструментов MCP с стабильным code. Те, которые вы чаще всего можете увидеть:
verification_required — LinkedIn показал CAPTCHA, контрольную точку или authwall. Сервер нарочно останавливается здесь и никогда не пытается обойти её. Откройте linkedin.com в обычном браузере, пройдите проверку сами, затем запустите linkedin_login ещё раз. Если это повторяется, воспринимайте это как сигнал снизить дневные квоты.
session_expired — сохранённая сессия больше не проходит аутентификацию, либо LinkedIn перенаправил ленту на страницу входа. Запустите linkedin_login и снова войдите вручную. linkedin_session_status покажет конкретный reason.
selector_not_found — сервер не смог найти нужный элемент. Почти всегда это огачает, что Instagram изменил свою разметку, а не то, что вы сделали что-то неправильно. Все DOM-серекторы проекта находятся в src/selectors.ts — это единственная точка поддержки: найти нужный список кандиетов, добавить в его наalо не new selector and rebuilding. Candidates are ordered lists, so adding a selector doesn't break old ones. Running with --log-level debug will tell you which lookup failed.
rate_limited — you have reached the day limit. The error itself and the quota block tell you which limit and when it resets (next local midnight). Wait it out or raise that limit in config.json — but raising a limit is exactly the behavior that causes accounts to get restricted, so do it intentionally.
external_application — the job posting redirects candidates to an external applicant tracking system instead of LinkedIn's Easy Apply form. The server does not fill out third-party sites. Open the job in a browser and apply there.
not_connected — you tried to message someone who is not a first-degree connection. Send a connection request first, wait for it to be accepted, then write. The server will not bypass that with InMail.
Other codes you may encounter: not_authenticated (no session on disk yet — run linkedin_login), not_easy_apply, invalid_input, confirmation_mismatch (arguments changed between preview and confirm — preview again), navigation_failed, browser_error, dry_run_unsupported, config_invalid (malformed config.json, reported before launch), and file_not_found.
What it does NOT do
No InMail. Messages are for first-tier contacts and existing conversations only.
** No external ATS responses.** Easy Apply only; anything that leaves LinkedIn is rejected.
No CAPTCHA solving. No 2FA automation, no bypass of checkpoints. Never.
No support for multiple accounts. One saved session, one account, one person at the keyboard.
No remote media upload. The server never fetches a URL to attach to a post.
Security and privacy
What and where is stored. Everything is on this machine in the state directory — by default <cwd>/.linkedin-mcp/, which can be overridden with LINKEDIN_MCP_STATE_DIR (or per-path with LINKEDIN_MCP_STORAGE_STATE, LINKEDIN_MCP_USER_DATA_DIR, LINKEDIN_MCP_SCREENSHOT_DIR, LINKEDIN_MCP_COUNTERS):
Path | Content |
| your LinkedIn cookies and origin storage, written in |
| directory of persistent Chromium profile |
| today's local date and four action counters |
| any diagnostic screenshots, written locally only |
Nothing is sent anywhere. No telemetry, no analytics, no crash reports, no “phone calls” to home. The only network destination is linkedin.com, reachable through your own browser session — and under --dry-run not even that.
Screenshots are local-only. They are written to the screenshot directory for your debugging and are never uploaded or included in tool output.
Logging is deliberately minimal. Diagnostics go to stderr. Cookies, contents of storageState, and contents of resume files are never logged or serialized. Failures log the error code rather than arguments, so message body or resume path can’t leak into a stderr captured by the client. redactConfig strips absolute paths (and our home directory) out of the perated configuration string the server logs at startup.
Gitignore guarantees. .gitignore excludes .ev and .env.* (leaving .ev.example), the entire .igore state directory, storageState.json at any depth, chromium-profile/, counters.json, screenshots/, *.log, plus node_modules/, dist/, and coverage/. Only config.example.json and your config.json remain under tracking, and they contain nothing but limit and timeoutces. Never commit storageState.json — it is a vive credential.
Treat the state directory as a secret. Anyone who can read storageState.json can act for you on LinkedIn without a password or a 2FA code. If you think it’s passed to the wrong hands, sign out of all sessions in LinkedIn’s own security settings, delete that file, and go through auth again.
Maintenance
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
- AlicenseBqualityBmaintenanceEnables full control over LinkedIn profiles through browser automation, allowing reading, editing, adding, removing entries, and publishing posts directly from conversations.41294MIT
- AlicenseNot gradedqualityDmaintenanceEnables fetching detailed LinkedIn profile data by automating a browser session with your LinkedIn cookie to access full profiles.214MIT
- AlicenseBqualityCmaintenanceEnables read-only extraction of LinkedIn profile data via MCP tools, using a local browser bridge for secure, authenticated access without exposing browser credentials.2MIT
- AlicenseAqualityBmaintenanceLets an AI assistant operate LinkedIn through an authenticated browser session, enabling profile management, posting, networking, messaging, job search, and automated applications.1003831MIT
Related MCP Connectors
Give AI agents the LinkedIn tools to find, qualify, engage, and follow up with prospects.
Browser MCP for logged-in tasks. Uses your Chrome — credentials stay local. Zero-token replay.
Let AI tools securely access your LinkedIn network and DMs
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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/bansalsahab/linkdin-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server