linkedin-poster
linkedin-poster
Poste in deinen eigenen LinkedIn-Feed aus Claude Code, einer Shell oder einem Cron-Job. Ein Cloudflare Worker, keine Abhängigkeiten, drei Oberflächen: ein MCP-Server, damit ein Agent direkt veröffentlichen kann, eine HTTP-API für Skripte und ein Stil-Linter, der sich weigert, Text zu veröffentlichen, der die Anzeichen maschinengeschriebener Prosa trägt.
Gebaut, weil ein guter Beitrag die Automatisierung wert ist und ein schlechter em-dash es nicht wert ist, veröffentlicht zu werden. Auch diese README enthält keine em-dashes, und genau das ist der Punkt.
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)Was es nicht ist
Dies postet als ein Mitglied: du. Es ist kein Planungs-SaaS, kein Multi-Tenant-Tool und es berührt kein Konto anderer. Alles, was es braucht, ist Self-Service; kein LinkedIn-Partnerprogramm ist beteiligt.
Related MCP server: mcp-server-linkedin
Die Einschränkung, die alles prägt
LinkedIn-Zugriffstokens halten 60 Tage, und programmatische Refresh-Tokens sind auf zugelassene Marketing Developer Platform-Partner beschränkt. Eine Self-Service-App bekommt nie eines. Daran führt kein Weg vorbei, und jeder Leitfaden, der LinkedIn-Automatisierung mit Einrichten-und-Vergessen verspricht, liegt falsch.
Was es gibt: Solange dein aktuelles Token noch gültig ist und du bei LinkedIn angemeldet bist, überspringt ein erneuter Aufruf des Autorisierungs-Endpunkts den Zustimmungsbildschirm und leitet einfach weiter. Die Erneuerung ist also alle sieben Wochen ein Klick, solange du es tust, bevor das Token stirbt. Verpasst du das Zeitfenster, bekommst du den Zustimmungsbildschirm zurück, also zwei Klicks statt einem.
Deshalb wird dieser Dienst mit einem Cron-Trigger ausgeliefert. Ab 14 Tagen vor Ablauf setzt er jeden Morgen einen Erneuerungsalarm an den Anfang der Tageszusammenfassung, solange die stille Erneuerung noch funktioniert. Siehe Benachrichtigungen.
Einrichtung
1. LinkedIn-Seite (manuell, etwa zehn Minuten)
Du brauchst eine LinkedIn-Seite. Das Entwicklerportal verlangt, dass du die App an eine Seite anhängst und sie von einem Seiten-Admin verifizieren lässt.
Erstelle die App unter linkedin.com/developers. Notiere dir die Client ID und das Client Secret.
Tab Produkte, fordere beide an (es sind offene Berechtigungen und sie werden sofort genehmigt):
Sign In with LinkedIn using OpenID Connect →
openid,profileShare on LinkedIn →
w_member_social
Tab Auth → füge die Redirect-URL hinzu. Sie muss HTTPS, absolut und zeichengenau mit
REDIRECT_URIübereinstimmen:https://your-worker.example.com/auth/callback
2. Worker-Seite
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. Verbinde dein Konto
Öffne https://your-worker.example.com/auth/start?key=<SETUP_KEY> in einem Browser, genehmige einmal, fertig. Die Seite zeigt dir das Ablaufdatum.
SETUP_KEY ist keine Deko. Ohne ihn könnte jeder, der die URL findet, sein LinkedIn-Konto an deinen Worker binden und dein gespeichertes Token überschreiben.
Verwendung mit Claude Code
claude mcp add --transport http linkedin https://your-worker.example.com/mcp \
--header "Authorization: Bearer $API_TOKEN"Drei Tools:
Tool | Funktion |
| Lintet, dann veröffentlicht. Lehnt bei Fehlern ab, außer mit |
| Lintet ohne zu veröffentlichen. Zum Entwerfen verwenden. |
| Als wessen Konto es postet und wie viele Tage das Token noch hat. |
Dann, in einer Sitzung: „Prüfe diesen Entwurf mit dem Linter und veröffentliche ihn dann.“
Verwendung aus der Shell
# 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 gibt 201 mit der Post-URN und der öffentlichen URL zurück, 422, wenn der Linter es blockiert, 503, wenn das Token fehlt oder abgelaufen ist.
Benachrichtigungen
Ohne das scheitert der Dienst so, wie jeder unbeaufsichtigte Dienst scheitert: leise, und du erfährst es an dem Tag, an dem ein Beitrag nicht erscheint. Eine Telegram-Gruppe behebt das für den Preis eines Bots.
# 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>"Was ankommt:
Ereignis | Wann |
Tägliche Zusammenfassung | bei jedem Cron-Lauf, auch an ruhigen Tagen |
Erneuerungsalarm | ab 14 Tagen vor Ablauf, zusätzlich zur Zusammenfassung des Tages |
Wiederverbunden | ein neues Token wurde gespeichert, mit dem neuen Ablaufdatum |
Veröffentlicht | jeder veröffentlichte Beitrag, mit seinem Link |
Blockiert | der Linter hat einen Entwurf abgelehnt, mit den ausgelösten Regeln |
Abgelehnt | LinkedIn hat einen Beitrag abgelehnt, mit einer Vermutung warum |
Kein brauchbares Token | ein Veröffentlichungsversuch fand das Token fehlend oder abgelaufen |
Kontingent | der Tag überschreitet 120 der 150 Anfragen, die LinkedIn einem Mitglied erlaubt |
Zwei Designentscheidungen, die du kennen solltest, bevor du etwas änderst:
Die tägliche Zusammenfassung ist der Herzschlag. Sie geht auch raus, wenn nichts passiert ist, sodass ihre Abwesenheit selbst der Alarm ist und nichts den Monitor überwachen muss. Genau deshalb reitet ein Erneuerungsalarm auf der Zusammenfassung, statt als zweite Nachricht zu kommen: genau eine Nachricht pro Morgen, sonst trainierst du dich darauf, nur zu überfliegen.
Der Erneuerungslink trägt ein Einmal-Nonce, nicht den Setup-Schlüssel. Ein Link mit SETUP_KEY würde ein langlebiges Geheimnis dauerhaft in einem Chatprotokoll parken. Der Cron prägt stattdessen ein Nonce, sieben Tage gültig, ersetzt durch die nächste Erinnerung und gelöscht in dem Moment, in dem ein Token gespeichert wird. Linkvorschauen sind auf jeder Nachricht deaktiviert, damit Telegram das Nonce nicht durch Vorabruf verbraucht.
Stellst du nichts davon ein, verhält sich der Dienst genau wie zuvor: NOTIFY_WEBHOOK, falls du einen hast, sonst das Log.
GET /telegram/preview?key=<SETUP_KEY>&kind=daily rendert jede Nachricht, ohne sie zu senden. So lässt sich der Wortlaut bearbeiten, ohne die Gruppe zuzuspammen. Füge &send=1 hinzu, um sie auf einem Telefon zu sehen.
Der Bot-API-Transport wird gegen einen lokalen Stub getestet, einschließlich der Supergruppen-Migration, die Telegram-Bots Monate nach der Einrichtung stillschweigend zerstört:
npm run test:telegramDer Linter
Fehler blockieren das Veröffentlichen; Warnungen nicht.
Regel | Schweregrad | Warum |
| Fehler | Das deutlichste Anzeichen generierter Prosa. Ein Komma oder ein Punkt funktioniert immer. |
| Fehler | LinkedIn lehnt Kommentare über 3000 Zeichen ab. |
| Fehler | Offensichtlich, aber leicht zu treffen, wenn man aus einer Datei piped. |
| Warnung | LinkedIn rendert nichts davon. Sternchen und Rauten erscheinen wörtlich. |
Bindestriche werden nie markiert. read-only tier, two-day sprint und per-client limits sind korrekt und müssen bleiben. Nur U+2014 und U+2013 sind Fehler.
src/lint.ts zu bearbeiten ist der vorgesehene Weg, das hier zu deinem zu machen. Die Regeln kodieren die Schreibkonventionen einer Person, keinen universellen Standard.
Dinge, die dich überraschen werden
Die Posts-API scrapt keine URLs, aber LinkedIn verlinkt sie trotzdem. An einem Live-Beitrag verifiziert: Eine nackte URL wird zu
lnkd.in/...gekürzt und ist klickbar. Was du nicht bekommst, ist die Vorschaukarte mit Bild und Titel. Dafür brauchst du ein über die Images-API hochgeladenes Thumbnail plus einencontent.article-Block. Hier noch nicht implementiert.LinkedIn-Versionist Pflicht und Versionen werden eingestellt.202508wurde am 17. August 2026 abgeschaltet. Behandle den Wert inwrangler.jsoncals Wartungsposten./v2/ugcPostsist veraltet. Dies verwendet/rest/posts, das es ersetzt hat.Das Ändern der angeforderten Scopes macht jedes vorhandene Token ungültig. Du musst dich neu autorisieren.
150 Anfragen pro Mitglied pro Tag. Pro Mitglied, nicht pro App.
Aufbau
Datei | Enthält |
| Routen, OAuth-Ablauf, Cron-Erinnerung |
| OAuth- und Posts-API-Aufrufe, alle Endpunkte an einem Ort |
| MCP-Server, handgeschriebenes JSON-RPC, zustandslos |
| Der Stil-Linter |
| KV-Token-Datensatz, Ablaufberechnung, timing-sicherer Geheimnisvergleich |
| Welche Ereignisse die Gruppe erreichen und in welchen Worten |
| Bot-API-Transport, Wiederholungen, Supergruppen-Migration |
| Die Zähler des Tages, die die Zusammenfassung ausliest |
Lokale Entwicklung
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 gibt dir ein lokales KV, sodass der gesamte Ablauf bis auf die echten LinkedIn-Aufrufe ohne Anmeldedaten getestet werden kann.
Notizen aus einer echten Einrichtung
docs/2026-08-27-postavljanje-i-zamke.md hält fest, was die Einrichtung von Null an tatsächlich gekostet hat: das Portal, das jeden App-Namen mit „LinkedIn“ ablehnt, die irreversible Seiten-Zuordnung, OAuth-Scopes, die leer bleiben, bis du die Seite aktualisierst, %20 gegenüber + im Scope-Parameter, ein negativer DNS-Cache, der eine funktionierende Bereitstellung tot aussehen ließ, und wie mit Geheimnissen umgegangen wurde, sodass keines durch ein Terminal ging.
docs/2026-08-27-telegram-obavijesti.md behandelt die Benachrichtigungsebene: die Supergruppen-Migration, die Telegram-Bots stillschweigend zerstört, warum der Herzschlag eine Zusammenfassung und kein Ping ist, und warum der Erneuerungslink ein Nonce trägt.
Lizenz
MIT. Die Regeln in src/lint.ts sind meine; den Mechanismus kannst du übernehmen.
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.-