linkedin-poster
linkedin-poster
Публикуйте в свою ленту LinkedIn из Claude Code, из оболочки или по cron. Один Cloudflare Worker, без зависимостей, три поверхности: MCP-сервер, чтобы агент мог публиковать напрямую, HTTP API для скриптов и стилистический линтер, который отказывается публиковать текст с признаками машинной прозы.
Создан, потому что хороший пост стоит автоматизировать, а плохое длинное тире не стоит публиковать. В этом README тоже нет длинных тире, в этом и суть.
Claude Code ──MCP──┐
├──> Worker ──> LinkedIn Posts API ──> your feed
curl / CI ──HTTP───┘ │
├──> KV (one 60-day token) + daily cron
└──> Telegram group (what happened, and what broke)Чем это не является
Это публикует от имени одного участника: вас. Это не SaaS для планирования, не мультитенантный инструмент и не затрагивает чужие аккаунты. Всё необходимое настраивается самостоятельно; никакая партнёрская программа LinkedIn не задействована.
Related MCP server: mcp-server-linkedin
Ограничение, определяющее всё
Токены доступа LinkedIn живут 60 дней, а программные refresh-токены доступны только одобренным партнёрам Marketing Developer Platform. Самостоятельно настраиваемое приложение их не получает. Обойти это невозможно, и любое руководство, обещающее настройку LinkedIn-автоматизации «один раз и навсегда», ошибочно.
Что есть: пока ваш текущий токен действителен и вы вошли в LinkedIn, повторный запрос к endpoint авторизации пропускает экран согласия и просто перенаправляет. Так что продление — это один клик каждые семь недель, если успеть до истечения токена. Упустите окно — и снова появится экран согласия, то есть два клика вместо одного.
Именно поэтому сервис поставляется с cron-триггером. Начиная за 14 дней до истечения, он каждое утро выводит напоминание о продлении в начало ежедневной сводки, пока бесшумное продление ещё работает. См. Уведомления.
Настройка
1. Сторона LinkedIn (вручную, около десяти минут)
Вам нужна страница LinkedIn. Портал разработчика требует привязать приложение к ней и подтвердить её администратором.
Создайте приложение на linkedin.com/developers. Запишите Client ID и Client Secret.
Вкладка Products, запросите оба разрешения (это Open Permissions, одобряются мгновенно):
Sign In with LinkedIn using OpenID Connect →
openid,profileShare on LinkedIn →
w_member_social
Вкладка Auth → добавьте redirect URL. Он должен быть HTTPS, абсолютным и совпадать с
REDIRECT_URIсимвол в символ:https://your-worker.example.com/auth/callback
2. Сторона Worker
git clone https://github.com/stepanic/linkedin-poster.git
cd linkedin-poster
npm install
# KV to hold the token
npx wrangler kv namespace create TOKENS
npx wrangler kv namespace create TOKENS --preview
# paste both ids into wrangler.jsonc
# edit wrangler.jsonc: name, routes, REDIRECT_URI, account_id
npx wrangler secret put LINKEDIN_CLIENT_ID
npx wrangler secret put LINKEDIN_CLIENT_SECRET
npx wrangler secret put API_TOKEN # openssl rand -hex 32, guards /post and /mcp
npx wrangler secret put SETUP_KEY # openssl rand -hex 32, guards /auth/start
npx wrangler secret put NOTIFY_WEBHOOK # optional, any URL taking a JSON POST
npx wrangler secret put TELEGRAM_BOT_TOKEN # optional, see Notifications below
npx wrangler secret put TELEGRAM_CHAT_ID
npm run deploy3. Подключите аккаунт
Откройте https://your-worker.example.com/auth/start?key=<SETUP_KEY> в браузере,
один раз подтвердите, готово. Страница покажет дату истечения.
SETUP_KEY — не для вида. Без него любой, кто найдёт URL, сможет привязать
свой аккаунт LinkedIn к вашему Worker и перезаписать сохранённый токен.
Использование из Claude Code
claude mcp add --transport http linkedin https://your-worker.example.com/mcp \
--header "Authorization: Bearer $API_TOKEN"Три инструмента:
Tool | Что делает |
| Проверяет линтером, затем публикует. Отказывает при ошибках, если не указано |
| Проверяет линтером без публикации. Используйте при подготовке черновика. |
| От чьего имени публикует и сколько дней осталось у токена. |
Затем в сессии: «проверь этот черновик линтером, затем опубликуй».
Использование из оболочки
# dry run: lint only, publish nothing
curl -s -X POST https://your-worker.example.com/post \
-H "Authorization: Bearer $API_TOKEN" -H 'Content-Type: application/json' \
-d '{"text":"…","dry_run":true}'
# publish
curl -s -X POST https://your-worker.example.com/post \
-H "Authorization: Bearer $API_TOKEN" -H 'Content-Type: application/json' \
-d '{"text":"…","visibility":"PUBLIC"}'
# token state
curl -s https://your-worker.example.com/status -H "Authorization: Bearer $API_TOKEN"/post возвращает 201 с URN поста и его публичным URL, 422, когда линтер
блокирует публикацию, 503, когда токен отсутствует или недействителен.
Уведомления
Без этого сервис отказывает так же, как любой сервис без присмотра: тихо, и вы узнаёте об этом в день, когда пост не вышел. Группа в Telegram решает эту проблему ценой одного бота.
# 1. @BotFather in Telegram, /newbot, copy the token
npx wrangler secret put TELEGRAM_BOT_TOKEN
# 2. make a group, add the bot, send /start@yourbot in it
# (a bot with privacy mode on sees only messages that mention it)
# 3. read the group id back, then store it
open "https://your-worker.example.com/telegram/chatid?key=<SETUP_KEY>"
npx wrangler secret put TELEGRAM_CHAT_ID
# 4. prove it works
open "https://your-worker.example.com/telegram/test?key=<SETUP_KEY>"Что приходит:
Событие | Когда |
Ежедневная сводка | каждый запуск cron, включая тихие дни |
Напоминание о продлении | начиная за 14 дней, поверх сводки за этот день |
Переподключение | сохранён новый токен, с новой датой истечения |
Опубликовано | каждый опубликованный пост, со ссылкой |
Заблокировано | линтер отклонил черновик, с указанием сработавших правил |
Отклонено | LinkedIn отклонил пост, с предположением о причине |
Нет рабочего токена | попытка публикации обнаружила, что токен отсутствует или недействителен |
Квота | за день превышено 120 из 150 запросов, разрешённых участнику LinkedIn |
Два проектных решения, о которых стоит знать, прежде чем что-то менять:
Ежедневная сводка — это сердцебиение. Она уходит, даже если ничего не произошло, поэтому её отсутствие само по себе является сигналом тревоги, и никому не нужно следить за наблюдателем. Именно поэтому напоминание о продлении приходит поверх сводки, а не отдельным сообщением: ровно одно сообщение утром, иначе вы приучите себя просматривать их мельком.
Ссылка на продление содержит одноразовый nonce, а не ключ настройки. Ссылка
с SETUP_KEY навсегда оставила бы долгоживущий секрет в логе чата. Вместо этого
cron создаёт nonce, действительный семь дней, заменяемый следующим напоминанием
и удаляемый в момент сохранения токена. Предпросмотр ссылок отключён во всех
сообщениях, чтобы Telegram не мог израсходовать nonce предварительной загрузкой.
Если ничего не настроить, сервис ведёт себя ровно как раньше: NOTIFY_WEBHOOK,
если он у вас есть, и лог, если нет.
GET /telegram/preview?key=<SETUP_KEY>&kind=daily отображает любое сообщение без
отправки — так можно править формулировки, не засоряя группу. Добавьте
&send=1, чтобы увидеть его на телефоне.
Транспорт Bot API протестирован на локальной заглушке, включая миграцию в супергруппу, которая незаметно ломает Telegram-ботов спустя месяцы после настройки:
npm run test:telegramЛинтер
Ошибки блокируют публикацию; предупреждения — нет.
Rule | Severity | Why |
| error | Самый явный признак сгенерированного текста. Запятая или точка работают всегда. |
| error | LinkedIn отклоняет комментарии длиннее 3000 символов. |
| error | Очевидно, но легко пропустить при передаче из файла. |
| warning | LinkedIn не отображает ни одно из этих форматирований. Звёздочки и решётки показываются буквально. |
Дефисы не помечаются никогда. read-only tier, two-day sprint и
per-client limits — корректны и должны оставаться. Ошибками являются только
U+2014 и U+2013.
Редактирование src/lint.ts — это предусмотренный способ сделать проект своим.
Правила кодируют писательские предпочтения одного человека, а не универсальный
стандарт.
Что вас удивит
Posts API не извлекает URL, но LinkedIn всё равно превращает их в ссылки. Проверено на живом посте: голый URL сокращается до
lnkd.in/...и становится кликабельным. Чего вы не получите — это карточки предпросмотра с изображением и заголовком. Для этого нужна миниатюра, загруженная через Images API, плюс блокcontent.article. Здесь пока не реализовано.LinkedIn-Versionобязателен, и версии выводятся из эксплуатации.202508перестал работать 17 августа 2026 года. Относитесь к значению вwrangler.jsoncкак к пункту, требующему обслуживания./v2/ugcPostsустарел. Здесь используется/rest/posts, который заменил его.Изменение запрашиваемых областей доступа аннулирует все существующие токены. Вам придётся авторизоваться заново.
150 запросов на участника в день. На участника, а не на приложение.
Структура
Файл | Содержимое |
| Маршруты, OAuth-поток, cron-напоминание |
| Вызовы OAuth и Posts API, все endpoint'ы в одном месте |
| MCP-сервер, самописный JSON-RPC, без состояния |
| Стилистический линтер |
| Запись токена в KV, расчёт срока действия, безопасное по времени сравнение секретов |
| Какие события доходят до группы и в каких формулировках |
| Транспорт Bot API, повторы, миграция в супергруппу |
| Счётчики дня, которые читает сводка |
Локальная разработка
cp .dev.vars.example .dev.vars # fill in, never commit
npm run types # regenerate Env after editing wrangler.jsonc
npm run typecheck
npm run devwrangler dev --local даёт вам локальное KV, так что весь процесс, кроме реальных
вызовов LinkedIn, можно проверить без учётных данных.
Заметки из реальной настройки
docs/2026-08-27-postavljanje-i-zamke.md
описывает, чего на самом деле стоила настройка с нуля: портал отклонял любое имя
приложения, содержащее «LinkedIn», необратимая привязка к Page, OAuth-скоупы,
которые остаются пустыми, пока не обновишь страницу, %20 против + в параметре
scope, отрицательный DNS-кэш, из-за которого работающее развёртывание выглядело
мёртвым, и как обрабатывались секреты, чтобы ни один из них не прошёл через терминал.
docs/2026-08-27-telegram-obavijesti.md
описывает слой уведомлений: миграцию в супергруппу, которая незаметно ломает
Telegram-ботов, почему сердцебиение — это сводка, а не пинг, и почему ссылка на
продление содержит nonce.
Лицензия
MIT. Правила в src/lint.ts — мои; механизм можете забирать.
This server cannot be deployed
Maintenance
Related MCP Connectors
Schedule and publish LinkedIn posts to profiles and company pages from AI agents.
Draft LinkedIn posts in your voice, then schedule or publish them through LinkedIn's official API.
Write LinkedIn posts in your own voice. Drafts only, ego never publishes for you.
Draft, reshape and schedule LinkedIn posts in the writer's own voice, not a generic AI one.
Related MCP Servers
- AlicenseAqualityCmaintenanceEnables AI agents to publish posts, images, comments, and reactions to LinkedIn as the authenticated user, with built-in safety features like daily budgets and deduplication.912 npmApache 2.0
- AlicenseAqualityBmaintenanceEnables AI agents to publish posts, images, videos, and articles to LinkedIn.10MIT
- FlicenseNot gradedqualityBmaintenanceEnables AI agents to draft, format, and manage LinkedIn posts through a review pipeline, with human approval via a web UI before publication.-
- FlicenseNot gradedqualityBmaintenanceEnables AI assistants to publish public text posts to an authenticated LinkedIn profile via the Model Context Protocol.-