Flin Linkedin Ads Mcp — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited Flin Linkedin Ads Mcp (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
flin-linkedin-ads-mcp ist ein strikt read-only MCP Server für LinkedIn Ads. Er ist dafür gebaut, direkt in Claude über MCP eingebunden und getestet zu werden.
get_creative_media)get_share_content, best effort für Bild-URLs)/accountIntelligence, private API Access nötig)search_ad_library)search_brand_partnerships)linkedin_loginv0.xv0.3.0get_creative_media: Bilder und Video-Vorschaubilder direkt im Chat anzeigenund als lokale Dateien für den Dashboard-Import speichern.
LINKEDIN_MEDIA_DIR, begrenzte Dateigrössen undgeprüfte LinkedIn-CDN-Downloads ohne Weitergabe des API-Tokens.
v0.2.0202610, auch ohne Posts-Zugriff.202610 als neuer Standard und verständliche Hinweise bei inaktiven Versionen.Bestehende Installationen mit explizitem LINKEDIN_API_VERSION müssen diesen Wert auf 202610 setzen, um die neue Bildauflösung zu nutzen. Anschliessend den MCP-Client neu starten. Die Ad Library benötigt eine separate Produktfreischaltung der LinkedIn App. Die Bildadressen aus get_creative bleiben externe URLs. get_creative_media kann Dateien separat abrufen; für Dashboards ist anschliessend deren unterstützter Datei-/Asset-Import nötig.
v0.x (strict read-only)In Claude:
Settings öffnenDeveloper öffnen{
"mcpServers": {
"flin-linkedin-ads-mcp-local": {
"command": "uv",
"args": [
"run",
"--directory",
"/Users/nicolasg/Antigravity/flin-linkedin-ads-mcp",
"flin-linkedin-ads-mcp"
],
"env": {
"LINKEDIN_CLIENT_ID": "DEINE_APP_CLIENT_ID",
"LINKEDIN_CLIENT_SECRET": "DEIN_APP_CLIENT_SECRET",
"LINKEDIN_REDIRECT_URI": "http://localhost:9876/callback",
"LINKEDIN_API_VERSION": "202610",
"LINKEDIN_RESTLI_PROTOCOL_VERSION": "2.0.0"
}
}
}
}In der LinkedIn Developer App unter Auth zuerst exakt http://localhost:9876/callback als Redirect URL eintragen. Danach Claude neu starten und linkedin_login aufrufen. Den zurückgegebenen Link auf demselben Computer im Browser öffnen und bei LinkedIn anmelden. Anschliessend linkedin_login_status und list_ad_accounts aufrufen. Die App braucht die Berechtigungen r_ads und r_ads_reporting.
Ein bereits vorhandener LINKEDIN_ACCESS_TOKEN funktioniert weiterhin. Nach einem Login hat der gespeicherte OAuth-Token Vorrang vor einem alten Token aus der Konfiguration.
uvx (wenn Paket publiziert ist){
"mcpServers": {
"flin-linkedin-ads-mcp": {
"command": "uvx",
"args": ["--refresh", "flin-linkedin-ads-mcp"],
"env": {
"LINKEDIN_CLIENT_ID": "DEINE_APP_CLIENT_ID",
"LINKEDIN_CLIENT_SECRET": "DEIN_APP_CLIENT_SECRET",
"LINKEDIN_REDIRECT_URI": "http://localhost:9876/callback",
"LINKEDIN_API_VERSION": "202610",
"LINKEDIN_RESTLI_PROTOCOL_VERSION": "2.0.0"
}
}
}
}Nach dem Restart in Claude diese Calls testen:
linkedin_login und den zurückgegebenen Link öffnenlinkedin_login_status (nach erfolgreichem Login: token_available)list_ad_accountslist_campaignsget_insights mit pivot=campaignWenn list_campaigns eine Auswahl zurückgibt, den Call mit einem vorgeschlagenen ad_account_id wiederholen.
Hinweis zu get_insights:
analytics Finder mit Single-Pivot.view=li-lms-2026-03 ausgerichtet.date_from ist Pflicht (LinkedIn adAnalytics erwartet dateRange).fields folgen der Metrics-Tabelle aus der offiziellen Reporting-Schema-Doku (max. 20 Felder pro Request).pivot.value / timeGranularity.value und hat zusätzlich einen Fallback auf Legacy-Parameternamen für bessere API-Kompatibilität.videoWatchTime, averageVideoWatchTime, eventViews, eventWatchTime, averageEventWatchTime.Beispiel für einen stabilen Test-Call:
{
"ad_account_id": "508834004",
"date_from": "2025-08-01",
"date_to": "2025-12-31",
"fields": [
"impressions",
"clicks",
"costInLocalCurrency",
"dateRange"
],
"pivot": "account",
"time_granularity": "MONTHLY"
}Voraussetzung ist das freigeschaltete Produkt LinkedIn Ad Library auf der App, die den verwendeten Mitgliedstoken ausgestellt hat. Ein vorhandener gültiger OAuth-Mitgliedstoken kann weiterverwendet werden; es ist kein zusätzlicher OAuth-Scope erforderlich. Beide Tools funktionieren ohne ad_account_id und ohne Zugriff auf das Werbekonto des gesuchten Unternehmens. Der bestehende Ads-Login bleibt unverändert.
search_ad_librarySucht über GET /rest/adLibrary?q=criteria nach öffentlichen Anzeigen, auch anderer Unternehmen.
keyword: Suchbegriffe im Anzeigeninhalt; mehrere Wörter werden mit UND verknüpft.advertiser: Name des werbenden Unternehmens.payer_name: Name der zahlenden Person oder Organisation.countries: zweistellige Ländercodes, z. B. ["CH", "DE", "AT"].date_from und date_to: gemeinsam im Format YYYY-MM-DD; Start inklusive,Ende exklusiv. Ohne diese Angaben nutzt LinkedIn nur heute minus 2 Tage bis heute minus 1 Tag, nicht das gesamte Archiv.
included_targeting_categories / excluded_targeting_categories: Targeting-Kategorien,z. B. ["COMPANY", "JOB"]; die verfügbaren Werte stehen im Tool-Schema.
impressions_from und impressions_to: gemeinsam als Impression-Bereich;1000000 bis 0 steht für die offene oberste Klasse ab einer Million.
sort_order: ASCENDING oder DESCENDING, sortiert nach Erstellungszeit der Anzeige.page_start: Offset ab 0; page_size: 1 bis 25, Standard 25.Beispiel für search_ad_library:
{
"advertiser": "Microsoft",
"countries": ["DE", "AT"],
"date_from": "2026-10-01",
"date_to": "2026-10-29",
"page_size": 25
}Die Antwort behält die API-Felder unverändert bei: adUrl, details mit Inserent, Anzeigenformat, verfügbaren Targeting- und Impression-Daten sowie isRestricted und restrictionDetails. Umfang und Verfügbarkeit hängen von der Anzeige und Region ab; zusätzliche Transparenzdaten betreffen insbesondere EU-Anzeigen. Die API liefert Vorschaulinks, keine heruntergeladenen Anzeigenbilder oder vollständigen Anzeigentexte und keine Klick-, Kosten- oder Conversion-Berichte. Redaktionen bei eingeschränkten Anzeigen bleiben erhalten. Anzeigen bleiben üblicherweise ein Jahr nach ihrer letzten Impression in der Bibliothek.
search_brand_partnershipsSucht über GET /rest/paidEndorsementPosts?q=searchCriteria nach organischen Beiträgen, die Creators als Markenpartnerschaft gekennzeichnet haben. Die Antwort enthält postUrl.
keyword: Pflicht; mehrere Wörter werden mit UND verknüpft.date_range: optional PAST_24H, PAST_WEEK oder PAST_MONTH; ohne Filter sindverfügbare Beiträge bis maximal ein Jahr nach Veröffentlichung durchsuchbar.
page_start und page_size: wie bei der Anzeigensuche; der MCP begrenzt beide Toolsauf höchstens 25 Ergebnisse pro Aufruf.
{
"keyword": "technology",
"date_range": "PAST_MONTH",
"page_size": 10
}Beide Tools liefern paging.next_start, paging.has_next und, sofern von LinkedIn geliefert, paging.total. Für die nächste Seite dieselben Filter mit page_start=paging.next_start erneut aufrufen. paging.next_after enthält denselben Offset als String zur Kompatibilität mit dem bestehenden Antwortformat. Fehlen die Paging-Angaben, kann eine volle Seite einen abschliessenden leeren Aufruf erfordern.
Bei 403 / ACCESS_DENIED prüfen, ob die MCP-Zugangsdaten zur freigeschalteten App gehören und das Produkt im Developer Portal aktiv ist. Ein gültiger Token oder der Status token_available allein bestätigt den Ad-Library-Zugriff nicht. Bei Bedarf über linkedin_login neu verbinden und die Suche wiederholen; eine fehlende Produktfreischaltung wird durch einen neuen Login nicht behoben.
Offizielle Referenzen: Zugang und Authentifizierung, Anzeigen-Schema, Markenpartnerschaften-Schema.
get_insights Parameter (2026-03)Wichtigste Parameter:
pivot: z. B. account, campaign_group, campaign, creative, member_company_size, member_industry, member_seniority, member_job_title, member_job_function, member_country_v2, member_region_v2, member_company, member_county, share, company, conversiontime_granularity: DAILY, MONTHLY, ALL, YEARLYfields: Liste aus der offiziellen Metrics-Tabelle, maximal 20 Einträge, case-sensitivepivotValue → pivotValues, clickThroughRate (berechnet als clicks / impressions), costPerClick (berechnet als costInLocalCurrency / clicks)date_from: YYYY-MM-DD (Pflicht)date_to: optional, YYYY-MM-DDFacets/Filter:
ad_account_id (ein Konto) oder account_ids (mehrere Konten)campaign_ids, campaign_group_ids, creative_ids, share_ids, company_idscampaign_type, objective_typesort_by_field + sort_order (müssen zusammen angegeben werden)Kompatibilität:
entity_ids bleibt für Backward-Kompatibilität erhalten (z. B. bei pivot=campaign).list_creatives / get_creative Bild-URL (optional)Für Creative-Calls kann optional das Derived-Field imageUrl in fields angefordert werden.
202610 wird imageUrl bevorzugt aus metadata.assetURL der einzelnen Creative-Abfrage gelesen. Dafür ist kein Zugriff auf die Posts-API nötig.get_creative fordert das Metadatum im selben Aufruf an; list_creatives macht nur bei angefordertem imageUrl oder metadata eine zusätzliche Abfrage je Creative (doppelte IDs werden pro Seite zwischengespeichert). Ohne diese Felder bleibt es bei einer Listenabfrage.metadata kann zusätzlich in fields angefordert werden und enthält je nach Format u. a. adHeadline, adIntroductionText, adFormat, assetURL und landingPage. Auf älteren API-Versionen wird eine ausdrückliche Anfrage nach metadata mit einem Konfigurationshinweis abgelehnt.content geprüft.content.reference auf urn:li:adDirectSponsoredContent:* zeigt, versucht der MCP zusätzlich eine Auflösung über adDirectSponsoredContents/{urn}.standardUpdateAd und videoAd werden erkannt.imageUrl null.Beispiel:
{
"ad_account_id": "508834004",
"id": "urn:li:sponsoredCreative:935973186",
"fields": ["id", "name", "imageUrl"]
}list_account_intelligence (202603)Neues Tool:
list_account_intelligenceDieser Endpunkt nutzt GET /rest/accountIntelligence?q=account und ist laut LinkedIn private API (zusätzliche Freischaltung erforderlich).
Wichtige Parameter:
ad_account_id (oder Auto-Resolve bei genau einem Account)lookback_window: LAST_7_DAYS, LAST_30_DAYS, LAST_60_DAYS, LAST_90_DAYSad_segment_ids, campaign_idskip_company_decorationpage_start, page_size (max. 1000)Wichtige Response-Felder:
companyName, engagementLevelpaidImpressions, paidClicks, paidEngagements, paidLeadspaidQualifiedLeads, conversions (ab API-Version 202603)organicImpressions, organicEngagementsBeispiel:
{
"ad_account_id": "508834004",
"lookback_window": "LAST_30_DAYS",
"page_size": 100
}get_share_content (best effort)Neues Tool:
get_share_contentWichtige Parameter:
share_urn (Pflicht, Format urn:li:share:<id> oder urn:li:ugcPost:<id>)include_raw (optional, true gibt zusätzlich das rohe API-Payload zurück)Response enthält u. a.:
share_urn, source_endpoint (posts)post_url (LinkedIn Feed URL)textimage_url (erstes gefundenes Bild oder null)image_urls, thumbnail_urlsBeispiel:
{
"share_urn": "urn:li:share:7379073146093568000",
"include_raw": false
}Für Anzeigenbilder bevorzugt get_creative / list_creatives mit fields: ["id", "imageUrl", "metadata"] und LINKEDIN_API_VERSION=202610 verwenden. Eine explizit auf 202603 oder älter gesetzte MCP-Konfiguration überschreibt weiterhin den neuen Default: dort den Wert ändern und den Server neu starten. Ein Paket-Update allein ändert diese Konfiguration nicht.
get_share_content verwendet die aktuelle Posts-API. Der alte /shares-Endpunkt wird nicht mehr aufgerufen. Ein 403 an /posts oder /images bedeutet fehlende Berechtigungen für diese APIs, auch wenn die Ads-Abfragen funktionieren. Der Connector fordert dafür keine zusätzlichen Schreibberechtigungen an. Der Creative-Metadatenweg funktioniert unabhängig vom Posts-Zugriff. Ein 426 enthält nun einen Hinweis auf die konfigurierte Version und gegebenenfalls die nötige Endpoint-Migration, statt wie ein Login-Fehler behandelt zu werden.
Die Bildadressen sind externe, möglicherweise zeitlich begrenzte LinkedIn-URLs. Wenn ein Dashboard externe Bilder blockiert, löst eine funktionierende URL diese Einschränkung nicht: Für eine Einbettung müssen die ausgewählten Bilder über den vom Dashboard erlaubten Import-/Asset-Workflow bereitgestellt werden. Der Connector exportiert oder lädt mit diesen URL-Tools keine Bilddateien herunter. Für einen Dateiabruf gibt es get_creative_media; Dashboard-Sicherheitsregeln bleiben unverändert. LinkedIn-Vorschaulinks bleiben eine mögliche Alternative. Eine assetURL kann je nach Anzeigenformat auch ein anderes Medium sein; erkennbare Video-/PDF-URLs werden nicht als imageUrl ausgegeben.
Referenzen: Creative-Metadaten, Posts-API, Images-API.
get_creative_media: Chat-Vorschau und Dateien für DashboardsDas Tool lädt das Medium einer zugänglichen Anzeige herunter. Standardmässig liefert es ein natives MCP-Bild für den Chat und speichert dieselbe Datei lokal. Bei einer Videoanzeige ist kind: "preview" das Vorschaubild, nicht die Videodatei. Die tatsächliche Darstellung des Bildes hängt vom MCP-Client ab.
{
"ad_account_id": "508834004",
"id": "urn:li:sponsoredCreative:935973186",
"kind": "preview",
"output": "both"
}kind: preview (Standard) für Bilder/Vorschaubilder, video für die vollständige MP4.output: inline für ein MCP-Bild ohne lokale Datei, file für eine lokale Datei,both (Standard) für beides. Videos unterstützen file und both; beide liefern eine Datei und einen Dateiverweis, keinen nativen Video-Block.
mime_type, size_bytes, sha256 und bei Dateiausgabelocal_path / file_uri. Bilddaten werden separat als MCP ImageContent ausgegeben, nicht als Base64 im Antworttext.
~/.cache/flin-linkedin-ads-mcp/media, konfigurierbar mit LINKEDIN_MEDIA_DIR.Dateien erhalten inhaltsbasierte Namen und private Dateirechte. Der lokale Bestand wird nicht automatisch gelöscht; nicht mehr benötigte Dateien können dort entfernt werden.
dessen unterstützten Asset-Import übernommen werden. Ein lokaler Pfad ist keine öffentliche Bildadresse und kein automatischer Upload; entfernte Clients brauchen einen Datei-Transfer.
werden geprüft. Downloads und Weiterleitungen sind auf HTTPS-LinkedIn-CDN-Adressen beschränkt; der API-Token wird nicht an den CDN-Server gesendet.
Dafür sind die jeweiligen Leseberechtigungen nötig. Ein 403 nennt kind: "preview" als Alternative. Das Tool gibt niemals ein JPEG als erfolgreich heruntergeladenes Video aus.
den Aufruf wiederholen. Der Connector verändert weiterhin keine LinkedIn-Ads-Daten.
Nach Installation des aktualisierten Codes den MCP-Client neu starten, damit das neue Tool sichtbar wird. Ein bereits installiertes veröffentlichtes Paket erhält lokale Änderungen nicht automatisch.
Referenzen: Videos-API, MCP-Bilder und Dateiverweise.
get_insightsBei ILLEGAL_ARGUMENT oder RESOURCE_NOT_FOUND bitte prüfen:
clicks statt click, impressions statt impression).fields.ad_account_id/account_ids oder andere Facets).share_ids und company_ids als URN).YYYY-MM-DD.date_from ist gesetzt (ohne dateRange antwortet LinkedIn oft mit ILLEGAL_ARGUMENT).pivot ist einer der dokumentierten Werte (siehe oben).Schneller API-Gegencheck (ohne MCP) für das Token:
curl -i 'https://api.linkedin.com/rest/adAccounts?q=search&pageSize=1' \
-H 'Authorization: Bearer DEIN_ACCESS_TOKEN' \
-H 'Linkedin-Version: 202603' \
-H 'X-Restli-Protocol-Version: 2.0.0'Die Anmeldung ist für einen lokalen stdio-MCP-Server mit einem Benutzer vorgesehen. Browser und Server müssen auf demselben Computer laufen. Dies ist keine OAuth-Anmeldung für einen gemeinsam genutzten Remote-/HTTP-Connector.
Einmalige Einrichtung:
r_ads undr_ads_reporting freischalten. Der anmeldende Benutzer braucht Zugriff auf das Werbekonto.
http://localhost:9876/callback registrieren.LINKEDIN_CLIENT_ID und LINKEDIN_CLIENT_SECRET in der MCP-Konfiguration hinterlegen(siehe Beispiel oben). Secrets gehören nicht in den Chat.
Danach reicht im Chat die Bitte „LinkedIn Ads neu verbinden“:
linkedin_login liefert authorization_url. Den Link im lokalen Browser öffnen.linkedin_login_status zeigt den lokalen Status; token_available bestätigt nochkeine Kontoberechtigungen. Mit list_ad_accounts den echten API-Zugriff prüfen.
get_ad_account mit {"id":"509614236"}. Kein weiterer Neustart und kein Token-Kopieren nötig.
Login-Links gelten zehn Minuten. Ein erneuter Aufruf während eines offenen Logins liefert denselben Link. Abbruch, Ablauf oder ein Fehler lässt sich über den Status abfragen; anschliessend kann ein neuer Login gestartet werden. Bei einem belegten Callback-Port entweder den anderen Server schliessen oder LINKEDIN_REDIRECT_URI auf einen freien Port ändern und dieselbe URL in der LinkedIn App registrieren.
Standard: ~/.config/flin-linkedin-ads-mcp/tokens.json (über LINKEDIN_TOKEN_FILE änderbar). Die Datei enthält sensible Zugangsdaten, wird atomar mit Dateirechten 0600 geschrieben und sollte ausserhalb des Repositories bleiben. Sie ist keine verschlüsselte Schlüsselablage. Für getrennte Benutzer/Verbindungen jeweils eine eigene Datei und einen eigenen Callback-Port verwenden. Ein gespeicherter Token wird nur für die zugehörige Client ID geladen.
Ein gespeicherter Login hat Vorrang vor LINKEDIN_ACCESS_TOKEN. Die Tokens werden beim Serverstart geladen und nach erfolgreichem Login sofort im laufenden Server verwendet. Token, Client Secret und Authorization Code erscheinen nicht in den Auth-Tool-Antworten oder Callback-Logs. linkedin_login_status gibt nur Status und Ablaufzeitpunkte zurück.
Falls LinkedIn einen Refresh-Token ausstellt, erneuert der Server den Access-Token kurz vor Ablauf bei der nächsten API-Abfrage sowie einmal nach einer 401-Antwort. Abgelaufene/widerrufene Refresh-Tokens führen zurück zu linkedin_login. Programmatic Refresh hängt von der Freischaltung der LinkedIn App ab; offline_access wird nicht angefordert. Siehe LinkedIn Programmatic Refresh Tokens.
401 / EXPIRED_ACCESS_TOKEN: linkedin_login aufrufen und den Link öffnen.403: App-Berechtigungen und Rolle des angemeldeten Benutzers im Werbekonto prüfen.Für direkten Login:
LINKEDIN_CLIENT_ID und LINKEDIN_CLIENT_SECRET (immer zusammen setzen)LINKEDIN_REDIRECT_URI (Default: http://localhost:9876/callback; HTTP, localhost oder127.0.0.1, expliziter Port und Pfad; muss exakt in der App registriert sein)
LINKEDIN_TOKEN_FILE (Default: ~/.config/flin-linkedin-ads-mcp/tokens.json)Alternativ bleibt LINKEDIN_ACCESS_TOKEN als fest hinterlegter Token unterstützt. Ohne OAuth-App-Konfiguration kann dieser Token nicht automatisch erneuert werden.
Weitere Optionen:
LINKEDIN_API_VERSION (Default: 202610)LINKEDIN_RESTLI_PROTOCOL_VERSION (Default: 2.0.0)LINKEDIN_TIMEOUT_SECONDS (Default: 30)LINKEDIN_MAX_RETRIES (Default: 3)cd /Users/nicolasg/Antigravity/flin-linkedin-ads-mcp
python -m pip install -e ".[dev]"
pytest -q
ruff check .
mypy srcLINKEDIN_ACCESS_TOKEN oder OAuth Secrets ins Repo committen.env ist bereits in .gitignoreenv oder lokale Shell-Umgebung setzen; OAuth-Tokens werden lokal gespeichertgit status
git diff -- .envWenn eine Datei mit Secrets auftaucht: Commit abbrechen und Secrets rotieren.
Dieses Repo enthält:
.github/workflows/ci.yml.github/workflows/release.ymlDer Release-Workflow macht bei v* Tags:
twine checkdist/* ArtefaktenNode-20-Deprecation-Warnungen sind abgefangen durch:
checkout@v6, setup-python@v6)FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true in beiden Workflowsflin-linkedin-ads-mcpflin-agencyflin-linkedin-ads-mcprelease.ymlpypigit add -A
git commit -m "release: v0.1.0"
git tag v0.1.0
git push origin main --tags~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.