Dieser Artikel knüpft an Was ist ein MCP-Server? an – dort erkläre ich die Grundlagen.
KI-Bildgeneratoren sind großartig für abstrakte Heroes – aber sobald ein konkretes Logo ins Bild soll, wird es haarig: Das Modell erfindet ein Logo, das dem echten nur ähnelt. Für einen Produkt-Hero, der das echte Home-Assistant-Logo zeigen soll, ist das unbrauchbar. Die Lösung: das Logo als Referenzbild mitschicken, statt es nur zu beschreiben.
Genau das habe ich in meinem bestehenden generate_ai_image-MCP-Tool nachgerüstet. Hier zeige ich, wie der Bild-Input bei den beiden Providern funktioniert, wie das Tool automatisch ins richtige Modell schaltet – und welche Stolperfalle mich ein paar vergebliche Generierungen gekostet hat.
Warum Text→Bild nicht reicht
Der klassische Pfad ist reines Text→Bild: Prompt rein, Bild raus (DALL·E 3, Imagen). Das Modell hat aber keine Vorlage – es kann ein Markenlogo nur aus dem Gedächtnis annähern. Ergebnis: verbogene Buchstaben, falsche Proportionen, ein „Logo“, das keins ist. Für Marken-Assets ist das ein No-Go.
Was wir brauchen, ist ein Bild-Input: Wir geben dem Modell das echte Logo mit und sagen „bau das hier sauber ins Motiv ein“. Beide großen Anbieter können das – nur über unterschiedliche Endpunkte.
Zwei Wege: images.edit vs. generate_content
Bei OpenAI läuft der Bild-Input nicht über images.generate, sondern über images.edit mit dem Modell gpt-image-1. Man übergibt ein oder mehrere Eingabebilder plus Prompt. Wichtig: Die Bytes müssen als datei-artige Objekte mit gesetztem .name kommen, sonst meckert das SDK.
# Referenzbild-Modus: gpt-image-1 image-edit mit (mehreren) Eingabebildern
if reference_images:
ref_model = model if str(model).startswith("gpt-image") else GPT_IMAGE_REF_MODEL
size = GPT_IMAGE_SIZE.get(aspect_ratio, "1024x1024")
inputs = []
for i, data in enumerate(reference_images):
bio = BytesIO(data)
bio.name = f"reference_{i}.png" # SDK braucht einen Dateinamen
inputs.append(bio)
response = client.images.edit(
model=ref_model,
image=inputs if len(inputs) > 1 else inputs[0],
prompt=prompt,
size=size,
)
return _openai_response_to_contentfile(response)Bei Google kann Imagen keine Bild-Inputs. Dafür gibt es gemini-2.5-flash-image, das über generate_content läuft – Prompt und Bild kommen als Part-Liste rein, das fertige Bild steckt in der inline_data der Antwort.
# Imagen kann keine Bild-Inputs -> Flash-Image (generate_content)
if reference_images:
ref_model = model if "flash-image" in str(model) else GEMINI_REF_MODEL
client = genai.Client(api_key=GOOGLE_API_KEY)
contents = [prompt]
for data in reference_images:
contents.append(types.Part.from_bytes(data=data, mime_type="image/png"))
response = client.models.generate_content(model=ref_model, contents=contents)
for cand in response.candidates or []:
for part in cand.content.parts or []:
inline = getattr(part, "inline_data", None)
if inline and inline.data:
return ContentFile(inline.data)
raise ValueError("Gemini (Flash-Image) hat kein Bild zurückgegeben.")Vom MCP-Tool bis zu den Bytes
Das MCP-Tool selbst soll bequem bleiben: Es nimmt entweder Wagtail-Bild-IDs (reference_image_ids) oder öffentliche URLs (reference_image_urls) und löst beides zu rohen Bytes auf, bevor es sie an den Service durchreicht. Sind Referenzen dabei, schaltet die Pipeline automatisch auf das bild-fähige Modell – der reine Text→Bild-Pfad bleibt unangetastet.
reference_images: list[bytes] = []
# 1) Wagtail-Bilder per ID
for iid in reference_image_ids or []:
img = Image.objects.get(id=iid)
with img.file.open("rb") as f:
reference_images.append(f.read())
# 2) Beliebige oeffentliche URLs
for url in reference_image_urls or []:
reference_images.append(requests.get(url, timeout=30).content)
image = generate_and_save_image(
prompt=prompt,
title=title,
use_case=use_case,
reference_images=reference_images or None, # None -> reiner Text-Pfad
)Nebenbei: Dieser MCP-Server – inklusive Bildgenerierung – läuft bei mir nicht in der Cloud, sondern auf einem sparsamen Mini-PC im Homelab, der nebenbei auch Home Assistant und diverse Docker-Stacks trägt:
Anzeige · Affiliate-Link – kaufst du darüber, erhalte ich ggf. eine Provision. Für dich ändert sich am Preis nichts.
Stolperfalle: provider ≠ Modell
Eine Sache hat mich echte Generierungen gekostet: Ich wollte für ein Logo-Bild den Provider auf openai zwingen, habe aber das Modell nicht mitgegeben. Mein Config-Resolver zieht das Modell dann aus den Settings – und dort stand das Imagen-Modell. Ergebnis: ein Imagen-Modellname wandert in den OpenAI-Call, und der quittiert das so:
Error code: 400 - The model 'imagen-4.0-generate-001' does not exist.Lektion: Provider und Modell gehören zusammen. Entweder beides explizit setzen (provider="openai", model="gpt-image-1") oder gar keinen Override und den konfigurierten Default nutzen. Der Referenzbild-Zweig fängt zwar ein unpassendes Modell ab und nimmt das jeweilige Bild-Modell – aber nur, wenn der Provider überhaupt zum Modell passt.
Was ich weggelassen habe
- Echter Live-Test im CI: Die Bild-APIs kosten Tokens und brauchen Keys – die Logik ist nach SDK-Doku gebaut und statisch geprüft, der scharfe Test lief erst am echten Logo.
- Auth & Fehlerbehandlung: Retries, Rate-Limits, ungültige URLs – für den Produktivbetrieb nötig, hier bewusst knapp gehalten.
- Text im Bild: Beide Modelle setzen gern ungewollt Buchstaben ins Bild; ein klares „no text“ im Prompt hilft, ist aber kein Garant.
Fazit & Ausblick
Mit ein paar Zeilen wird aus einem Text→Bild-Tool ein Werkzeug, das echte Logos sauber ins Motiv übernimmt – der Schlüssel ist der richtige Endpunkt je Provider (images.edit bzw. generate_content) und ein Tool, das IDs/URLs zu Bytes auflöst. Der Hero dieses Artikels ist übrigens selbst so entstanden. Nächster logischer Schritt: dieselbe Referenz-Mechanik für konsistente Charaktere über mehrere Heroes hinweg.
Anzeige · Affiliate-Link – kaufst du darüber, erhalte ich ggf. eine Provision. Für dich ändert sich am Preis nichts.