Skip to main content
Glama
stepanic

linkedin-poster

by stepanic

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: LinkedIn MCP Server

Ограничение, определяющее всё

Токены доступа LinkedIn живут 60 дней, а программные refresh-токены доступны только одобренным партнёрам Marketing Developer Platform. Самостоятельно настраиваемое приложение их не получает. Обойти это невозможно, и любое руководство, обещающее настройку LinkedIn-автоматизации «один раз и навсегда», ошибочно.

Что есть: пока ваш текущий токен действителен и вы вошли в LinkedIn, повторный запрос к endpoint авторизации пропускает экран согласия и просто перенаправляет. Так что продление — это один клик каждые семь недель, если успеть до истечения токена. Упустите окно — и снова появится экран согласия, то есть два клика вместо одного.

Именно поэтому сервис поставляется с cron-триггером. Начиная за 14 дней до истечения, он каждое утро выводит напоминание о продлении в начало ежедневной сводки, пока бесшумное продление ещё работает. См. Уведомления.

Настройка

1. Сторона LinkedIn (вручную, около десяти минут)

  1. Вам нужна страница LinkedIn. Портал разработчика требует привязать приложение к ней и подтвердить её администратором.

  2. Создайте приложение на linkedin.com/developers. Запишите Client ID и Client Secret.

  3. Вкладка Products, запросите оба разрешения (это Open Permissions, одобряются мгновенно):

    • Sign In with LinkedIn using OpenID Connectopenid, profile

    • Share on LinkedInw_member_social

  4. Вкладка 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 deploy

3. Подключите аккаунт

Откройте 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

Что делает

linkedin_post

Проверяет линтером, затем публикует. Отказывает при ошибках, если не указано force: true.

linkedin_check

Проверяет линтером без публикации. Используйте при подготовке черновика.

linkedin_status

От чьего имени публикует и сколько дней осталось у токена.

Затем в сессии: «проверь этот черновик линтером, затем опубликуй».

Использование из оболочки

# 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

no-em-dash, no-en-dash

error

Самый явный признак сгенерированного текста. Запятая или точка работают всегда.

max-length

error

LinkedIn отклоняет комментарии длиннее 3000 символов.

empty

error

Очевидно, но легко пропустить при передаче из файла.

no-markdown-bold, no-markdown-link, no-markdown-heading, markdown-bullets

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 запросов на участника в день. На участника, а не на приложение.

Структура

Файл

Содержимое

src/index.ts

Маршруты, OAuth-поток, cron-напоминание

src/linkedin.ts

Вызовы OAuth и Posts API, все endpoint'ы в одном месте

src/mcp.ts

MCP-сервер, самописный JSON-RPC, без состояния

src/lint.ts

Стилистический линтер

src/tokens.ts

Запись токена в KV, расчёт срока действия, безопасное по времени сравнение секретов

src/notify.ts

Какие события доходят до группы и в каких формулировках

src/telegram.ts

Транспорт Bot API, повторы, миграция в супергруппу

src/stats.ts

Счётчики дня, которые читает сводка

Локальная разработка

cp .dev.vars.example .dev.vars   # fill in, never commit
npm run types                    # regenerate Env after editing wrangler.jsonc
npm run typecheck
npm run dev

wrangler 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 — мои; механизм можете забирать.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.

No tool schema history has been recorded yet.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

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

Related MCP Connectors

Related MCP Servers

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/stepanic/linkedin-poster'

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