Skip to main content
Glama
Weone404

weone-daily-post

by Weone404

weone-daily-post — Remote-MCP-Server

Publishing-Backend für den täglichen Instagram- und Facebook-Post von We One Aviation. Es ist ein zustandsloser Streamable HTTP MCP-Server: Claude übernimmt das Denken (Themenwahl, Formulierung, Bildunterschrift), dieser Dienst übernimmt die Nebeneffekte (Verlauf, Bildrendering, Speicherung, Graph API). Bilder werden gesetzt, nicht generiert: Der Text auf dem Poster ist exakt der gelieferte Text.

Claude ──POST /mcp (Bearer)──▶ Render web service (Node 20, Express)
                                 ├─ Supabase  posts table + post-images bucket
                                 ├─ Chromium      HTML template → JPEG
                                 └─ Meta Graph  IG container/publish, FB photos

Tools

Tool

Zweck

get_past_topics()

Verlauf, neueste zuerst, max. 200 Zeilen: {id, topic, category, status, created_at}. Vor der Themenwahl lesen.

reserve_topic(topic, category)

Fügt status='reserved' ein, gibt {id} zurück. categorynews, subject, career. Ein wiederholtes Thema schlägt mit duplicate_topic fehl.

render_post({template, headline, points, footer?, eyebrow?, slug?})

Rendert eine gebrandete HTML-Vorlage zu einem JPEG in exakter Größe (sRGB), lädt es hoch und gibt einen Bildblock plus die öffentliche URL zurück.

publish_socials(image_url, caption, hashtags, history_id)

Veröffentlicht auf Instagram und dann Facebook, protokolliert das Ergebnis.

mark_draft(history_id, image_url, caption)

Schattenmodus: Den fertigen Post als draft aufzeichnen, ohne zu veröffentlichen.

check_token()

Tage bis zum Ablauf des Meta-Tokens + gewährte Berechtigungen.

Jedes Tool gibt JSON zurück. Erfolg ist {"ok": true, ...}; Fehler sind ein MCP-Fehler Ergebnis mit {"ok": false, "error": {code, message, retryable, details}}. Nichts wirft einen rohen Stacktrace an den Aufrufer.

render_post

template ist eines von news, subject oder career — dieselben drei Kategorien, die auch die posts-Tabelle verwendet.

Feld

Limit

Hinweise

headline

60 Zeichen

Barlow 700, bis zu 3 Zeilen. Satzfall, nicht Titelcase.

points

3–4 Punkte, je 90 Zeichen

Barlow 400, je ein goldener Marker

eyebrow

32 Zeichen, optional

Gold, per CSS in Großbuchstaben, z. B. NAVIGATION

footer

90 Zeichen, optional

Linke Seite der Fußzeile, z. B. DGCA · 14 Aug 2026

Es gibt zwei Inhaltsblöcke zurück: einen Bildblock (Base64-JPEG) und einen Text Block mit der öffentlichen URL, Dateiname, Abmessungen, Bytegröße und Renderzeit.

Zwei Wächter laufen, bevor etwas hochgeladen wird, und beide nennen das fehlerhafte Feld:

  1. Längenlimits, geprüft bevor Chromium berührt wird — die kostengünstige Ablehnung. points[2] is 97 characters, limit is 90. Shorten it and retry.

  2. Messung im Layout, nach dem Layout — jede Textbox ist eine feste Beschneidungsbox, und wenn ihr Inhalt höher oder breiter als die Box ist, wird das Rendern mit dem Feldnamen und dem Überlauf in Pixeln abgelehnt. Das fängt ab, was eine Zeichenanzahl nicht sehen kann, z. B. ein unteilbares 80-Zeichen-Token, das in der Länge legal ist, aber über den Rand läuft.

Nichts wird hochgeladen, wenn einer der Wächter auslöst, also kostet eine Ablehnung eine Sekunde und die Lösung ist immer „das genannte Feld kürzen“.

Der Bildblock kommt trotzdem zurück, damit du die Formulierung im Kontext lesen kannst, aber er ist keine Korrektheitsprüfung mehr: Eine feste Vorlage kann kein Wort falsch schreiben oder ein Diagramm erfinden. Der ungünstigste legale Eingabefall (58-Zeichen-Überschrift plus vier 90-Zeichen-Punkte, breitestes legales Eyebrow und Footer) wurde verifiziert, um in alle drei Vorlagen zu passen.

publish_socials, Schritt für Schritt

  1. HEAD auf die image_url und 200 + content-type: image/jpeg bestätigen. Meta ruft diese URL serverseitig ab, und eine schlechte URL schlägt dort stundenlang undurchsichtig fehl. (Ein Speicher, der HEAD ablehnt, bekommt stattdessen einen 1-Byte-Bereichs-GET.)

  2. Instagramfull_caption = caption + "\n\n" + hashtags.join(' '), begrenzt auf 2200 Zeichen. Nur Hashtags werden entfernt, vom Ende; der Bildunterschriftstext wird nie abgeschnitten. Wenn der Text allein 2200 überschreitet, schlägt der Aufruf mit caption_too_long fehl, bevor etwas gepostet wird. POST {IG_USER_ID}/mediaGET {container}?fields=status_code,status einmal pro Sekunde für bis zu 60s pollen → nur bei FINISHED veröffentlichen. Bei ERROR wird die status-Zeichenfolge wörtlich zurückgegeben, denn sie ist die einzige Stelle, an der Meta erklärt, was es nicht mochte.

  3. FacebookPOST {FB_PAGE_ID}/photos mit url und message. Wird unabhängig vom Instagram-Ergebnis versucht.

  4. Aufzeichnen — die posts-Zeile erhält ig_post_id, fb_post_id, image_url und status = published (beide), partial (eines) oder failed (keines).

Gibt {ig_post_id, fb_post_id, status, errors: [...]} zurück. Ein Fehler auf einer Plattform wird nie verschluckt: Er erscheint in errors[] mit der Plattform, der Stufe, bei der er auftrat, und Metas eigenem code / error_subcode / message.

Related MCP server: Social Analytics MCP Server

Umgebungsvariablen

Variable

Erforderlich

Was es ist

MCP_AUTH_TOKEN

ja

Gemeinsames Geheimnis für /mcp. Der Connector muss Authorization: Bearer <value> senden. Wenn nicht gesetzt, startet der Server trotzdem und bedient /health, lehnt aber jede /mcp-Anfrage mit einem 500 ab — er schlägt geschlossen fehl, nie offen. Erzeuge eines mit node -e "console.log(require('crypto').randomBytes(32).toString('hex'))".

SUPABASE_URL

ja

https://<project-ref>.supabase.co.

SUPABASE_SERVICE_KEY

ja

Service-Rollen-Schlüssel. Umgeht RLS — nur serverseitig. Nie in die Connector-Konfiguration legen.

META_GRAPH_VERSION

nein (Standard v23.0)

Graph-API-Version, die für jeden Aufruf verwendet wird.

IG_USER_ID

für Veröffentlichung

Instagram-Business-Konto-ID (eine Zahl, nicht der @Handle).

FB_PAGE_ID

für Veröffentlichung

Facebook-Seiten-ID, die mit diesem Instagram-Konto verknüpft ist.

META_PAGE_ACCESS_TOKEN

für Veröffentlichung

Langlebiges Seiten-Zugriffstoken mit instagram_basic, instagram_content_publish, pages_show_list, pages_read_engagement, pages_manage_posts. Läuft in etwa 60 Tagen ab — check_token() sagt dir, wie viel Zeit bleibt.

PORT

nein

Render setzt dies. Standard 10000.

MAX_INLINE_IMAGE_BYTES

nein (Standard 1400000)

Größe, ab der die Inline-Base64-Vorschau herunterskaliert wird.

CHROMIUM_EXECUTABLE_PATH

nein

Expliziter Pfad zu einer Chrome/Chromium-Binärdatei. Überschreibt den plattformspezifischen Standard.

CHROMIUM_SINGLE_PROCESS

nein

Auf 1 setzen, um --single-process zu erzwingen. Kostet Browser-Wiederverwendung — ein Rendern pro Start. Siehe Browser-Lebenszyklus.

Kopiere .env.example nach .env für lokale Läufe. .env ist gitignored — lass es so.

Einrichtung

1. Supabase

Führe migrations/001_init.sql im SQL-Editor aus (oder supabase db push). Es ist idempotent und erstellt:

  • die posts-Tabelle mit den Check-Constraints und dem eindeutigen Index auf topic — dieser Index ist die Wiederholungssperre, also muss eine doppelte Reservierung erwartungsgemäß fehlschlagen,

  • created_at desc- und status-Indizes,

  • RLS auf posts ohne Richtlinien (nur der Service-Schlüssel kommt rein),

  • den öffentlichen post-images-Speicher-Bucket plus seine öffentliche Lese-Richtlinie. Öffentliches Lesen ist Pflicht: Meta ruft das JPEG selbst ab und kann keine Anmeldedaten vorweisen.

2. Meta

Du brauchst ein Instagram Business- oder Creator-Konto, das mit einer Facebook-Seite verknüpft ist, und ein langlebiges Seiten-Token mit den oben aufgeführten Berechtigungen. Bestätige es mit check_token() vor dem ersten Lauf — ein abgelaufenes Token ist die häufigste Ursache für einen fehlgeschlagenen Morgen.

3. Auf Render bereitstellen

Mit render.yaml (Blueprint):

  1. Dieses Repo auf GitHub pushen.

  2. Render-Dashboard → NeuBlueprint → das Repo auswählen. Es liest render.yaml: Node 20, npm ci && npm run build, npm start, Health-Check auf /health.

  3. Render fragt nach jeder Variable mit sync: false. Füge sie ein.

  4. Bereitstellen, dann in den Logs nach server.listening ... auth=configured suchen. auth=MISSING bedeutet, dass MCP_AUTH_TOKEN nicht gesetzt wurde.

Manuell:

  1. NewWeb Service → das Repository verbinden.

  2. Runtime Node, Build npm ci && npm run build, Start npm start.

  3. Health-Check-Pfad /health.

  4. Die Umgebungsvariablen aus der obigen Tabelle hinzufügen, plus NODE_VERSION=20.

Überprüfen:

curl https://<your-service>.onrender.com/health
# {"status":"ok","server":{...},"tools":[...six...],"uptime_s":3}

Den Starter-Plan verwenden, nicht den kostenlosen. Chromium benötigt etwa 400 MB Arbeitsspeicher zusätzlich zu Node, und die kostenlose Instanz hat 512 MB – es wird mitten im Rendering ein OOM auftreten, und der Fehler erscheint als toter Worker statt als nützliche Logzeile. Der kostenlose Plan schläft außerdem nach Inaktivität ein, sodass der erste Tool-Aufruf des Tages zusätzlich einen 30–60 Sekunden dauernden Kaltstart bezahlt. render.yaml setzt aus beiden Gründen starter.

Beim Build ist kein Browser-Download nötig: @sparticuz/chromium bringt sein eigenes Binary als Abhängigkeit mit, also ist npm ci && npm run build der gesamte Build. Dieser Build-Schritt kopiert auch src/templates/ nach dist/ – tsc emittiert nur .ts, also startet der Server ohne diesen Schritt zwar einwandfrei, schlägt aber beim ersten Rendern mit einer fehlenden Template-Datei fehl.

4. Mit Claude verbinden

Der Endpunkt ist:

https://<your-service>.onrender.com/mcp

mit dem Header:

Authorization: Bearer <MCP_AUTH_TOKEN>

Claude Code / Cowork CLI:

claude mcp add --transport http weone-social \
  https://<your-service>.onrender.com/mcp \
  --header "Authorization: Bearer <MCP_AUTH_TOKEN>"

.mcp.json (projektbezogen, ohne Token eingecheckt):

{
  "mcpServers": {
    "weone-social": {
      "type": "http",
      "url": "https://<your-service>.onrender.com/mcp",
      "headers": { "Authorization": "Bearer ${MCP_AUTH_TOKEN}" }
    }
  }
}

Im Dialog für den benutzerdefinierten Connector von Claude Desktop/Web dieselbe /mcp-URL einfügen und das Bearer-Token in das Feld für den Request-Header setzen. Die Authentifizierung ist bewusst nur über den Header möglich – das Token wird nie als Query-Parameter akzeptiert, weil URLs in Proxy-Logs und Browserverläufen landen.

Die Anweisungen für den täglichen Lauf (Markenrichtlinien, verbotene Behauptungen, Kategorienrotation, Bildspezifikation, QA-Checkliste) liegen im weone-daily-post-Skill, nicht in diesem Server. Dieser Dienst enthält bewusst keine redaktionelle Richtlinie.

Lokale Entwicklung

npm install
cp .env.example .env      # fill it in
npm run dev               # tsx watch, http://localhost:10000
npm run typecheck
npm run build && npm start

npm run smoke

npm run smoke                 # render all three templates, upload, print 3 URLs
npm run smoke -- --no-upload  # render locally only, no credentials needed

Rendert je eine Vorlage von jedem Typ, schreibt alle drei JPEGs nach ./out, lädt sie hoch, prüft jede öffentliche URL per HEAD und gibt die drei Links aus. Danach beweist es, dass beide Guards weiterhin greifen. Es berührt keinen Meta-Endpunkt, ist also gegen Produktionsanmeldedaten sicher. Für den Upload werden nur SUPABASE_URL und SUPABASE_SERVICE_KEY benötigt; --no-upload benötigt nichts.

Lokale Dateien werden vor dem Upload geschrieben, sodass ein Supabase-Fehler trotzdem etwas zum Ansehen hinterlässt.

Rendering-Pipeline

Ein Headless-Chromium lädt src/templates/{template}.html über file://, die Werte werden in das DOM geschrieben und die Seite wird gescreenshottet. Dieselbe Eingabe erzeugt immer dieselben Pixel.

  • Vorlagen liegen in src/templates/. tokens.css enthält alle Farben; base.css enthält das Grundgerüst, das alle drei gemeinsam haben. Eine Vorlagendatei unterscheidet sich von ihren Geschwistern nur in der Eyebrow-Behandlung und dem Punkt-Marker (News: goldene Regel, Thema: nummerierte goldene Kreise, Karriere: goldene Chevrons).

  • Schriftarten sind selbst gehostet in src/templates/fonts/ (Barlow 400/600/700 für alles, Cinzel 600 nur für das Wortzeichen, lateinische Teilmengen, OFL). Beim Rendern wird nichts abgerufen – ein Netzwerkaufruf würde die Ausgabe nicht deterministisch machen und auf Render stillschweigend fehlschlagen, mit Fallback auf eine System-Serife. Der Renderer wartet auf document.fonts.ready und prüft dann, dass beide Schriftschnitte tatsächlich geladen wurden, statt einen Fallback zu screenshotten.

  • Kein Benutzertext wird jemals in Markup konkateniert. Werte gehen über textContent und createElement hinein, also gibt es kein Escaping, das falsch sein könnte: Ein <script> in einer Überschrift landet auf dem Poster als die wörtlichen Zeichen.

  • Viewport 1080×1350 bei deviceScaleFactor: 2, also ist der Screenshot 2160×2700 und wird herunterskaliert – Textkanten bleiben sauber.

  • sharp: resize(1080, 1350, {fit:'cover'})toColorspace('srgb')jpeg({quality: 90, chromaSubsampling: '4:4:4'}), Metadaten entfernt. 4:4:4 ist keine Dekoration – 4:2:0 verschmiert farbige Textkanten, und diese Poster sind Text.

  • Prüft, dass das kodierte JPEG unter 8 MB liegt und die dekodierten Abmessungen wirklich die angeforderten sind.

  • Upload als {yyyy-mm-dd}-{slug}-{6 hex}.jpg (UTC-Datum). Jedes Rendering bekommt seinen eigenen Schlüssel und nichts wird jemals überschriebenupsert: false. Ein erneutes Rendern eines Themas kann das Bild unter einem bereits veröffentlichten Beitrag mit der vorherigen URL nicht ändern. cacheControl ist aus demselben Grund 60s: Ein schlechtes Objekt bleibt innerhalb einer Minute korrigierbar, statt ein Jahr lang in einem CDN festzustecken. Meta ruft die URL einmal serverseitig kurz nach dem Upload ab, also braucht nichts den langen Cache. Objekte sammeln sich an; Speicher ist viel billiger als ein veraltetes Bild auf einem Live-Beitrag.

Layout-Verhalten

Die Typografie skaliert mit der Punktanzahl. Drei Punkte bekommen eine 68px-Überschrift und 36px-Textkörper; vier bekommen 60px und 32px. Das wird in CSS mit :has() gemacht, also liegt die Layout-Entscheidung vollständig in der Vorlage und der Renderer weiß weder davon noch kümmert es ihn. Der Overflow-Guard läuft nach der Skalierung, also wird das skalierte Ergebnis gemessen.

Der Inhaltsblock ist vertikal zentriert zwischen der Kopfzeilen-Regel und der Fußzeilen-Leiste. Feste Abstände allein können kein Füllziel halten, weil die Textmenge variiert, also teilen sich drei elastische Elemente den verbleibenden Raum: ein Band oben, ein Band unten und der Abstand unter der Überschrift. Die Bänder sind hart auf 150px begrenzt, was „kein großer leerer Rand“ erzwingt; sobald sie das Limit erreichen, fließt der Überschuss in den Abstand unter der Überschrift, wo er wie Atemraum wirkt statt wie ein Loch.

Gemessene vertikale Füllung bei repräsentativem Inhalt: 74–79 %, Bänder 99–124px. Ein bewusst spärlicher Fall (einzeilige Überschrift, drei einzeilige Punkte) liegt bei 68,8 % mit den Bändern an ihrem 150px-Limit – mit einer harten Obergrenze für die Bänder ist das das arithmetische Maximum für so wenig Text, und es weiter anzuheben würde bedeuten, die Punkte so weit zu verteilen, dass sie nicht mehr wie eine Liste lesen.

Überschriften sind Barlow 700 im Satzfall, Zeilenhöhe 1.1, Buchstabenabstand −0.5px. Cinzel überlebt nur im „WE ONE AVIATION“-Wortzeichen. Der Satzfall wird nicht im Code erzwungen – mechanisches Kleinschreiben einer Überschrift würde DGCA, ATPL und AAI zerstören – also wird er stattdessen in der Feldbeschreibung von headline spezifiziert.

Jede Vorlage trägt ein flaches SVG-Akzent inline: eine breite diagonale Regel (News), konzentrische Kompassbögen (Thema), ein aufsteigender Chevron-Stapel (Karriere). Gold bei 7 %, läuft unten rechts hinter dem Text aus. Sie existieren, um der Komposition in Thumbnail-Größe Gewicht zu geben, und sind viel zu dezent, um den Textkontrast zu beeinträchtigen.

Das Akzent sitzt in .anchor-wrap, einer Box, die mit overflow: hidden an der Leinwand fixiert ist. Ohne sie hängt die absolut positionierte Grafik über die untere Kante hinaus, zählt zu body.scrollHeight, und der Overflow-Guard lehnt jedes Rendering mit einem konstanten 160px-Seitenüberlauf ab.

src/templates/assets/logo.png ist das gelieferte Lockup: das Stern-/Flugzeugzeichen über einem „WE ONE AVIATION“-Wortzeichen. Der Header rendert dieses Wortzeichen selbst in Cinzel, also leitet scripts/prepare-logo.mjs logo-mark.png ab – nur das Zeichen – um den Markennamen nicht zweimal zu drucken. Es findet die horizontalen Bänder nicht transparenter Pixel und behält das höchste, sodass ein erneuter Export des Logos in einer anderen Auflösung weiterhin funktioniert. Nach dem Ersetzen von logo.png:

npm run prepare-logo

Browser-Lebenszyklus und Speicher

Ein Chromium wird für die Lebensdauer des Prozesses geteilt und nur neu gestartet, wenn es die Verbindung verliert. Das Starten kostet etwa eine Sekunde und ein paar hundert MB, viel zu viel, um es pro Beitrag zu wiederholen.

Render-Speicher. Chromium benötigt etwa 400 MB Arbeitsspeicher zusätzlich zu Node. Die kostenlose Instanz hat 512 MB und wird darunter einen OOM erleiden – der Deploy stirbt mitten im Rendering ohne nützliche Logzeile. Den Starter-Plan verwenden. Wenn du auf dem kostenlosen Plan bleiben musst, erwarte Neustarts und behandle das erste Rendering nach jedem als Kaltstart.

Woher das Binary kommt, hängt vom Host ab:

Host

Quelle

CHROMIUM_EXECUTABLE_PATH gesetzt

dieser Pfad, gewinnt immer

Linux (Render)

@sparticuz/chromium, das sein eigenes Binary mitbringt, also kein Browser-Download beim Build

macOS / Dev

was auch immer playwright-core bereits gecacht hat (npx playwright-core install chromium)

--single-process wird bewusst nicht verwendet. Es ist inkompatibel mit der Wiederverwendung eines Browsers: Das Schließen eines BrowserContext unter diesem Flag reißt den gesamten Browser mit, also schlägt das zweite Rendering mit „Target page, context or browser has been closed“ fehl. Gemessen an dieser Codebasis: 1 von 3 Kontexten überlebt damit, 3 von 3 ohne. Wiederverwendung ist die wertvollere Hälfte des Tauschs. Setze CHROMIUM_SINGLE_PROCESS=1, um es wieder zu erzwingen, falls ein Host es jemals verlangt, und erwarte ein Rendering pro Start.

Fehlerbehandlung

Code

Bedeutung

bad_input

Argumente haben die Validierung nicht bestanden.

duplicate_topic

Das Thema existiert bereits. Funktioniert wie vorgesehen – ein anderes wählen.

not_found

Keine posts-Zeile für diese history_id. Wurde reserve_topic aufgerufen?

db_error / storage_error

Supabase hat Nein gesagt. details trägt den Postgres-Code.

image_generation_failed / image_too_large

Provider- oder sharp-Problem.

image_url_unreachable

Die URL, die Meta abrufen würde, ist kein erreichbares JPEG.

caption_too_long

Der Caption-Text allein überschreitet 2200 Zeichen. Hashtags werden automatisch gekürzt; der Text selbst nie.

meta_error

Graph API. details hat code, error_subcode, type, fbtrace_id, unverändert.

timeout

Etwas hat sein Budget überschritten (Bild 60s, Container-Poll 60s, Graph 30s).

config_error

Eine erforderliche Umgebungsvariable fehlt. retryable: false.

Meta-Codes 190 und 200 werden nie erneut versucht. 190 ist ein abgelaufenes oder ungültiges Token, 200 ist eine fehlende Berechtigung; beide brauchen einen Menschen, und ein erneuter Versuch verbrennt nur das Rate-Limit, während die eigentliche Ursache verborgen bleibt. Diese Fehler kommen mit retryable: false und einer needs_human-Notiz zurück, die sagt, was zu tun ist.

Jeder Tool-Aufruf protokolliert tool.start und tool.ok/tool.error mit einer Dauer, und jeder Graph-Aufruf protokolliert graph.call mit Methode, Endpunkt, Status und verstrichenen Millisekunden – also reicht der Log-Viewer von Render aus, um einen Lauf zu rekonstruieren.

Fehlerbehebung

Symptom

Ursache

401 bei jeder Anfrage

Header fehlt, oder der Token stimmt nicht mit MCP_AUTH_TOKEN überein.

500 config_error bei /mcp, /health ok

MCP_AUTH_TOKEN ist auf dem Dienst nicht gesetzt.

image_url_unreachable

Der post-images-Bucket ist nicht öffentlich, oder der Upload ist still fehlgeschlagen. Führe npm run smoke aus.

IG-Container hängt 60s lang bei IN_PROGRESS

Meta kann das Bild nicht abrufen oder ist langsam. Prüfe die URL zuerst in einem Browser.

meta_error Code 190

Token abgelaufen. Erstelle einen neuen langlebigen Page-Token. check_token() warnt bei ≤7 Tagen.

meta_error Code 200

Scope fehlt — erteile instagram_content_publish / pages_manage_posts erneut.

duplicate_topic

Kein Bug. Das Thema ist bereits in posts enthalten.

Erster Aufruf des Tages läuft in einen Timeout

Kaltstart im Free-Plan. Rufe zuerst /health auf oder wechsle zu starter.

Worker stirbt während des Renderings, kein Fehler

Nicht genügend Arbeitsspeicher. Chromium benötigt ~400 MB; die kostenlose Instanz hat 512 MB. Wechsle zu starter.

bad_input nennt ein Feld

Funktioniert wie vorgesehen. Kürze das genannte Feld und rufe render_post erneut auf — es wurde nichts hochgeladen.

Self-hosted fonts failed to load

src/templates/fonts/ hat dist/ nicht erreicht. Führe npm run build erneut aus; das Rendering wird verweigert, statt mit einer Serifen-Fallback-Schrift ausgeliefert zu werden.

Target page, context or browser has been closed

CHROMIUM_SINGLE_PROCESS=1 ist gesetzt. Dieses Flag erlaubt nur ein Rendering pro Start — entferne es.

Vorlagendatei auf Render nicht gefunden

npm run build wurde übersprungen, daher fehlt dist/templates/.

Layout

src/
  server.ts            Express, bearer auth, /health, POST /mcp
  config.ts            Lazy env resolution, constants
  log.ts               Timed stdout logging
  errors.ts            AppError / MetaError, the no-retry rule for 190 & 200
  supabase.ts          posts CRUD + storage upload
  meta.ts              Graph client, IG container flow, FB photos, debug_token
  image.ts             Chromium lifecycle, template render, overflow guard, sharp
  tools/
    register.ts        Timing, error envelope, content-block shaping
    get_past_topics.ts reserve_topic.ts render_post.ts
    publish_socials.ts mark_draft.ts check_token.ts
    index.ts
  templates/
    tokens.css base.css          design tokens + shared skeleton
    news.html subject.html career.html
    fonts/     barlow-400/600/700, cinzel-600 (woff2, self-hosted)
    assets/    logo.png (supplied), logo-mark.png (derived)
migrations/001_init.sql
scripts/
  smoke.ts           render all three, upload, prove both guards fire
  copy-templates.mjs build step: tsc emits only .ts, templates must reach dist/
  prepare-logo.mjs   derives logo-mark.png from logo.png
render.yaml
F
license - not found
Not graded
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

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

Related MCP Servers

  • A
    license
    A
    quality
    C
    maintenance
    Enables AI assistants to manage Instagram and Threads accounts — publish content, handle comments, view insights, search hashtags, and manage DMs through the Meta Graph API.
    59
    46
    10
    MIT
  • A
    license
    A
    quality
    D
    maintenance
    MCP server to safely publish posts to multiple Facebook Pages via Meta Graph API, with built-in guardrails for brand voice, banned topics, image requirements, and anti-duplication.
    4
    MIT
  • F
    license
    Not graded
    quality
    C
    maintenance
    MCP server for automatic Instagram publishing — single image, carousel and Reels — via the official Instagram Graph API.
    40

View all related MCP servers

Related MCP Connectors

  • Create, schedule and publish social posts to TikTok, Instagram, Facebook and YouTube.

  • Schedule and publish social posts to 11 platforms with media, campaigns, analytics and AI captions

  • Boost posts and launch community growth campaigns from your AI assistant. OAuth, credit-billed.

View all MCP Connectors

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/Weone404/mcp-content-cron'

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