Skip to main content
Glama
alebros20

instagram-publish-mcp

by alebros20
README.md
# MCP-Server für Instagram-Veröffentlichungen

Ein MCP-Server, der einem KI-Agenten neun Werkzeuge gibt, um auf einem
Instagram-Business-Konto zu veröffentlichen: Medien hochladen, Beitrag anlegen,
Verarbeitungsstatus abfragen, veröffentlichen, zuletzt veröffentlichte Beiträge
auflisten, kommentieren, Kommentare lesen und beantworten, Kennzahlen abrufen.
Er kapselt ausschließlich die Meta Graph
API und einen S3-kompatiblen Objektspeicher. Ablaufsteuerung, Zeitplan und
Entscheidung, ob überhaupt veröffentlicht wird, bleiben beim aufrufenden Agenten.

## Warum es existiert

Instagram nimmt keine Datei entgegen. Die Graph API erwartet eine öffentlich
erreichbare URL, von der sie das Medium selbst abholt, und sie veröffentlicht
in zwei Schritten: erst einen Container anlegen, dann — nachdem die
Verarbeitung auf Instagram-Seite abgeschlossen ist — diesen Container
freigeben. Wer das aus einem Agenten heraus tun will, braucht dazwischen einen
Medienspeicher und eine Zustandsabfrage.

Dieser Server ist genau diese Zwischenschicht, und mehr nicht. Er entstand als
Ersatz für eine Automatisierung über einen Workflow-Dienst, die für den Zweck
zu schwer war: Für acht HTTP-Aufrufe braucht es keinen zweiten Dienst mit
eigener Oberfläche, eigener Datenhaltung und eigenem Betriebsaufwand.

## Voraussetzungen

Der Aufwand liegt nicht im Code, sondern in der Freischaltung bei Meta.
Realistisch sind das mehrere Stunden.

- Ein **Instagram-Konto vom Typ Business** (nicht Creator, nicht privat).
- Eine **Meta-App** mit dem Produkt „Instagram" und den Berechtigungen
  `instagram_business_basic`, `instagram_business_content_publish` und
  `instagram_business_manage_comments`.
- Das Instagram-Konto muss in der App als **Tester** hinterlegt sein, und die
  Einladung muss zusätzlich in den Instagram-Kontoeinstellungen angenommen
  werden. Beides ist nötig; eine Seite allein reicht nicht.
- Ein **S3-kompatibler Objektspeicher** mit öffentlich lesbarem Bucket. Die
  Standardwerte sind auf Cloudflare R2 zugeschnitten, jeder andere
  S3-kompatible Dienst funktioniert bei angepasstem Endpunkt.

## Installation

```bash
python -m venv .venv
.venv\Scripts\activate
pip install -r requirements.txt
copy .env.example .env
```

Danach die `.env` ausfüllen. Sie liegt neben `server.py` und wird von dort
gelesen; über die Umgebungsvariable `INSTAGRAM_MCP_ENV` lässt sich ein anderer
Pfad setzen, falls die Zugangsdaten zentral verwaltet werden.

Die `.env` steht in der `.gitignore` und gehört unter keinen Umständen in ein
Repository. Sie enthält ein Token mit vollem Zugriff auf das Konto.

### Bei einem MCP-Client registrieren

```bash
claude mcp add instagram-publish -- python /pfad/zu/server.py
```

Der Server spricht stdio und funktioniert mit jedem MCP-fähigen Client, nicht
nur mit dem Beispiel oben. Nach der Registrierung stehen die Werkzeuge in einer
neuen Sitzung als `mcp__instagram-publish__*` bereit.

## Die Werkzeuge

| Werkzeug | Zweck |
|---|---|
| `upload_media` | Lokale Datei in den Objektspeicher, gibt die öffentliche URL zurück |
| `create_container` | Schritt 1 der Veröffentlichung, gibt die `container_id` zurück |
| `get_status` | Verarbeitungsstatus: `IN_PROGRESS`, `FINISHED`, `ERROR`, `EXPIRED` |
| `publish_container` | Schritt 2 — der echte, irreversible Beitrag |
| `list_recent_media` | Zuletzt veröffentlichte Beiträge auflisten (`id`, `caption`, `timestamp`, `permalink`) |
| `post_comment` | Kommentar unter einen eigenen Beitrag setzen |
| `get_comments` | Kommentare lesen, liefert die `comment_id` für Antworten |
| `reply_comment` | Auf einen einzelnen Kommentar antworten |
| `get_insights` | Reichweite, Likes, Kommentare, Speicherungen |

### Der Ablauf

```
upload_media("kachel.png", "2026-08-29/kachel.png")
    -> https://<bucket>/2026-08-29/kachel.png

create_container(media_url, caption, is_video=False)
    -> "17912345678901234"

get_status("17912345678901234")
    -> "IN_PROGRESS"   … warten …
    -> "FINISHED"

publish_container("17912345678901234")
    -> "18012345678901234"      ab hier öffentlich sichtbar
```

Für Reels ist `is_video=True` zu setzen; die Verarbeitung dauert dann spürbar
länger, weshalb `get_status` überhaupt existiert. Ein Container, der zu lange
liegen bleibt, geht in `EXPIRED` über und lässt sich nicht mehr veröffentlichen.

## Token erneuern

Das langlebige Zugriffstoken gilt **60 Tage**. Die Erneuerung ist die Stelle,
an der die Dokumentation von Meta in die Irre führt: Der naheliegende
Endpunkt `ig_exchange_token` ist für den ersten Tausch eines kurzlebigen Tokens
gedacht und antwortet auf ein bereits langlebiges Token mit
„Session key invalid". Richtig ist:

```
GET https://graph.instagram.com/refresh_access_token
    ?grant_type=ig_refresh_token
    &access_token=<aktuelles Token>
```

Das Ergebnis in `INSTAGRAM_LONG_LIVED_TOKEN` eintragen. Ein Token, das abläuft,
ohne erneuert worden zu sein, lässt sich nicht auffrischen — dann ist der
komplette Freischaltungsweg noch einmal fällig. Eine Erinnerung einige Tage vor
Ablauf ist keine Bequemlichkeit, sondern notwendig.

## Grenzen

**`publish_container` ist irreversibel und sofort öffentlich.** Es gibt keinen
Entwurfszustand und keine Rücknahme über diesen Server. Das Werkzeug gehört
hinter eine ausdrückliche menschliche Freigabe und darf nicht in einer
automatischen Kette stehen, die ohne Rückfrage durchläuft. Der Beitrag lässt
sich danach nur noch von Hand in der App löschen, und auch das nicht
rückstandslos.

**Das Token ist Vollzugriff auf das Konto.** Wer die `.env` hat, kann
veröffentlichen, kommentieren und Kennzahlen lesen. Der Server gibt das Token
nie zurück und schreibt es nicht ins Log — die Log-Stufe von `httpx` wird
bewusst hochgesetzt, weil die Bibliothek sonst die vollständige Anfrage-URL
samt `access_token` im Klartext protokolliert. Das ist die einzige Stelle im
Code, an der ein Geheimnis auslaufen könnte.

Weiter offen, bewusst:

- **Kein Ratenlimit und keine Wiederholung.** Läuft man in ein Limit der Graph
  API, wirft `raise_for_status` eine Ausnahme, und der aufrufende Agent muss
  damit umgehen. Für ein bis zwei Beiträge am Tag ist das unkritisch.
- **Kein Liken von Kommentaren.** Dafür wäre
  `instagram_business_manage_engagement` nötig. Diese Berechtigung gibt es auf
  dem reinen Instagram-Login-Pfad nicht, sie setzt eine verknüpfte
  Facebook-Seite und den Facebook-Login-Pfad voraus.
- **Kommentar-Endpunkte antworten im Entwicklungsmodus leer.** `get_comments`
  liefert eine leere Liste, obwohl der Beitrag sichtbar Kommentare hat. Das ist
  kein Fehler im Server: Die App muss dafür auf „Live" geschaltet sein, was
  Datenschutzerklärung und Nutzungsbedingungen als erreichbare URLs voraussetzt.
  Diese Stunde Fehlersuche kann man sich sparen.
- **`mcp[cli]` ist auf `<2` festgelegt.** Der Server ist gegen die 1.x-API
  geschrieben (`FastMCP`). In 2.x heißt die Klasse `MCPServer`, der Import
  bricht dort. Die Umstellung ist überschaubar, aber nicht gemacht — ohne die
  Versionsgrenze läuft ein frisch geklontes Repository sofort in einen
  `ModuleNotFoundError`.
- **Keine Tests.** Alle Werkzeuge sprechen mit einer fremden API; ein
  sinnvoller Test bräuchte einen Doppelgänger für die Graph API. Für den
  Umfang stand das bisher in keinem Verhältnis.

## Lizenz

MIT, siehe [LICENSE](LICENSE).